← Blog

I didn’t think you could convert a compiled Java mapping

How a decompiled SAP PI Java mapping became a Groovy script in twenty minutes, and how to prove the result matches the original byte for byte.

September 4, 2026 · 9 min read

A decade-old JAR. No source code. Thirty-six classes of shipment mapping logic. Twenty minutes later it was a Groovy script producing identical output. I did not expect it would be that easy.

For years, my assumption was that migrating a Java mapping from SAP PI/PO to Cloud Integration meant rewriting it by hand. You read the Java, understand the business logic, and then recreate it in Groovy. Slow, expensive, and the kind of task nobody volunteers for.

Then I heard DHH talk on the Lex Fridman podcast about converting Python to Rust in 40 minutes.

Could it really be that easy?

SAP has also added a Java Mapping to Groovy converter to Integration Suite, so there clearly had to be something to it.

The idea kept nagging at me, so I decided to try it.

A small one first

I took a simple Java library from one of our own PI systems, handed it to Claude Code, and asked for a Groovy version.

It produced a working script in a few minutes. This told me that the basic idea wasn’t nonsense, but a small utility case proves very little.

So I went looking for something genuinely nasty.

What was actually in the archive

I started a new Claude Code session with a much larger file to see how far it could go.

It was a shipment mapping: IDoc in, fixed-record EDI out. The archive contained 36 compiled class files and no .java source at all. No documentation either.

I didn’t know exactly what the mapping did, only that it was used to process a B2B shipment document.

We had previously been able to run the mapping JAR using our Groovy wrapper. But rather than keep wrapping the old Java, why not see if we could turn it into a native Groovy solution?

A year ago, I would have expected this conversion to take one or two weeks to reconstruct and verify.

This was clearly not a simple mapping.

The first useful discovery came within a couple of minutes of decompiling.

The code reads around 20 IDoc segment types and builds a complete packing hierarchy. But almost none of the roughly 6,000 lines is actually SAP PI-specific.

The entire SAP PI surface comes down to three types:

// everything that actually ties this mapping to PI/PO
com.sap.aii.mapping.api.StreamTransformation          → Message processData(Message)
com.sap.aii.mapping.api.AbstractTrace                 → an MPL attachment
com.sap.aii.mapping.api.StreamTransformationException → your own exception class

Everything else is org.w3c.dom and java.util.

And Groovy is close enough to Java that most of the plain DOM-walking code is already valid Groovy.

So this was not really a translation job. It was mostly a platform API swap around a large body of code that could remain almost unchanged.

The conversion is the easy half. The part that decides whether you can actually ship it is proving that the new script does exactly the same thing as the old one.

How do we make sure?

Here is what makes this tractable, and it is almost embarrassingly obvious once you see it: You still have the original. The compiled mapping is right there. It is an executable specification of what the output should be. You do not have to argue about whether the new version looks correct. You can run both versions and compare the result.

  • Decompile the archive: CFR, a free open-source Java decompiler, turned the 36 classes back into readable Java. It was not pretty everywhere. Some of the control flow came back as while(true) loops with labelled jumps. But it was complete and correct.

  • Stub the PI API and run the original: With three small stub classes for StreamTransformation, AbstractTrace and the exception, the 2014 mapping could run on a plain JDK. Feed it a sample payload and you have your reference output.

  • Port it, then compare byte for byte: Run the Groovy script on Groovy and compare the output with the original. Not “looks right”. Identical, or it is not done.

  • Widen the coverage by flipping the switches: One sample payload exercises one path. This mapping’s behaviour is driven by configuration flags carried in the payload itself, so we generated a variant per flag plus a few combinations: 13 payloads, each producing a different output. One of them took a genuinely different route through the mapping: 12,807 bytes out instead of 16,031.

  • Check the API surface, not just the output: Functional tests only cover the paths you actually exercise. So we also used reflection to list every public method on the original 36 classes and assert they all still exist on the Groovy side. That’s the check that catches an accessor quietly dropped on a branch no test reaches.

All of this became the specification for the Claude Code task. I did not sit and guide the conversion line by line. The tests defined what “done” meant.

Two versions, one comparison

During the migration, the original mapping is not just the thing you are replacing.

It is your test oracle.

Run the same payload through the original Java mapping and the new Groovy version, then compare the outputs.

Diagram: the original JAR runs on a plain JDK with stubbed PI APIs, the Groovy port runs on Groovy 2.4.21, and 13 outputs from each match byte for byte
13 payloads derived from 1 real production sample, covering 8 configuration flags and a few combinations

“This looks crappy, make it pretty”

The first working version was faithful, but ugly. It still looked like decompiled Java because that is exactly what it was: decompiler output with the platform-specific calls replaced. Long index loops. Hundreds of hand-written getters. Variables called block0.

Normally, I would be very cautious about cleaning up working integration code just to make it look pretty. But once you have a byte-exact oracle and 13 payloads, refactoring stops being a gamble: change something, re-run, and you know within seconds whether you broke it.

Across the codebase, the cleanup included:

  • 488 hand-written accessors replaced with Groovy properties
  • 95 index loops replaced with for-each loops
  • decompiler-generated control flow rewritten as normal nested loops
  • meaningless variable names replaced with names that actually describe what they do

The result: 6,219 lines reduced to 3,939.

And all 13 payloads were still byte-identical. All 609 public methods were still present.

The safety net proved its value immediately. It caught three mistakes during the cleanup that I could easily have shipped, including one rename that quietly turned a local variable into a field.

Where this leaves the migration problem

Compiled Java is not the blocker I assumed: Decompilation is a solved problem, and the output is good enough to port from. “We lost the source” stops being the end of the conversation.

The old mapping is your test oracle: This is the key part. Do not rely on reviewing the converted code and deciding that it looks right. Run the old version and the new version with the same input and compare the output. Correctness becomes something you measure rather than something you argue about.

Generate payloads from the config switches: One production sample covers one path. Flags in the payload are free test cases, and they find the branches a single message never reaches.

Readable code is now cheap: With an oracle in place, cleanup is mechanical and verifiable. You don’t have to choose between “it works” and “someone can maintain it”.

The honest caveat: All 13 payloads were derived from a single production sample. That is much better than testing with one message, but it is still not the same as having a real regression set from production history. Packing structures that were not present in that original sample remain untested. Before this goes anywhere near production, I would want real payloads from the interface’s own history. The method makes that easier. It does not make it optional.

Where the conversion stops

This mapping was a pure payload transformation: XML in, XML out, with its configuration read from the message itself. That is the straightforward case, and a great many Java mappings are exactly that.

Plenty are not. If a mapping reaches into the PI runtime rather than just working on the payload, for example CPA cache lookups, PI lookup APIs that call an RFC or web service during the mapping, dynamic configuration, or access to PI configuration objects, none of those converts for free. Those things have Cloud Integration equivalents, but they are not one-for-one, and each would need a few more iterations to work out.

Going on how the rest of this behaved, I don’t expect that to be the hard part either. It is more of the same work: find the platform surface, swap it, verify against the original. What genuinely changes is the verification: once a mapping calls out to another system mid-flight, you can no longer prove it with two JVMs and a diff. You need the backend in the loop.

SAP ships migration tooling for this too

SAP has also added Java mapping migration to the Integration Suite tooling, so this isn’t a gap only a coding assistant can fill. I’ve only seen a short demo, so I can’t tell you how deep it goes or how many scenarios it checks before handing you a result.

If you have the AI capability in your Integration Suite, I would start there. It’s already paid for. If not, a coding assistant is a reasonable way around it, which is what this post is.

Either way, the important question is not whether the tool can produce Groovy. The important question is how you prove that the Groovy still does the same thing as the Java mapping.

Why we are not building this into the Figaf tool

We won’t be.

The market for a dedicated Java-mapping converter is too small to justify building one. And more importantly, I do not think we could build something better than this anyway. A general coding assistant on a normal subscription already does the job well enough that a dedicated Figaf feature would likely be both more expensive and less flexible. I would rather say that plainly than pretend there is a product opportunity here.

Whatever converts it, test it like a migration

The recommendation is the same whichever route you take: If you convert your mappings, test the migration.

The conversion took around twenty minutes. Proving that the interface still works is the job that remains, and that job is the same whether the Groovy script was written by hand, produced by SAP’s tooling, or generated by a coding assistant.

This is where Figaf has a role, and there are really two separate jobs.

End-to-end testing: Pull real messages from the SAP PI system, run them through the new iFlow, let them interact with the backend systems, and compare the result. That catches both the variations your original sample never contained and the problems that only show up when the integration meets a real system.

Review the generated code. You are still taking a few thousand lines of Groovy into your landscape. Code that may not have been read carefully by anyone yet. It deserves the same review as any other code you would deploy into production.

“The conversion stopped being the expensive part.”

Twenty minutes

The conversion and verification of the large mapping took around fifteen minutes. The cleanup took another five. Then it went onto an Integration Suite tenant and ran against our input and output payloads.

I went into this expecting to prove that AI-assisted conversion would fall apart on real integration code. It did not. What I got wrong was not whether a model could understand decompiled Java. I got the question wrong.

“Can this be converted?” is no longer the interesting part. The interesting part is whether you can prove that the converted version still does exactly what the old one did. If you have a shelf of Java mappings waiting for a PI/PO migration, this is worth twenty minutes of your afternoon. Just budget considerably more than twenty minutes for the testing.

This post covers these products

Capabilities covered

More from the blog

All posts →

See this working in your landscape

No credit card required.

Integration cost calculator →

Start your free trial

No credit card required

Try Figaf DevOps Suite free for 30 days. It takes less than an hour to get started, and we send everything you need by email.

Join our newsletter

Get the latest about Figaf, SAP and anything relevant to SAP integration, every week in Figaf Weekly.