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.
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:
- Log at boundaries, not at every step: what arrived, what came out of each meaningful transformation, what was sent.
- Do not log what must not leak. MPL attachments are readable by anyone with access to the monitor. Personal data and credentials stay out — and that decision belongs in the design, not in the audit.
- Propagate a correlation header. Without one, tracing a transaction across three iFlows becomes archaeology by timestamp.
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:
- Does the sender require authentication?
- Is there an Exception Subprocess, and does it end in an Error End Event?
- Is there a declared retry policy, and what happens when it is exhausted?
- Is there logging at boundaries, with no sensitive data?
- Is the correlation header propagated?
- Are the adapter timeouts explicit?
- 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.