SOAP WS, WSDL, client with Maven (Spring Boot)
- Introduction
This article will build in an Spring Boot project with Maven, step by step, a full SOAP WS client, starting from scratch with just a WSDL file:
2. Reference
JAX-WS Maven Plugin [com.sun.xml.ws : jaxws-maven-plugin]
https://eclipse-ee4j.github.io/metro-jax-ws/jaxws-maven-plugin/
Customizations with the external binding file
http://itdoc.hitachi.co.jp/manuals/3020/30203Y2310e/EY230286.HTM
Jakarta XML Binding 4.0 (JAXB 4)
https://jakarta.ee/specifications/xml-binding/4.0/
JAXB2 Basics Plugins
https://github.com/highsource/jaxb2-basics#jaxb2-basics-plugins
3. File structure
In the example we'll generate two web service clients (out of two wsdl files) customized with a single external bindings file:
pom.xml
src/main/resources/
jaxws/wsdl_bindings.xjb
wsdls/
AuthService.wsdl
TercerService.wsdl
52. Using JAXB 4 with jakarta
Since Spring Boot 3 (jdk 17 minimum supported version), it only uses jakarta.
UNTESTED, TRY https://github.com/highsource/jaxb-tools
Eg: defense
jaxws/wsdl_bindings.xjb
<?xml version="1.0" encoding="UTF-8"?>
<!--
Jakarta XML Binding 3.0 https://jakarta.ee/specifications/xml-binding/3.0/
Customizations with the external binding file http://itdoc.hitachi.co.jp/manuals/3020/30203Y2310e/EY230286.HTM
jakarta - version="3.0"
javax - version="2.0"
jakarta - xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
javax - xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
jakarta - xmlns="https://jakarta.ee/xml/ns/jaxws"
javax - xmlns="http://java.sun.com/xml/ns/jaxws"
jakarta - xmlns:xjc="https://jakarta.ee/xml/ns/jaxb/xjc"
javax - xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
-->
<jaxb:bindings version="3.0" xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="https://jakarta.ee/xml/ns/jaxws"
xmlns:xjc="https://jakarta.ee/xml/ns/jaxb/xjc"
jaxb:extensionBindingPrefixes="xjc">
<jaxb:bindings>
<jaxb:globalBindings generateElementProperty="false" underscoreBinding="asCharInWord">
<!--(JAXB 3)<jaxb:serializable uid="27"/>-->
<!--(JAXB 2)<xjc:serializable uid="27"/>-->
<jaxb:serializable uid="27"/>
<!-- <javaType name="java.util.Calendar" xmlType="xsd:dateTime" parseMethod="jakarta.xml.bind.DatatypeConverter.parseDate" printMethod="jakarta.xml.bind.DatatypeConverter.printDate" /> -->
</jaxb:globalBindings>
</jaxb:bindings>
</jaxb:bindings>
pom.xml
<!-- wsimport -->
<plugin>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>4.0.0<!--same-jmp --></version>
<dependencies>
<dependency>
<groupId>javax.jws</groupId>
<artifactId>jsr181-api</artifactId>
<version>1.0-MR1</version>
</dependency>
<!--sb3 jakarta (wsimport) -->
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-tools</artifactId>
<version>4.0.0<!--same-jmp --></version>
</dependency>
<!--sb3 jakarta (wsimport) -->
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>4.0.0</version>
</dependency>
<!--sb3 jakarta (wsimport) -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
<!--sb3 jakarta (wsimport) -->
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>2.1.0</version>
</dependency>
<!--sb3 jakarta (wsimport) -->
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>4.0.0</version>
</dependency>
<!-- put the XJC plugins on the jaxws-maven-plugin's classpath
uses javax.xml.bind.JAXBException
<dependency>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>1.11.1</version>
</dependency>
-->
<!--sb3 [put the XJC plugins on the jaxws-maven-plugin's classpath] instead of previous one, just a test that didn't work
<dependency>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.15.1</version>
</dependency>
-->
<!--sb3 [put the XJC plugins on the jaxws-maven-plugin's classpath] instead of org.jvnet.jaxb2_commons:jaxb2-basics-->
<dependency>
<groupId>com.evolvedbinary.maven.jvnet</groupId>
<artifactId>jaxb30-maven-plugin</artifactId>
<version>0.15.0</version>
</dependency>
<!-- sb3 put XJC value constructor plugin classpath -->
<dependency>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-value-constructor</artifactId>
<version>3.0</version>
</dependency>
</dependencies>
<configuration>
<!-- tell JAXB to actually use the XJC plugins -->
<xjcArgs>
<xjcArg>-XautoNameResolution</xjcArg>
<!--(sb3)<xjcArg>-XtoString</xjcArg>-->
<xjcArg>-Xvalue-constructor</xjcArg>
</xjcArgs>
</configuration>
<executions>
<execution>
<?m2e execute onConfiguration?>
<id>wsimport-wsdl</id>
<goals>
<goal>wsimport</goal>
</goals>
<configuration>
<vmArgs>
<!-- Needed with JAXP 1.5 -->
<vmArg>-Djavax.xml.accessExternalSchema=all</vmArg>
</vmArgs>
<bindingDirectory>${project.basedir}/src/main/resources/jaxws</bindingDirectory>
<bindingFiles>
<bindingFile>wsdl_bindings.xjb</bindingFile>
</bindingFiles>
<keep>true</keep>
<sourceDestDir>${project.build.directory}/generated-sources/wsimport</sourceDestDir>
<xnoAddressingDataBinding>false</xnoAddressingDataBinding>
<xuseBaseResourceAndURLToLoadWSDL>true</xuseBaseResourceAndURLToLoadWSDL>
<wsdlDirectory>${project.basedir}/src/main/resources/wsdls</wsdlDirectory>
<wsdlFiles>
<!-- produces wsdlLocation = {wsdlLocation}{wsdlFile} -->
<wsdlFile>AuthService.wsdl</wsdlFile>
<wsdlFile>ThirdService.wsdl</wsdlFile>
</wsdlFiles>
</configuration>
</execution>
</executions>
</plugin>
53. Using JAXB 3 with jakarta
Since Spring Boot 3 (jdk 17 minimum supported version), it only uses jakarta.
Eg: defense
jaxws/wsdl_bindings.xjb
<?xml version="1.0" encoding="UTF-8"?>
<!--
Jakarta XML Binding 3.0 https://jakarta.ee/specifications/xml-binding/3.0/
Customizations with the external binding file http://itdoc.hitachi.co.jp/manuals/3020/30203Y2310e/EY230286.HTM
jakarta - version="3.0"
javax - version="2.0"
jakarta - xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
javax - xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
jakarta - xmlns="https://jakarta.ee/xml/ns/jaxws"
javax - xmlns="http://java.sun.com/xml/ns/jaxws"
jakarta - xmlns:xjc="https://jakarta.ee/xml/ns/jaxb/xjc"
javax - xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
-->
<jaxb:bindings version="3.0" xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="https://jakarta.ee/xml/ns/jaxws"
xmlns:xjc="https://jakarta.ee/xml/ns/jaxb/xjc"
jaxb:extensionBindingPrefixes="xjc">
<jaxb:bindings>
<jaxb:globalBindings generateElementProperty="false" underscoreBinding="asCharInWord">
<!--(JAXB 3)<jaxb:serializable uid="27"/>-->
<!--(JAXB 2)<xjc:serializable uid="27"/>-->
<jaxb:serializable uid="27"/>
<!-- <javaType name="java.util.Calendar" xmlType="xsd:dateTime" parseMethod="jakarta.xml.bind.DatatypeConverter.parseDate" printMethod="jakarta.xml.bind.DatatypeConverter.printDate" /> -->
</jaxb:globalBindings>
</jaxb:bindings>
</jaxb:bindings>
pom.xml
<!-- wsimport -->
<plugin>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>4.0.0<!--same-jmp --></version>
<dependencies>
<dependency>
<groupId>javax.jws</groupId>
<artifactId>jsr181-api</artifactId>
<version>1.0-MR1</version>
</dependency>
<!--sb3 jakarta (wsimport) -->
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-tools</artifactId>
<version>4.0.0<!--same-jmp --></version>
</dependency>
<!--sb3 jakarta (wsimport) -->
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>4.0.0</version>
</dependency>
<!--sb3 jakarta (wsimport) -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
<!--sb3 jakarta (wsimport) -->
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>2.1.0</version>
</dependency>
<!--sb3 jakarta (wsimport) -->
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>4.0.0</version>
</dependency>
<!-- put the XJC plugins on the jaxws-maven-plugin's classpath
uses javax.xml.bind.JAXBException
<dependency>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>1.11.1</version>
</dependency>
-->
<!--sb3 [put the XJC plugins on the jaxws-maven-plugin's classpath] instead of previous one, just a test that didn't work
<dependency>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.15.1</version>
</dependency>
-->
<!--sb3 [put the XJC plugins on the jaxws-maven-plugin's classpath] instead of org.jvnet.jaxb2_commons:jaxb2-basics-->
<dependency>
<groupId>com.evolvedbinary.maven.jvnet</groupId>
<artifactId>jaxb30-maven-plugin</artifactId>
<version>0.15.0</version>
</dependency>
<!-- sb3 put XJC value constructor plugin classpath -->
<dependency>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-value-constructor</artifactId>
<version>3.0</version>
</dependency>
</dependencies>
<configuration>
<!-- tell JAXB to actually use the XJC plugins -->
<xjcArgs>
<xjcArg>-XautoNameResolution</xjcArg>
<!--(sb3)<xjcArg>-XtoString</xjcArg>-->
<xjcArg>-Xvalue-constructor</xjcArg>
</xjcArgs>
</configuration>
<executions>
<execution>
<?m2e execute onConfiguration?>
<id>wsimport-wsdl</id>
<goals>
<goal>wsimport</goal>
</goals>
<configuration>
<vmArgs>
<!-- Needed with JAXP 1.5 -->
<vmArg>-Djavax.xml.accessExternalSchema=all</vmArg>
</vmArgs>
<bindingDirectory>${project.basedir}/src/main/resources/jaxws</bindingDirectory>
<bindingFiles>
<bindingFile>wsdl_bindings.xjb</bindingFile>
</bindingFiles>
<keep>true</keep>
<sourceDestDir>${project.build.directory}/generated-sources/wsimport</sourceDestDir>
<xnoAddressingDataBinding>false</xnoAddressingDataBinding>
<xuseBaseResourceAndURLToLoadWSDL>true</xuseBaseResourceAndURLToLoadWSDL>
<wsdlDirectory>${project.basedir}/src/main/resources/wsdls</wsdlDirectory>
<wsdlFiles>
<!-- produces wsdlLocation = {wsdlLocation}{wsdlFile} -->
<wsdlFile>AuthService.wsdl</wsdlFile>
<wsdlFile>ThirdService.wsdl</wsdlFile>
</wsdlFiles>
</configuration>
</execution>
</executions>
</plugin>
54. Using JAXB 2 with javax
Note: This example was tested with jdk8. For jdk11 or newer you might need additional dependencies, see:
https://stackoverflow.com/a/54574003/1323562
54.1. pom.xml
Let's add a plugin to the pom.xml for generating the client Java code:
<project>
<dependencies>
<!-- to compile xjc-generated sources (needed by wsimport) -->
<dependency>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics-runtime</artifactId>
<version>1.11.1</version>
</dependency>
<dependencies>
<build>
<plugins>
<plugin>
<!-- Plugin from org.codehaus.mojo not updated since 2017 and it fails with jdk11.
Alternatives seem to be com.helger.maven or com.sun.xml.ws
-->
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>2.3.3-b01<!--same-jmp --></version>
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-tools</artifactId>
<version>2.3.3-b01<!--same-jmp --></version>
</dependency>
<dependency>
<groupId>javax.jws</groupId>
<artifactId>jsr181-api</artifactId>
<version>1.0-MR1</version>
</dependency>
<dependency>
<!-- put the XJC plugins on the jaxws-maven-plugin's classpath -->
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>1.11.1</version>
</dependency>
<dependency>
<!-- put XJC value constructor plugin classpath -->
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-value-constructor</artifactId>
<version>3.0</version>
</dependency>
</dependencies>
<configuration>
<!-- tell JAXB to actually use the XJC plugins -->
<xjcArgs>
<xjcArg>-XautoNameResolution</xjcArg>
<xjcArg>-XtoString</xjcArg>
<xjcArg>-Xvalue-constructor</xjcArg>
</xjcArgs>
</configuration>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
<configuration>
<vmArgs>
<!-- Needed with JAXP 1.5 -->
<vmArg>-Djavax.xml.accessExternalSchema=all</vmArg>
</vmArgs>
<xjcArgs>
<!-- Enable "-B-XautoNameResolution" ONLY if needed: <xjcArg>-XautoNameResolution</xjcArg> -->
<xjcArg>-XautoNameResolution</xjcArg>
</xjcArgs>
<bindingDirectory>${project.basedir}/src/main/resources/jaxws</bindingDirectory>
<bindingFiles>
<bindingFile>wsdl_bindings.xjb</bindingFile>
</bindingFiles>
<keep>true</keep>
<sourceDestDir>${project.build.directory}/generated-sources/wsimport</sourceDestDir>
<xnoAddressingDataBinding>false</xnoAddressingDataBinding>
<wsdlDirectory>${project.basedir}/src/main/resources/wsdls</wsdlDirectory>
<wsdlFiles>
<wsdlFile>AuthService.wsdl</wsdlFile>
<wsdlFile>TercerService.wsdl</wsdlFile>
</wsdlFiles>
</configuration>
</execution>
</executions>
</plugin>
54.2. wsdl_bindings.xjb
The goals of this customization file are:
- Generate generate Java type instead of JAXBElement (generateElementProperty).
- Generate Camel Case instead of Snake Case, when appropriated (underscoreBinding).
- Generated classes implement 'Serializable' (serializable).
<?xml version="1.0" encoding="UTF-8"?>
<!-- http://itdoc.hitachi.co.jp/manuals/3020/30203Y2310e/EY230286.HTM
jakarta - version="3.0"
javax - version="2.0"
jakarta - xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
javax - xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
-->
<jaxb:bindings version="3.0" xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://java.sun.com/xml/ns/jaxws"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
jaxb:extensionBindingPrefixes="xjc">
<jaxb:bindings>
<jaxb:globalBindings generateElementProperty="false" underscoreBinding="asCharInWord">
<xjc:serializable uid="27"/>
<!-- <javaType name="java.util.Calendar" xmlType="xsd:dateTime" parseMethod="javax.xml.bind.DatatypeConverter.parseDate" printMethod="javax.xml.bind.DatatypeConverter.printDate" /> -->
</jaxb:globalBindings>
</jaxb:bindings>
</jaxb:bindings>
55. Sample client (javax)
Warning:
For OpenJDK > com.sun.xml.internal.ws.developer.JAXWSProperties;
for OracleJDK > com.sun.xml.ws.developer.JAXWSProperties].
Reference: https://www.codenotfound.com/spring-ws-client-timeout-example.html
/**
* TERCER port.
*
* @return -
* @throws IOException
*/
@Bean
public TercerServicePortType tercerPort() throws IOException {
log.info("Initializing bean tercerPort");
// Service (from classpath WSDL file)
final URL wsdlUrl = (new ClassPathResource("wsdls/TercerService.wsdl")).getURL();
final edu.uoc.serveis.tercers.tercer.service.TercerService service = new edu.uoc.serveis.tercers.tercer.service.TercerService(
wsdlUrl);
// URL endpoint
final String urlWsService = this.environmentHelper.getEndpointUrlBase() + this.tercerUrlEndpoint;
// Soap port initialization
final TercerServicePortType port = service.getTercerServiceHttpPort();
this.initializeBindingProvider((BindingProvider) port, urlWsService);
// authentication basic
initializeBasicAuthentication((BindingProvider) port);
//
return port;
} /**********************************************************************/
/**
* Auxiliary method for initializing the SOAP port binding provider. Setea
* propiedades configurables para la conexion al WS.
*
* @param bindingProvider -
* @param endPointUrl -
*/
private void initializeBindingProvider(@NonNull final BindingProvider bindingProvider,
@NonNull final String endPointUrl) {
// Context that is used to initialize the message context for request messages
final Map<String, Object> requestContext = bindingProvider.getRequestContext();
// url endpoint
requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endPointUrl);
// [com.sun.xml.ws.client.BindingProviderProperties.CONNECT_TIMEOUT]
requestContext.put(DuplicatsHelper.JAXWS_CONNECT_TIMEOUT, ConstantsWs.WS_CONNECT_TIMEOUT);
// [com.sun.xml.ws.client.BindingProviderProperties.REQUEST_TIMEOUT]
requestContext.put(DuplicatsHelper.JAXWS_REQUEST_TIMEOUT, ConstantsWs.WS_REQUEST_TIMEOUT);
//
log.info("Initialized binding provider [" + bindingProvider.getClass().getName() + "]. Connect timeout (ms)="
+ ConstantsWs.WS_CONNECT_TIMEOUT + "; Request timeout (ms)=" + ConstantsWs.WS_REQUEST_TIMEOUT
+ "; Enpoint=" + endPointUrl);
} /**********************************************************************/
/**
* Basic authentication
*
* @param port
*/
private void initializeBasicAuthentication(BindingProvider port) {
port.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, EnvironmentHelper.getBasicAuthenticationUser());
port.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY,
EnvironmentHelper.getBasicAuthenticationPawwsord());
} /**********************************************************************/