Even though nowadays REST with JSON is the most common API style, sometimes we need to integrate with an existing SOAP web service. If you already use the Spring framework, a good option is to use the Spring Web Services library for this purpose.
Let's go through the process using an example of a free public web service that can convert a numeric input into words.
SOAP (W3C) web services use a standard document format called WSDL to specify the available operations and the respective request/response schemas of a web service. So they serve a similar purpose like OpenAPI specifications in the REST API world.
Our example web service is called Number Conversion Service and its URL is https://www.dataaccess.com/webservicesserver/NumberConversion.wso. This URL is also called the endpoint of the web service. A commonly used convention is that adding a "?wsdl" suffix to a web service endpoint URL gives us a URL to download the web service's WSDL. The convention applies in this case, so if we try the URL https://www.dataaccess.com/webservicesserver/NumberConversion.wso?wsdl. in a web browser, we can see an XML document containing "xmlns="http://schemas.xmlsoap.org/wsdl/" is returned, i.e. a document using the WSDL namespace.
Let's first try the service manually. We will use the SoapUI testing tool. After importing the WSDL, test requests are automatically created. There are two versions of the "SOAP binding" imported from the WSDL. We will use the newer one, SOAP 1.2. We just need to substitute the input number in the place of a question mark and we can invoke the web service:
Now let's start implementing our Java Spring Boot code. You can check out the full sources on GitHub.
We'll use the Spring Initializr to generate the skeleton of our project. We will choose Spring Web and Spring Web Services as dependencies.
We will use a Maven plugin to generate Java models from the WSDL file. We copy the WSDL file in our source directory so that we do not have to download it on every build and add the following to the pom.xml:
<plugin>
<groupId>org.jvnet.jaxb</groupId>
<artifactId>jaxb-maven-plugin</artifactId>
<version>4.0.0</version>
<executions>
<execution>
<id>schema-generate</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<schemaLanguage>WSDL</schemaLanguage>
<generatePackage>com.example.springwsdemo.gen</generatePackage>
<schemaDirectory>src/main/wsdl</schemaDirectory>
<generateDirectory>${project.build.directory}/generated-sources</generateDirectory>
<schemaIncludes>
<include>*.wsdl</include>
</schemaIncludes>
</configuration>
</execution>
</executions>
</plugin>
The Java sources are generated in the target directory, so they are not versioned in git. Generated code should not be manually edited, our manually written source code will only refer to it. In case the WSDL file changes, the classes will be regenerated automatically.
To invoke the web service, we will create a Spring service class, as a subclass of WebServiceGatewaySupport. We will set the web service URL and the package of the generated schema classes in its constructor. Then we can use the marshalSendAndReceive method of the WebServiceTemplate to call the web service operation:
@Service
public class NumberConversionClient extends WebServiceGatewaySupport {
public NumberConversionClient() {
setDefaultUri("https://www.dataaccess.com/webservicesserver/NumberConversion.wso");
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.example.springwsdemo.gen");
setMarshaller(marshaller);
setUnmarshaller(marshaller);
}
public String convert(BigDecimal input) {
NumberToDollars requestPayload = new NumberToDollars();
requestPayload.setDNum(input);
NumberToDollarsResponse responsePayload = (NumberToDollarsResponse) getWebServiceTemplate()
.marshalSendAndReceive(requestPayload);
return responsePayload.getNumberToDollarsResult();
}
}
After wiring the service to the REST controller our tiny integration application is ready:
@RestController
public class NumberController {
private final NumberConversionClient numberConversionClient;
public NumberController(NumberConversionClient numberConversionClient) {
this.numberConversionClient = numberConversionClient;
}
@GetMapping("/number/{input}")
String convert(@PathVariable String input) {
return numberConversionClient.convert(new BigDecimal(input));
}
}
We can start the application and try to call it e.g. from a web browser:
Top comments (2)
Concise and straight to the point. Great article, Marián!
I couldn't find NumberToDollars class in this project.