1. Overview
In this tutorial, we’ll learn how to build a SOAP client in Java with JAX-WS RI in Java 11.
First, we’ll generate the client code using the wsimport utility and then test it using a JUnit.
For those starting out, our introduction to JAX-WS provides great background on the subject.
2. The Web Service
Before we build the client, we need a SOAP web service to consume. We’ll use a service that fetches country details based on the country name.
2.1. Summary of Implementation
The web service is exposed via the CountryService interface and deployed using the javax.xml.ws.Endpoint API. Next, we publish the endpoint by running a CountryServicePublisher class as a Java application.
Once the server is running, the web service WSDL is available at:
http://localhost:8888/ws/country?wsdl
This WSDL serves as a contract that defines the service operations and data types, enabling us to generate client code.
2.2. Understanding the Web Services Description Language (WSDL)
Let’s look at our web service’s WSDL, country:
<?xml version="1.0" encoding="UTF-8"?>
<definitions <!-- namespace declarations -->
targetNamespace="http://server.ws.soap.baeldung.com/" name="CountryServiceImplService">
<types>
<xsd:schema>
<xsd:import namespace="http://server.ws.soap.baeldung.com/"
schemaLocation="http://localhost:8888/ws/country?xsd=1"></xsd:import>
</xsd:schema>
</types>
<message name="findByName">
<part name="arg0" type="xsd:string"></part>
</message>
<message name="findByNameResponse">
<part name="return" type="tns:country"></part>
</message>
<portType name="CountryService">
<operation name="findByName">
<input wsam:Action="http://server.ws.soap.baeldung.com/CountryService/findByNameRequest"
message="tns:findByName"></input>
<output wsam:Action="http://server.ws.soap.baeldung.com/CountryService/findByNameResponse"
message="tns:findByNameResponse"></output>
</operation>
</portType>
<binding name="CountryServiceImplPortBinding" type="tns:CountryService">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"></soap:binding>
<operation name="findByName">
<soap:operation soapAction=""></soap:operation>
<input>
<soap:body use="literal" namespace="http://server.ws.soap.baeldung.com/"></soap:body>
</input>
<output>
<soap:body use="literal" namespace="http://server.ws.soap.baeldung.com/"></soap:body>
</output>
</operation>
</binding>
<service name="CountryServiceImplService">
<port name="CountryServiceImplPort" binding="tns:CountryServiceImplPortBinding">
<soap:address location="http://localhost:8888/ws/country"></soap:address>
</port>
</service>
</definitions>
The WSDL describes the structure and operations of the web service. It defines a single operation, findByName, which takes a string input representing the country name and returns a country object.
This country object includes details such as the country’s name, capital, currency, and population. The currency field is further defined as an enumeration with possible values like USD, EUR, and INR.
This comprehensive structure allows the client to understand the input and output requirements for interacting with the web service effectively.
2.3. Data Model Schema (XSD)
The country type and related data are defined in an XSD available at:
http://localhost:8888/ws/country?xsd=1
Below is an example of the XSD:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema <!-- namespace declarations -->
targetNamespace="http://server.ws.soap.baeldung.com/">
<xs:complexType name="country">
<xs:sequence>
<xs:element name="capital" type="xs:string" minOccurs="0"></xs:element>
<xs:element name="currency" type="tns:currency" minOccurs="0"></xs:element>
<xs:element name="name" type="xs:string" minOccurs="0"></xs:element>
<xs:element name="population" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="currency">
<xs:restriction base="xs:string">
<xs:enumeration value="EUR"></xs:enumeration>
<xs:enumeration value="INR"></xs:enumeration>
<xs:enumeration value="USD"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:schema>
With the WSDL and XSD, we have all the information needed to generate client code and interact with the service. Let’s move on to building the client.
3. Using wsimport to Generate Client Code
3.1. Generating Client Code
To generate client code, we need to add the jakarta.xml.ws-api, jaxws-rt, and jaxws-ri dependencies to the pom.xml file:
<dependencies>
<dependency>
<groupId>jakarta.xml.ws</groupId
<artifactId>jakarta.xml.ws-api</artifactId
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>3.0.0</version
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-ri</artifactId>
<version>2.3.1</version
<type>pom</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<wsdlUrls>
<wsdlUrl>http://localhost:8888/ws/country?wsdl</wsdlUrl>
</wsdlUrls>
<keep>true</keep>
<packageName>com.baeldung.soap.ws.client.generated</packageName>
<sourceDestDir>src/main/java</sourceDestDir>
</configuration>
</plugin>
</plugins>
</build>
Next, we can run the following Maven command to generate the client code:
mvn clean jaxws:wsimport
3.2. Generated POJOs
The wsimport tool generates a POJO class, Country.java, based on the XSD schema. This class is annotated with JAXB annotations for XML marshaling and unmarshaling:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "country", propOrder = { "capital", "currency", "name", "population" })
public class Country {
protected String capital;
@XmlSchemaType(name = "string")
protected Currency currency;
protected String name;
protected int population;
// standard getters and setters
}
@XmlType(name = "currency")
@XmlEnum
public enum Currency {
EUR, INR, USD;
public String value() {
return name();
}
public static Currency fromValue(String v) {
return valueOf(v);
}
}
3.3. CountryService Interface
The CountryService interface acts as a proxy to the actual web service. It declares the findByName() method as defined in the server:
@WebService(name = "CountryService", targetNamespace = "http://server.ws.soap.baeldung.com/")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface CountryService {
@WebMethod
@WebResult(partName = "return")
public Country findByName(@WebParam(name = "arg0") String arg0);
}
3.4. CountryServiceImplService
The CountryServiceImplService class extends javax.xml.ws.Service. It provides methods for interacting with the web service.
Its annotation WebServiceClient denotes that it is the client view of a service:
@WebServiceClient(name = "CountryServiceImplService",
wsdlLocation = "http://localhost:8888/ws/country?wsdl")
public class CountryServiceImplService extends Service {
public CountryServiceImplService() {
super(QName("http://server.ws.soap.baeldung.com/", "CountryServiceImplService"));
}
@WebEndpoint(name = "CountryServiceImplPort")
public CountryService getCountryServiceImplPort() {
return super.getPort(CountryService.class);
}
}
To call the web service, we use the CountryServiceImplService to get a proxy instance. This allows us to invoke the findByName() method as if we are calling it locally, abstracting away the complexity of remote communication.
4. Testing the Client
Next, to test the client, we’ll write a JUnit test to connect to the web service using the generated client code.
4.1. Setting Up the Service Proxy
Before running the tests, we need to obtain the proxy instance of the CountryService on the client side:
@BeforeClass
public static void setup() {
CountryServiceImplService service = new CountryServiceImplService();
CountryService countryService = service.getCountryServiceImplPort();
}
4.2. Writing the Tests
Now we can write tests to check the functionality of the findByName() method.
@Test
public void givenCountryService_whenCountryIndia_thenCapitalIsNewDelhi() {
assertEquals("New Delhi", countryService.findByName("India").getCapital());
}
@Test
public void givenCountryService_whenCountryFrance_thenPopulationCorrect() {
assertEquals(66710000, countryService.findByName("France").getPopulation());
}
@Test
public void givenCountryService_whenCountryUSA_thenCurrencyUSD() {
assertEquals(Currency.USD, countryService.findByName("USA").getCurrency());
}
With the proxy instance set up, invoking the remote service’s methods is as simple as calling local methods.
The proxy’s findByName() method returns a Country object, and we can easily access its properties for assertions, like capital, population, and currency.
5. Conclusion
In this article, we demonstrate how to consume a SOAP web service in Java using JAX-WS RI and the wsimport utility for Java 11.
Alternatively, we can use other JAX-WS implementations such as Apache CXF, Apache Axis2, and Spring to do the same.
The code backing this article is available on GitHub. Once you're
logged in as a Baeldung Pro Member, start learning and coding on the project.