top of page
Search

Consuming SOAP Web Services With Java 21 & AEM Cloud Service

  • Writer: Juan Ayala
    Juan Ayala
  • 2 days ago
  • 5 min read

Updated: 7 hours ago

Simple Object Access Protocol, aka SOAP. It is a lightweight protocol for exchanging data over the internet. The message format is XML. And the transport protocol of choice is HTTP. Back when I was a young man, at the turn of the millennium, it was all the rage. Twenty five years later I can't help but wonder. What the 🤬 were we thinking!


Which brings us to today's topic. What should you do if you have to integrate your AEM application with a legacy SOAP web service. These are your three choices.


Upgrade The Web Service

Your first option is to try and convince the web service owner to upgrade. Its 2025, REST and JSON dominate the web. So try to build a compelling case why they should update. Head over to ChatGPT and ask "Why should I update my SOAP service to REST & JSON?". If all else fails, beg. There is no shame in it 😊.


The SOAP with Attachments API for Java

The second option would have to be the SOAP with Attachments API for Java (SAAJ). It is a low-level Java API that developers can use create, send, and receive SOAP messages. It is part of the JavaEE stack. And it is available in AEM, with no extra effort.


SOAP services usually publish a Web Services Description Language (WSDL) file. The WSDL describes what the web service does, where it’s located, and how to call it. It defines the service’s operations, messages, data types, and communication protocols. It is the a contract between the service and its clients. Lets take the following WSDL for example.



I'm not going to get into the details about how to read a WSDL file. Suffice it to say, if you want to send a message to this service, it would be a SOAP 1.2 message. POSTed to http://localhost:8080/service/products. Here is the curl request.



And the response would come back as



And now that you know what the SOAP message should to look like, you can use SAAJ to create, send and receive messages.


First, create a SOAP 1.2 envelope. Then set the target namespace on the envelope. Create its body, and send it. When you receive the response, you dive in and get the relevant data.



Use A Framework Like Apache CXF

The example above is very simple. But as you could imagine, things can get really complicated. Especially with more complex request and response data structures.


Which brings us to our third choice. Using a framework. And the best one I found out there is Apache CXF. It is an open-source services framework that helps you build and consume web services. It supports both SOAP (JAX-WS) and REST (JAX-RS) APIs. Making it flexible for different integration needs. For our purposes, we are only interested in consuming SOAP services.


Generate the SOAP Client from WSDL

The first step is to generate the client from the WSDL. As I remember back then, this was the coolest thing about SOAP. That you could automagically generate a client, saving yourself several hours of work. To do this, I will use the cxf-codegen-plugin.



The two things I would like to point out are the wsdl and wsdlLocation options. The first tells the plugin where to get the WSDL file from. I chose to put it in the local bundle resources. The second wsdlLocation tells the client where to find the WSDL file at runtime.

⚠️ If you attempt to build the bundle using an AEM archetype project, you might see the following error: Exception in thread "main" java.lang.AbstractMethodError: Receiver class org.apache.xerces.jaxp.DocumentBuilderFactoryImpl does not define or inherit an implementation of the resolved method 'abstract void setFeature(java.lang.String, boolean)' of abstract class javax.xml.parsers.DocumentBuilderFactory. This is because the archetype project template sticks a dependency to junit-addons. Which in turn has a dependency on an outdated version of Apache Xerces. I don't know why anyone would be using junit-addons. My suggestion would be to remove it.

Add the Apache CXF Maven Dependencies

Next, add the Apache CXF dependencies.



As of this writing, the latest 3.6.x version is 3.6.8. There is also a 4.x version. The difference is that 3.x is Java EE. And 4.x is Jakarta EE. To make a long story short, Java EE was the name of the enterprise Java platform under Oracle. While Jakarta EE is its successor under the Eclipse Foundation. With the same purpose but a new name due to trademark restrictions. At the end of the day, AEM already has all the Java EE APIs I need. While I would need to import the Jakarta ones. Plus, I could not get 4.x to work 🧐. So 3.6.x it is 😎.


Embed Apache CXF In Your Bundle

This is the whole genesis for this post. Getting the Apache CXF dependencies into the Felix OSGi container. If you Googled around, you may have stumbled onto this solution from 2021. While this works in AEM 6.5, I had some concerns. First it instructs you go generate the client with a CLI utility. Second, it provides a link to a content package for you to download and install. A few months later there was a better solution. It generates the client from the WSDL using a Maven plugin. And it embeds the CXF bundles in the all content package.


These solutions will not work in AEMaaCS. To be specific, they call for adding the stax2-api and woodstox-core bundles. While they do not exist in AEM 6.5, they do in AEMaaCS. The difference in versions may be small. And there may be no harmful side-effects if you update them. But it is a better idea to leave them alone. And embed all the required dependencies within your own bundle.


Using the bnd-maven-plugin, we can embed them as follows



If you attempt to use the client now, the CXF dependencies will resolve. Except for javax.xml.ws.spi.Provider.


Provider org.apache.cxf.jaxws.spi.ProviderImpl not found

The stack trace clearly states that the javax.xml.ws.spi.FactoryFinder can not find org.apache.cxf.jaxws.spi.ProviderImpl. Looking at the comments within the FactoryFinder, there it says:



cxf-rt-frontend-jaxws is registering its self as a JAX-WS provider via META-INF/services/javax.xml.ws.spi.Provider. But because we are embedding that bundle, that service registration gets lost. So to help the FactoryFinder locate this service, we must register our bundle as a JAX-WS provider. By placing the same file with the same contents into our bundle resources.


Once you have created that file with the class name of the CXF ProviderImpl, you are good to go. The following code demonstrates how to use the generated CXF client.


⚠️ In the cxf-codegen-plugin configuration I set the wsdlLocation to classpath:/products.wsdl. If I had not done this, I would have had to resolve and pass it to the client's constructor. That is why I commented it out. At runtime, the WSDL provides the endpoint address. Instead of swapping the WSDL based on the environment, I am setting the endpoint dynamically.

Conclusion

So there you have it. SOAP may feel like dial-up internet in a 5G world, but it’s still hanging around in dark corners of the enterprise. If you can convince the service owner to modernize, do it. If not, you’ve got SAAJ for the “I just need this one thing” scenarios, and Apache CXF when things get real messy.


At the end of the day, the goal isn’t to fall in love with SOAP again (please don’t). It’s to get your AEM project talking to that legacy service without losing your sanity. And with the approaches above, you’ve got a fighting chance.

Comments


bottom of page