POSTED ON 31 JUL 2025
READING TIME: 5 MINUTES
Some technologies age like wine. Others, like SOAP, age like milk.
In 2025, REST rules the web, gRPC powers internal services, and GraphQL is fashionable again. And yet - SOAP still lurks in telco stacks, in critical systems that power provisioning, billing, and network orchestration.
When we were asked to consume a SOAP API from a legacy WSDL file, it sounded simple. Generate some Java classes, send a few requests, parse a response. We've done it before.
But as it turned out, this particular WSDL had other ideas.
This is the story of how we got it working - through trial, tools, errors, a few dead ends, and ultimately, a solution that restored sanity (and SOAP support) to our system.
We were given a WSDL - short for Web Services Description Language. It’s the blueprint of a SOAP API: a structured, XML-based contract that tells you what messages are expected, how they’re shaped, and where to send them.
Our job:
Generate the client classes. Marshal requests into XML. Unmarshal the responses. Wrap it all in a Feign client so it fits into a modern Java microservice architecture.Consume the API in Java
SOAP isn’t our first choice, but it’s still deeply entrenched in telco ecosystems - and often the only way to talk to older systems.
So we started building.
We kicked things off using Apache Axis2 and its wsdl2java tool. It’s a common choice for generating Java classes from WSDL, and with a few Gradle tweaks, we had it running:
groovy
CopyEdit
tasks.register('generateFromWsdl', JavaExec) {
mainClass.set('org.apache.axis2.wsdl.WSDL2Code')
classpath = configurations.axis2Tools
args = [
'-uri', 'file.wsdl',
'-d', 'xmlbeans',
'-o', "${projectDir}/generated",
...
]
}
The classes were generated. So far, so good.
Then we plugged it into our Feign SOAP client.
And boom - this:
text
CopyEdit
IllegalAnnotationsException: 150 counts of IllegalAnnotationExceptions
GeneratedSoapClass is an interface, and JAXB can't handle interfaces.
Axis2 had generated interfaces in the model layer. JAXB, which we were relying on for marshaling and unmarshaling, doesn't like interfaces. Not even a little bit.
We tried multiple binding options - ADB, JAXB RI - but no success. After wrestling with it for far too long, we decided to change course.
Our team lead suggested switching to Apache CXF, a more modern framework with better tooling support.
We swapped the codegen task to use wsdl2java from CXF:
groovy
CopyEdit
tasks.register('generateFromWsdl', Exec) {
workingDir "$buildDir/apache-cxf/bin"
commandLine './wsdl2java', '-client', ...
}
This time the classes looked cleaner. Structurally more sound. But once again, when we tried to marshal and unmarshal a request:
text
CopyEdit
IllegalAnnotationsException:
Two classes have the same XML type name "generatedType".
Use @XmlType.name and @XmlType.namespace to assign different names.
Even after applying the -xjc-npa flag (which forces namespace annotations into individual classes rather than relying on package-info.java), the problem persisted.
So we stepped back and started looking into how the marshaller itself was being initialized.
The default JAXB marshaller in our Feign client was choking on the generated classes. Not because they were wrong - but because the context being built for marshalling wasn’t seeing them correctly.
The issue wasn’t just about namespaces or duplicates. It was about how the context was created and which classloader it used.
We scrapped the default marshaller logic and created a custom one, using Jakarta JAXB backed by Glassfish’s runtime. This allowed us to explicitly control the context and ensure everything was correctly scoped.
Here’s the encoder fix:
java
CopyEdit
jakarta.xml.bind.Marshaller marshaller =
JAXBContext.newInstance(bodyType.getPackageName(), bodyType.getClassLoader())
.createMarshaller();
And the corresponding decoder:
java
CopyEdit
jakarta.xml.bind.Unmarshaller unmarshaller =
JAXBContext.newInstance(((Class<?>) type).getPackageName(), ((Class<?>) type).getClassLoader())
.createUnmarshaller();
With this in place, everything snapped into place.
SOAP messages were being generated. Responses were unmarshaling. No more type errors. No more annotation collisions.
Finally, a working SOAP integration in 2025.
This wasn’t just an exercise in nostalgia. Legacy SOAP APIs still sit at the heart of critical telco platforms - from subscriber management to OSS systems and billing gateways.
When you’re modernising a stack or integrating new capabilities, you’ll often run into these interfaces. They're not pretty. They’re not RESTful. But they’re essential.
What matters is how you deal with them.
At Sonalake, we don’t treat legacy like a liability. We treat it like a puzzle. One that, with the right tools and a bit of persistence, can be solved without compromising quality.
We use this kind of experience to power real integrations - bridging modern cloud-native services with systems that were designed long before Kubernetes was a thing.
SOAP in 2025 isn’t dead. It’s just hiding in plain sight.
It takes patience, experimentation, and collaboration to get these kinds of integrations working right - but when they do, they form the backbone of critical telco operations.
We know how to navigate these waters - because we already have.
If your next project involves not only the newest technologies but also more mature legacy interfaces, outdated protocols, or ancient WSDLs… we should probably talk.