CPI

The silent drop problem

You deploy an iFlow, it runs, the message monitor shows Completed β€” but the target system never received the data. No errors, no warnings, just silence.

This is one of the most frustrating debugging experiences in CPI because the tooling actively misleads you. A Completed status means the iFlow finished without throwing an unhandled exception. It says nothing about whether your data actually reached its destination.

Silent drops happen for three main reasons:

  1. Exception caught and swallowed β€” an Exception Subprocess catches the error but ends with a normal End Message, marking the message as Completed
  2. Router sends message to wrong branch β€” a Router step has a default catch-all route that discards messages that don't match any condition
  3. JMS acknowledgement before processing β€” the JMS adapter acknowledges the message before the processing succeeds, so retries don't happen

Pattern 1 β€” Exception subprocess

This is the most common cause. A developer adds an Exception Subprocess (correctly), but the subprocess body just has a Content Modifier and an End Message β€” no escalation, no error queue, no notification.

The fix: end every Exception Subprocess with an Error End Event (not End Message). An Error End Event marks the message as Failed in the monitor, which triggers alerts and makes the failure visible.

<!-- iFlow XML snippet showing the difference -->
<!-- Wrong: silently completes -->
<bpmn:endEvent id="EndMessage_1">
  <bpmn:terminateEventDefinition />
</bpmn:endEvent>

<!-- Right: marks as Failed -->
<bpmn:endEvent id="ErrorEnd_1">
  <bpmn:errorEventDefinition errorRef="Error_1" />
</bpmn:endEvent>
Rule

Exception Subprocess + End Message = silent completion. Exception Subprocess + Error End Event = visible failure. Always use Error End Event in exception handling.

Pattern 2 β€” Dead letter monitoring

For JMS-based iFlows, even with correct exception handling, messages can end up in the dead letter queue without any alert. The dead letter queue fills up silently unless you actively monitor it.

Build a second monitoring iFlow that runs on a schedule (every 15 minutes in production):

import groovy.json.JsonSlurper

def Message processData(Message message) {
    def headers = message.getHeaders()
    def queueDepth = Integer.parseInt(headers.get('X-DLQ-Depth', '0'))

    if (queueDepth > 0) {
        message.setProperty('ALERT_REQUIRED', 'true')
        message.setProperty('ALERT_MESSAGE',
            "Dead letter queue has ${queueDepth} messages")
    }
    return message
}

Route to a notification adapter (HTTP webhook to Slack or email adapter) when ALERT_REQUIRED = true.

Pattern 3 β€” Correlation IDs

Even when messages complete successfully, you can't trace them across a chain of iFlows without correlation IDs. When iFlow A calls iFlow B calls iFlow C, each creates a separate MPL entry β€” and they're not linked by default.

Set a correlation ID at the entry point of your integration landscape:

// In the first iFlow that receives the message
def Message processData(Message message) {
    def headers = message.getHeaders()

    // Prefer an existing correlation ID from the caller
    def correlationId = headers.get('X-Correlation-ID')
        ?: headers.get('X-Request-ID')
        ?: UUID.randomUUID().toString()

    message.setHeader('X-Correlation-ID', correlationId)

    // This makes it searchable in the MPL
    message.setProperty(
        'SAP_MessageProcessingLogCustomStatus',
        "CorrelationID: ${correlationId}"
    )
    return message
}

Pass the X-Correlation-ID header to every outbound adapter call. When an incident happens, search the message monitor by this ID across all iFlows to see the complete message journey.

← All articles