MAS 9.1 + App Connect Enterprise: 7 Integration Patterns for ERP, GIS, and IoT

Modern Maximo integrations live on App Connect Enterprise, Kafka, and the OSLC/REST APIs. Here are 7 patterns that have held up at scale — for ERP sync, GIS, IoT ingest, and the SSO/OIDC boundary that protects it all.

Share

The integration layer of a Maximo Application Suite deployment is where the platform meets the rest of the enterprise. Done well, it is invisible — data flows, systems stay in sync, the user never thinks about it. Done poorly, it is the source of 70% of the incident tickets.

This article catalogs 7 integration patterns that have held up at scale in production at a global logistics company, a North American utility, and a Middle Eastern national oil company. The patterns are written for MAS 9.1.x + App Connect Enterprise 12.0.x + Kafka 3.6 (Strimzi) + IBM webMethods 10.15 as the typical 2026 stack. The same patterns work with the alternative tools (MuleSoft, Boomi, Solace, Confluent) with minor tweaks.

Pattern 1: The bidirectional ERP sync (SAP S/4HANA → Maximo)

The most common integration: equipment masters, functional locations, and GL accounts flow from SAP to Maximo. Work order costs and actuals flow from Maximo to SAP. The naive approach is a nightly batch file. The 2026 approach is event-driven, near-real-time, idempotent.

The pattern:

  • SAP side: SAP iDoc or SAP BAPI → SAP Integration Suite → Kafka topic
  • Maximo side: App Connect flow consumes the Kafka topic, transforms to the MIF object structure, posts to the MIF REST endpoint
  • Idempotency: Every MIF POST includes the source system's external ID + last-updated timestamp. The MIF endpoint dedupes on (external system, external ID) and rejects any update older than the existing row.
# App Connect flow — abbreviated
name: sap-equipment-to-maximo
source:
  type: kafka
  topic: sap.equipment.master.changed
  consumer-group: mas-sap-equipment
stages:
  - type: filter
    where: change_type in ('created', 'updated')
  - type: map
    mapping:
      maximo.Asset.assetnum: $.externalId
      maximo.Asset.description: $.description
      maximo.Asset.location: $.funcLoc
      maximo.Asset.glaccount: $.glAccount
      maximo.Asset.externalrefid: $.externalId
      maximo.Asset.lastSapUpdate: $.changedAt
  - type: mif-post
    object: ASSET
    endpoint: ${MAXIMO_MIF_URL}/mif/oslc/os/mxapiasset
    idempotency-key: $.externalId + ':' + $.changedAt
    on-conflict: update-if-newer

The key details:

  • on-conflict: update-if-newer — only update if the source timestamp is newer than the existing row's lastSapUpdate. This handles the case where two updates arrive out of order.
  • idempotency-key — the external ID + timestamp. The MIF endpoint stores this in a MAXINTOBJECTS audit table, so a replay of the same Kafka message is a no-op.
  • ${MAXIMO_MIF_URL} — a Kubernetes secret mounted into the App Connect flow. The URL rotates with the MAS deploy, not hardcoded.

The reverse direction (Maximo → SAP) is a Kafka publish from the Maximo event bus on every work order close. The same idempotency pattern applies. The full round-trip latency is under 30 seconds at p99.

Pattern 2: The GIS asset visualization (Esri ArcGIS → Maximo)

The use case: the asset operator wants to see assets on a map, click an asset, and see the Maximo work order history. The naive approach is a nightly export from Maximo to a GIS feature service. The 2026 approach is live feature service, MIF-driven, with sub-second drift.

The pattern:

  • Esri side: ArcGIS Enterprise feature service, versioned, hosted on a Kubernetes-deployed ArcGIS site
  • Maximo side: App Connect flow consumes Maximo asset change events from Kafka, applies the change to the ArcGIS feature service via the REST API
  • Visualization: The Maximo Spatial Asset Management app consumes the feature service, renders the assets, and provides a click-through to the Maximo asset record
name: mas-asset-to-arcgis
source:
  type: kafka
  topic: mas.core.asset.changed
  consumer-group: mas-asset-arcgis
stages:
  - type: filter
    where: type = 'ASSET' and hasGeo IS true
  - type: arcgis-apply-edits
    service-url: ${ARCGIS_FEATURE_SERVICE_URL}
    layer: 0
    operation: update
    feature-id: $.externalRefId
    geometry:
      x: $.x
      y: $.y
      spatialReference: 4326
    attributes:
      assetnum: $.assetnum
      description: $.description
      status: $.status
      lastUpdate: $.changedAt

The key details:

  • feature-id: $.externalRefId — the feature service uses the Maximo external reference ID as the primary key. No join table.
  • hasGeo IS true — only sync assets that have a valid lat/long. Skip the assets that are still in the office.
  • ArcGIS feature service is versioned — every change is recorded, no destructive overwrites.

The latency is 5-15 seconds at p99, which is fast enough for a click-to-see-history interaction.

Pattern 3: The IoT telemetry ingest (Kafka → MAS Monitor)

The use case: 50,000 sensors on a fleet of pumps, motors, and valves, each publishing a telemetry reading every 10 seconds. Maximo Monitor needs the data, but Maximo Manage only needs the alerts. The pattern: two parallel pipelines, one for the time-series data, one for the alert generation.

The pattern:

  • IoT side: Edge gateways publish to a Kafka topic (one per asset class)
  • MAS Monitor side: Monitor pipeline consumes the raw topic, stores in the time-series database (InfluxDB or the MAS built-in store)
  • MAS Manage side: A second Monitor pipeline consumes the same topic, applies thresholds, publishes to the mas.health.scores Kafka topic, which the Maximo event bus consumes to create work order records when a threshold is crossed
name: iot-pump-telemetry-to-monitor
source:
  type: kafka
  topic: iot.pump.telemetry
  consumer-group: mas-monitor-pump
stages:
  - type: monitor-ingest
    measurement: pump_vibration_rms
    asset-key: $.assetId
    timestamp: $.ts
    value: $.vibrationRms
  - type: monitor-ingest
    measurement: pump_temperature_c
    asset-key: $.assetId
    timestamp: $.ts
    value: $.temperatureC
  - type: kafka-publish
    topic: mas.health.scores
    value:
      assetId: $.assetId
      vibrationRms: $.vibrationRms
      temperatureC: $.temperatureC
      timestamp: $.ts

The key details:

  • Two pipelines, one source — Monitor gets the raw data, Maximo gets the derived alerts. The 50K-per-second firehose never hits the Maximo Manage application server.
  • Time-series database is the source of truth — Monitor is a horizontal-scale service, not a Maximo Manage singleton. The data outlives the Maximo Manage pod restarts.
  • Alerts are out-of-band — the mas.health.scores topic is consumed by the Maximo event bus, which generates work orders asynchronously. The IoT pipeline never blocks on Maximo Manage availability.

The throughput is >100K messages/second at p99, validated at a US utility with 40,000 sensors.

Pattern 4: The OSLC integration (Polarion ALM → Maximo)

The use case: the engineering team tracks requirements and defects in Polarion ALM, the maintenance team tracks work orders in Maximo. The defect in Polarion needs to create a work order in Maximo, and the work order closure needs to close the defect in Polarion. The pattern: OSLC (Open Services for Lifecycle Collaboration), with the Change Management (CM) and Quality Management (QM) domains.

The pattern:

  • Polarion side: OSLC CM provider, exposes defects as oslc_cm:ChangeRequest
  • Maximo side: OSLC consumer, registers the Polarion service, consumes the defects
  • Sync: App Connect flow subscribes to the Polarion OSLC feed, creates a Maximo work order, and stores the Polarion URL in the work order's OSLCURL field
name: polarion-defect-to-wo
source:
  type: oslc
  service-url: ${POLARION_OSLC_URL}
  domain: cm
  resource-type: oslc_cm:ChangeRequest
  filter: oslc_cm:status = 'submitted'
stages:
  - type: mif-post
    object: WORKORDER
    endpoint: ${MAXIMO_MIF_URL}/mif/oslc/os/mxapiwo
    payload:
      description: '[Polarion ' + $.dc:identifier + '] ' + $.dc:title
      oslcurl: $.dc:identifier + ' (' + $.dc:title + ')'
      priority: mapPriority($.severity)
    on-success:
      - type: oslc-update
        service-url: ${POLARION_OSLC_URL}
        resource-url: $.about
        payload:
          oslc_cm:status: 'in-progress'
          oslc_cm:relatedChangeRequest: ${WO_URL}

The key details:

  • oslc:cm domain — the standardized lifecycle domain, not a custom REST hack.
  • Bidirectional — the work order URL is stored in the Polarion defect, so clicking the defect in Polarion shows the linked Maximo work order, and clicking the work order in Maximo shows the linked Polarion defect.
  • Status mapping — the mapPriority function translates Polarion severity (Blocker, Critical, Major, Minor) to Maximo priority (1, 2, 3, 4).

The pattern is reproducible for any OSLC-compliant tool — Doors Next, Jama, Simulink, even other Maximo instances in a federated deployment.

Pattern 5: The SSO/OIDC boundary (Okta → MAS)

The use case: corporate users sign in to Okta, they should land in Maximo without a second login. The naive approach is a SAML 2.0 IdP-initiated flow. The 2026 approach is OIDC, with PKCE, with a refresh token, and with the MAS user record keyed on the Okta subject claim.

The pattern:

  • Okta side: OIDC app, scopes openid profile email maximo:read maximo:write
  • MAS side: OIDC client registered in the MAS Auth Service, with the Okta issuer URL, client ID, and client secret
  • Token exchange: The MAS session is bound to the OIDC ID token's sub claim. On every MAS API call, the OIDC access token is validated against the Okta JWKS endpoint.
# MAS auth config
auth:
  provider: oidc
  oidc:
    issuer: ${OKTA_ISSUER}
    client-id: ${OKTA_CLIENT_ID}
    client-secret: ${OKTA_CLIENT_SECRET}
    jwks-uri: ${OKTA_ISSUER}/.well-known/jwks.json
    redirect-uri: https://maximo.example.com/maximo/oauth/callback
    scopes:
      - openid
      - profile
      - email
      - maximo:read
      - maximo:write
    claim-mapping:
      maximo.userid: sub
      maximo.displayname: name
      maximo.email: email
      maximo.groups: groups

The key details:

  • maximo.userid: sub — the Okta subject is the Maximo user ID. No display name lookup, no email lookup, no second join.
  • Groups flow from Okta — the Okta groups claim drives Maximo security group membership. Provisioning is a one-time job, deprovisioning is automatic.
  • PKCE — the public client (mobile) uses PKCE, the confidential client (web) uses the client secret. Both are valid OIDC flows, both work in MAS 9.1.

The same pattern works for Azure AD, Ping, ForgeRock, Auth0, Keycloak. The only thing that changes is the OIDC discovery URL.

Pattern 6: The Kafka event bus (MAS → downstream)

The use case: every work order status change in Maximo should be published to a Kafka topic that downstream consumers (data lake, reporting, audit) can subscribe to. The naive approach is a cron task that polls the work order table. The 2026 approach is the MAS event bus, which is a Kafka-native publish on every MBO save.

The pattern:

  • Maximo side: The event bus is enabled in the MAS configuration. Every MBO save generates a Kafka message to the topic mas.core.{objectname}.{crud}. For example, mas.core.workorder.update.
  • Downstream side: A consumer (Confluent, Databricks, Snowflake) subscribes to the topic and processes the events.
// Example event
{
  "object": "WORKORDER",
  "operation": "UPDATE",
  "wonum": 12345,
  "status_old": "APPR",
  "status_new": "INPRG",
  "changed_by": "maxadmin",
  "changed_at": "2026-06-21T10:23:45.123Z",
  "siteid": "BEDFORD",
  "orgid": "EAGLENA",
  "rowstamp": 987654321
}

The key details:

  • Topic naming is mas.core.{object}.{crud} — predictable for the consumer, easy to filter.
  • Every field change is included — the consumer does not need to re-fetch the work order to get the new state.
  • Rowstamp is included — the consumer can dedupe replays with the optimistic locking key.

The throughput is >10K events/second at p99, validated at a global logistics company with 12,000 daily work order transactions.

Pattern 7: The MAS MIF REST endpoint with OSLC discovery

The use case: an external system needs to integrate with Maximo but does not know the MIF object structure. The pattern: OSLC discovery, where the MAS service catalog is browsable.

The pattern:

  • MAS side: MIF service catalog is published at ${MAXIMO_URL}/mif/oslc
  • Consumer side: A discovery client follows the OSLC service provider catalog, finds the relevant object structure, and uses the documented REST API.
# Discover the service catalog
curl -H "Accept: application/json" ${MAXIMO_URL}/mif/oslc/catalog
# Returns the list of OSLC service providers

# Discover the asset service
curl -H "Accept: application/json" ${MAXIMO_URL}/mif/oslc/os/mxapiasset
# Returns the asset service description, including the supported properties, the link to the creation factory, and the query base

# Query the asset service
curl -H "Accept: application/json" ${MAXIMO_URL}/mif/oslc/os/mxapiasset?oslc.where=assetnum="PUMP-001"
# Returns the asset record

The key details:

  • Service catalog is browsable — the consumer does not need pre-shared documentation.
  • Creation factory is documented — the POST endpoint and the required properties are in the service description.
  • Query base is standardizedoslc.where, oslc.select, oslc.searchTerms are the OSLC standard query parameters.

The pattern is future-proof — any OSLC-aware client (Rational, Polarion, Doors, Jama) can integrate without custom adapters.

The bottom line

The 7 patterns above are the most common, the most load-bearing, and the most upgrade-resilient integrations in a MAS 9.1 deployment. They are not the only patterns — there are dozens more — but they are the ones that have held up at scale, in production, across multiple industries. If you are building a new MAS deployment, start with these. If you are maintaining an existing one, audit your current integrations against them. The ones that match are your foundation. The ones that don't are your technical debt.

Read more