If you receive null
for every attribute to your SOAP requests in Spring, after upgrading JDK, this is what you need to do
If you have a SOAP service written in Java using Spring, you have written methods that are called with Java objects representing the body of the SOAP request. If, after upgrading from Java 8 to Java 11, you see that every attribute of those Java objects is null, adding the following to your Maven file is the solution:
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>maven-replacer-plugin</artifactId>
<version>1.3.5</version>
<executions>
<execution>
<id>make-namespaces-visible-to-soap</id>
<phase>process-sources</phase>
<goals><goal>replace</goal></goals>
<configuration>
<includes>
<include>target/generated-sources/jaxb/com/myproject/package-info.java</include>
</includes>
<replacements>
<replacement>
<token>@javax.xml.bind.annotation.XmlSchema\(</token>
<value>@javax.xml.bind.annotation.XmlSchema(elementFormDefault =
javax.xml.bind.annotation.XmlNsForm.QUALIFIED, </value>
</replacement>
</replacements>
</configuration>
</execution>
</executions>
</plugin>
Explanation
Since JDK9, Project Jigsaw is part of the JDK. That means, that certain functionality which was previously provided in the JDK is no longer available by default. One such functionality is the XJC tool and JAXB library used by Spring to provide SOAP services:
- XJC generates Java classes from XML schemas (XSD) during the build process.
- JAXB allows these Java classes to be populated from XML at runtime e.g. by incoming SOAP requests.
These were previously included in the JDK, and are now available on Maven to be added to your pom.xml
file.
I don't know what versions of these two modules were included in JDK8 (I suppose they were just "JDK8 version") but the versions of JAXB runtime on Maven Central (needed at runtime) are 2.2, 2.3 and 2.4. (I tried all of them.)
SOAP requests use namespaces to differentiate the SOAP envelope (containing the request), the security information, and your body. This is a good thing, and anyway even if it wasn't, Spring complains otherwise:
Caused by: java.lang.IllegalArgumentException:
class path resource [sap.xsd] has no targetNamespace
After a full day of debugging and reading source code, the conclusion I came to is that this is what's going on:
- When the SOAP request is sent, each element contains the appropriate namespace. This is interpreted correctly by Spring.
- JAXB, at runtime, attempts to take values from the XML supplied in the SOAP request, and place them into objects (of classes generated from the XSD by XJC).
- JAXB does not have access to the XSD definition at runtime; all it has access to is the generated classes. It attempts to read the XML namespace out of these generated classes.
- The XML namespace is written by XJC into a generated
package-info.java
file next to the Java classes representing each XSD element. This file has an@XmlSchema
annotation, which correctly contains the XML namespace. - However, JAXB at runtime fails to read this namespace from the
@XmlSchema
in thepackage-info.java
, meaning it thinks that the classes to be populated from the request represent XML without a namespace. - Seeing that none of the elements in the SOAP request (with namespace) match the elements in the Java objects to be populated (without namespace), it does not populate them.
- Therefore, your Java service method is called with an object of the right class, but whose every attribute is
null
.
The code to extract, from the generated Java class, the XML attribute that each Java attribute represents, is here. You can see it checks for the package annotation @XmlSchema
.
The @XmlSchema
annotation has an attribute elementFormDefault
. This is default UNSET
.
If it's UNSET
, the JAXB code ignores the namespace extracted from the @XmlSchema
. That's the problem.
I don't understand why you would want to ignore namespaces. Perhaps to workaround some other bug?
There is no way I can find to alter the XJC generation process to add that attribute to the @XmlSchema
annotation on the generated package-info.java
, so I alter the files with that "sed"-like file manipulation.
Other info
- How to write a SOAP service in Spring. Great tutorial, but only covers JDK8, that stuff doesn't work with JDK11. I'm hoping they update it soon, JDK8 is already past end-of-life.
- If you get the error
Prefix '' is already bound to ''
, see here. Downgrade to jaxb2-maven-plugin version 1.x, or for 2.x you need to add an extra uselessdummy.xsd
to your XSD directory. - If you get errors about the paths of your XSD files being wrong e.g. your files are in
/opt/foo/x.xsd
and it complains about/home/asmith/opt/foo/x.xsd
not being found, downgrade to jaxb2-maven-plugin version 1.x. If you use 2.x then it finds files relative to your current working directory and not pom.xml.