I am working on a large ECC to S/4HANA migration where the SAP Cloud Integration tenant stays the same .Existing iFlows are repointed to S/4HANA rather than rebuilt. The cutover therefore requires interfaces talking to ECC to be stopped before data migration, repointed, and brought back up in a controlled sequence. Crucially, only the iFlows touching the migrating SAP system need to stop; the same tenant runs third party and non-SAP interfaces that must keep running. So "undeploy everything" is not an option . What is needed is selective undeploy and deploy of a defined list and this is exactly what this tool does.
On SAP PI/PO this was a solved problem: PIMON let you filter, multi-select and stop/start channels centrally, with status visible in the same place. CPI has no equivalent . The web UI handles one artifact at a time, which in a cutover window is slow and error prone. The model also differs: PI stopped the channel while the scenario stayed in place; in CPI the lever is undeploying the artifact from the runtime — design untouched, but stopping means removing the deployed artifact.
The case for a tool comes down to two numbers — time and error rate. At real cutover scale, say 250 iFlows in scope:
Reduce time. Manually, each undeploy is filter, select, click Undeploy, confirm, verify — 30 to 45 seconds per iFlow on a responsive tenant. That is close to 3 hours of pure clicking for 250 iFlows, and with scope cross-checking, evidence capture and interruptions, realistically 4 to 6 hours, usually split across several people — which adds its own coordination risk of items falling between the splits. The tool executes the same 250 undeploys via the API in under 10 minutes.
Reduce error rate. Human accuracy on a repetitive 250-item selection task over hours is not 100%. Even a 1–2% slip rate means 2 to 5 wrong outcomes: an in-scope iFlow missed and still pumping messages into ECC during data migration, or an out-of-scope one undeployed — taking down a live interface that had nothing to do with the cutover. Worst of all, manual misses are silent. With the tool, the scope is a reviewed CSV and the follow-up -status run is a complete, deterministic audit of all 250 — not a memory of what got clicked.
This blog describes a command line tool I built to solve this .The setup is:
CSV list of iFlows --> Java CLI tool --> CPI OData API (v1) --> CPI runtime
During cutover we needed:
A reviewed and signed off list of in-scope iFlows
A status check before the shutdown window (everything STARTED)
A bulk undeploy of exactly that list
A status check after (everything gone from runtime
Console output that can be pasted into the cutover log as evidence
Only the interfaces in scope should be touched. Other integrations on the tenant keep running.
The tool is a single jar Java 21 application taking three arguments:
java -jar cpi-iflow-controller-1.1.0.jar <config-file> <iflows-csv> <-status|-undeploy|-deploy>
The iFlow list is a two column CSV — package and iFlow name:
Package,iFlow Name
PACKAGEMONTHLYEXTRACTINTERFACES,MONTHLY_TAX_DATA
PACKAGEOTCOBJECTS,OTC_BILLING_DATA
PACKAGEOTCOBJECTS,OTC_MASTER_DATA
PACKAGEOTCOBJECTS,OTC_FI_DATA
Note the Package column contains technical package Ids.
When the cutover task says "stop in-scope interfaces", the definition of in-scope is this file.
This one cost me a debugging session. I first populated the CSV from the Manage Integration Content monitor, where every deployed iFlow shows a Package field:
The first run failed for every iFlow with "iFlow not found in package" — even though the monitor showed all of them as Started:
The reason: the Package field in the monitor is the package display name, but the OData API keys on the technical package Id — and the two differ. The Id is auto-generated from the name with special characters (including underscores) stripped, so a package named PACKAGE_OTC_OBJECTS has the Id PACKAGEOTCOBJECTS:
The same applies to artifacts — an iFlow has a display name and a technical Id, and the API wants the Id. In my case the two matched (the monitor shows both, under Name and ID), but do not assume they do.
Where to find the technical Ids:
Open the package in Design — the Package ID is on the Overview tab and in the browser URL (.../shell/design/contentpackage/<Id>)
Or query the API directly:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://<tmn-host>/api/v1/IntegrationPackages?$select=Id,Name&$format=json"
With the technical Ids in the CSV, the same run succeeds:
One more useful behaviour: for -status the package column can be left empty. The runtime API only needs the iFlow Id, so the tool skips package resolution and queries IntegrationRuntimeArtifacts directly:
Rule of thumb: the CSV takes technical Ids, not the display names in the monitor. For status-only runs the package column is optional; for deploy it must be correct, because the design-time lookup depends on it.
java -jar target/cpi-iflow-controller-1.1.0.jar config.properties iflows.csv -status
This prints the runtime status of each iFlow in the CSV:
+---------+---------------------------------+------------------+
| STATUS | PACKAGE | IFLOW |
+---------+---------------------------------+------------------+
| STARTED | PACKAGEMONTHLYEXTRACTINTERFACES | MONTHLY_TAX_DATA |
| STARTED | PACKAGEOTCOBJECTS | OTC_BILLING_DATA |
| STARTED | PACKAGEOTCOBJECTS | OTC_MASTER_DATA |
| STARTED | PACKAGEOTCOBJECTS | OTC_FI_DATA |
+---------+---------------------------------+------------------+
Summary: 4 succeeded, 0 failed (4 total)
Status is run at two points in the cutover: before the shutdown window to confirm the baseline (everything STARTED), and after the undeploy to confirm the interfaces are down. The two captures become the before/after evidence for the runbook verification task. Beyond cutover, the same command doubles as a quick health check across a defined interface list — after a tenant update or during hypercare. A useful side effect: it surfaces artifacts already in ERROR state, which you want to find before the cutover window, not during it.
java -jar target/cpi-iflow-controller-1.1.0.jar config.properties iflows.csv -undeploy
This walks the same CSV and undeploys each artifact via the OData v1 API (IntegrationRuntimeArtifacts). The summary line shows immediately if anything failed.
An important detail: undeploy is asynchronous in CPI. The OData call only submits the request — the API accepts it and returns, while removal from the runtime node happens in the background. A successful call means "the stop was requested", not "the interface is down". If an iFlow is mid-message, the runtime winds that down first. This is visible in Manage Integration Content right after the bulk undeploy — all four iFlows sit in Stopping state.
This is exactly why status matters in the shutdown sequence. After -undeploy, run -status against the same CSV; if some artifacts still show up, wait and re-run until the list is clear. The evidence that satisfies the verification task is the status output showing the interfaces are down — not the undeploy summary showing requests were submitted. The undeploy is the trigger; the status check is the confirmation.
Connectivity uses a properties file for the credentials
cpi.base.url=https://your-tenant.it-cpi018.cfapps.eu10.hana.ondemand.com
cpi.auth.type=oauth2
cpi.oauth.token.url=https://your-tenant.authentication.eu10.hana.ondemand.com/oauth/token
cpi.oauth.client.id=your-client-id
cpi.oauth.client.secret=your-client-secret
One config file per tenant: the same CSV is exercised against test during cutover rehearsal, then replayed against production by changing a single argument.
The service key comes from a Process Integration Runtime instance with the api plan (not *integration-flow*, which is only for calling deployed iFlows). Create the instance in the BTP cockpit under Services > Service Marketplace, select the roles at creation, and create a service key with grant type client_credentials. The clientid, clientsecret, tokenurl and url map directly to config.properties above.
Three roles are needed, mapped to what each mode does:
Role | Used for |
MonitoringDataRead | -status — reading runtime artifact status via IntegrationRuntimeArtifacts |
WorkspacePackagesRead | Resolving iFlows inside a package at design time (IntegrationPackages / IntegrationDesigntimeArtifacts) |
WorkspaceArtifactsDeploy | -deploy and -undeploy — deploying and undeploying artifacts |
These are set as parameters at instance creation. The exact configuration the tool has been verified against:
The token-validity of 43200 seconds gives a 12 hour token — longer than any cutover window, so the tool requests one token per run and never refreshes mid-sequence. Role names per API operation are listed in the SAP Help Portal under "Tasks and Permissions" for the Cloud Integration OData API.
A recommendation from experience: create a dedicated instance and key with only these roles, rather than reusing a broad integration developer key or an AuthGroup role — least privilege applies to cutover tooling too. For status-only runs (e.g. a hypercare health check), MonitoringDataRead and WorkspacePackagesRead are enough. And keep the filled-in config.properties out of Git — the repo commits templates only.
The tool also supports a deploy mode:
java -jar target/cpi-iflow-controller-1.1.0.jar config.properties iflows-wave1.csv -deploy
However, shutdown and startup are not used the same way. In this migration the deploy step matters even more: between shutdown and startup the iFlows are repointed from ECC to S/4HANA — connection details, externalized parameters and endpoints updated — so each wave brings up the reconfigured interface, validated wave by wave against the new environment.
Shutdown is a blanket action — everything in scope must be down before migration starts, so one bulk undeploy of the full CSV is exactly right. Startup is a controlled start — interfaces return in a deliberate sequence, typically master data flows first and then transactional, gated by smoke tests and sign-offs between waves. Starting everything in one shot would flood a freshly migrated system with queued messages before anyone has validated the first interface.
At the same time, a wave is rarely a single interface — a master data wave might hold a dozen iFlows that come up together, and deploying those one by one through the UI has the same problem as the shutdown, just smaller. So the same CSV driven approach applies, with one CSV per wave:
Running: deploy with a wave CSV brings up all its iFlows in one command. Deploy is asynchronous like undeploy — the call submits, the runtime takes time to start — so the pattern repeats: -deploy the wave, then -status against the same CSV until everything shows STARTED, then run the smoke tests. Only after sign-off does the next wave go up.
I ran the full sequence on my tenant with the four iFlows split into two waves: all four stopped in one bulk undeploy (the Stopping screenshot above), then wave 1 (the three OTC iFlows) and wave 2 (MONTHLY_TAX_DATA) started separately.
Deploying wave 1 brings up all three OTC iFlows in one command — the DEPLOYING result confirms the requests were accepted
The asynchronous start is visible in the UI moments later — OTC_BILLING_DATA is already Started while the other two are still Starting:
Once the runtime finishes, -status against wave1.csv confirms all three STARTED — this is the check before wave 1 smoke tests:
Only then does wave 2 go up the same way:
Sequencing between waves stays a runbook decision. The tool automates everything within a wave — one command instead of a dozen clicks, with the status check confirming it.
For this tool, and for development in general
The part I want to highlight is the environment. The classic failure mode of a utility like this: it works on the author's laptop, but the person executing the cutover task has JDK 17 instead of 21, or no Maven, or a proxy issue — and a five minute task becomes an hour of environment debugging inside the cutover window.
The repo ships with a .devcontainer definition: a small JSON file (plus optionally a Dockerfile) committed to the repo, describing the complete development environment — runtime versions, build tools, CLI utilities, VS Code extensions and settings. Open the repo and VS Code builds a container from it; your editor works inside that container.
With a traditional IDE setup (Eclipse, IntelliJ, or plain VS Code on the host), the environment lives on the machine and only the project lives in the repo. JDK version, Maven settings, proxy config and plugins are per-machine state set up by hand, usually from a stale wiki page. Two people on the same project routinely have different environments, and nobody can prove which is correct.
With a dev container, the environment lives in the repo, next to the code:
The environment is versioned. When the tool moves to a newer Java version, the dev container moves in the same commit. Checking out an old tag gives you the old environment too — no traditional IDE setup offers that.
Onboarding is one click. Clone, "Reopen in Container", done. No install guide, no "which JDK" thread. Extensions and settings come along, so even the editor is consistent.
The host stays clean. Projects with conflicting toolchains (Java 17 here, Java 21 there, different Node versions for CAP projects) coexist without nvm/sdkman juggling — each project brings its own container.
It runs in the browser. GitHub Codespaces runs the same definition on a cloud VM with zero local install — genuinely useful when the person on cutover duty has a locked down laptop where installing a JDK needs a service ticket.
The trade-offs are small: Docker Desktop must be installed and licensed on the host (Codespaces avoids even this), the first build takes a few minutes, and Windows file I/O is slower unless the repo lives in WSL2. For most SAP side-car development these costs are negligible against the consistency gained.
This isn't unique to this tool. For a great walkthrough of the same pattern applied to SAP CAP development - showing how one dev container definition runs identically across local VS Code, GitHub Codespaces, and Business Application Studio - see DJ Adams' Developing CAP in containers – three ways.
I now treat the dev container as part of the deliverable for anything I build outside the ABAP stack, and it fits BTP development well — CAP (Node.js and Java), the supporting tooling that accumulates around a CPI workstream, and UI5/Fiori all benefit from pinned runtime and tooling versions, removing the "works for me" drift between developers entirely.
ABAP itself stays in the ABAP stack — but nearly everything around it in a modern SAP landscape is Java or Node, and all of it can be containerised this way.
For SAP integration teams this matters more than in mainstream web development. The team mixes ABAP developers, middleware specialists and functional consultants — you cannot assume a curated local Java setup. The dev container turns "build and run this Java tool" into a task anyone can execute the same way.
Open the repo in Codespaces (or reopen locally in the dev container)
mvn clean package --> target/cpi-iflow-controller-1.1.0.jar
Copy the config and CSV templates; fill in tenant details and the signed off interface list
Run -status, capture, run -undeploy, run -status again, capture
Paste both tables into the cutover log
The code is on GitHub: https://github.com/viksingh/sap-cpi-iflow-controller
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
| User | Count |
|---|---|
| 15 | |
| 14 | |
| 11 | |
| 10 | |
| 10 | |
| 10 | |
| 9 | |
| 9 | |
| 8 | |
| 8 |