iFlowMind

SAP CPI

Error handling in CPI: passing is not surviving

11 min read · updated September 2026

An iFlow imports into CPI, runs with the test payload and delivers the message to the target. That means it works. It does not mean it survives — surviving is what happens when the payload arrives empty, when the target returns 503, when an optional field disappears, when the certificate expires on a Tuesday at three in the morning.

The distance between those two states almost always comes down to four error-handling decisions. None of them is hard. All of them tend to be left for later.

1. The catch that makes a message disappear

This is the most common and the most expensive. A Groovy script inside the flow catches the exception so as “not to break processing”, and carries on:

try {
    def json = new JsonSlurper().parseText(body)
    total = json.items.sum { it.price * it.quantity }
} catch (Exception e) {
    total = 0     // on we go
}

The flow finishes successfully. The MPL shows green. And the order went to Salesforce with a total value of zero.

The mistake here is not using try/catch — it is using it to hide. Catching is legitimate when there is a real decision about what to do with the failure; when the only thing the block does is stop the exception from propagating, it is not handling the error, it is erasing the evidence of it.

The version that survives records and rethrows:

try {
    def json = new JsonSlurper().parseText(body)
    def items = json?.items
    if (!items) {
        message.setProperty("OrderTotalWarning", "payload with no items")
    }
    total = items ? items.sum { (it?.price ?: 0) * (it?.quantity ?: 0) } : 0
} catch (Exception e) {
    // Visible in the MPL, and the message stays marked as failed
    message.setProperty("ErrorMessage", "processData failed: ${e.message}")
    throw new IllegalArgumentException("Failed to parse JSON payload", e)
}

Three small changes: safe navigation (?.) stops a missing field from becoming a NullPointerException; the warning property leaves a trace even on the non-error path; and the throw preserves CPI's behaviour — the message is marked as failed and the exception subprocess fires.

Rule Catching without rethrowing is only acceptable when the flow has a defined alternative path for that case. If it does not, the catch is trading a visible incident today for wrong data discovered in three weeks.

2. Exception Subprocess is not optional

An iFlow without an Exception Subprocess hands the raw error to the caller — or, worse, simply ends. With one, you decide what happens: record the context, return an agreed HTTP code, persist the message for reprocessing, notify.

One detail that slips by: the subprocess has to end in an Error End Event, not a plain End Event. Ending normally tells CPI the error was handled successfully, and the message shows as completed in the monitor. You gained the handling and lost the record of the failure — the green message that lies, again.

3. Retry that doesn't hide the cause

“Three attempts, five minutes apart” solves the transient failure: a momentary 503, a network timeout, a token that expired half a second early. That is what it is for, and all it is for.

The problem appears when retry becomes the answer to any error. A 401 from wrong credentials does not improve on the third attempt; nor does a malformed payload. What retry does in those cases is delay the diagnosis and multiply the load on the target.

In practice: retry only errors that can resolve themselves (5xx, timeout, connection failure) and fail fast on the rest (4xx for authentication, contract, validation). And define explicitly what happens when the attempts run out — returning an error to the caller, persisting to a Data Store or sending to a dead-letter queue are different decisions with different operational consequences.

4. Logging that helps you debug, not logging that fills disk

The MPL accepts attachments, and that is what turns an error into something diagnosable: the payload that came in, the payload that came out of the transformation, the relevant headers. Without them, what is left in the incident is the exception message — and the exception message almost never explains which piece of data caused the failure.

Three habits separate useful logging from logging nobody reads:

And the logging itself has to be defensive: messageLogFactory is injected by the CPI runtime, but in simulation or with tracing off the object may not be there. A logging script that brings down the flow because logging failed is an expensive irony.

def messageLog = messageLogFactory?.getMessageLog(message)
if (messageLog != null) {
    messageLog.addAttachmentAsString("InboundPayload", body ?: "", "text/plain")
}

The arithmetic nobody does

Each of those four items costs between ten minutes and an hour at build time. None of them shows up in acceptance testing, because acceptance testing uses the happy payload. And all of them show up in the first production incident — when the cost is no longer measured in development hours, but in downtime and in data that has to be reconciled by hand.

Which is why it is worth measuring iFlow quality before the import, not after the incident. The questions are objective and fit on a list:

  1. Does the sender require authentication?
  2. Is there an Exception Subprocess, and does it end in an Error End Event?
  3. Is there a declared retry policy, and what happens when it is exhausted?
  4. Is there logging at boundaries, with no sensitive data?
  5. Is the correlation header propagated?
  6. Are the adapter timeouts explicit?
  7. Do the scripts handle a missing field without breaking the flow?

Where this touches what we build

iFlowMind's iFlow validator answers those questions by reading the package ZIP: it scores 0 to 100 per axis — security, error handling, observability, performance, best practice — and lists the findings by severity, before the package goes into CPI.

A confession is in order: when we ran a package generated by our own tool through the validator, it scored 75 out of 100, with one critical finding — sender with no authentication. That is exactly the behaviour you want from a validator that is worth anything. A tool that approves whatever it produces is not measuring anything.


Read next: The eight questions an FSD never answers.