Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll see how to create a SOAP-based web service with Spring Boot Starter Web Services.

2. SOAP Web Services

A web service is, in short, a machine-to-machine, platform independent service that allows communication over a network.

SOAP is a messaging protocol. Messages (requests and responses) are XML documents over HTTPThe XML contract is defined by the WSDL (Web Services Description Language). It provides a set of rules to define the messages, bindings, operations, and location of the service.

The XML used in SOAP can become extremely complex. For this reason, it is best to use SOAP with a framework like JAX-WS or Spring, as we’ll see in this tutorial.

3. Contract-First Development Style

There are two possible approaches when creating a web service: Contract-Last and Contract-First. When we use a contract-last approach, we start with the Java code, and we generate the web service contract (WSDL) from the classes. When using contract-first, we start with the WSDL contract, from which we generate the Java classes.

Spring-WS only supports the contract-first development style.

4. Setting Up the Spring Boot Project

We’re going to create a Spring Boot project where we’ll define our SOAP WS server.

4.1. Maven Dependencies

Let’s start by adding the spring-boot-starter-parent to our project:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.2.RELEASE</version>
</parent>

Next, let’s add the spring-boot-starter-web-services and wsdl4j dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
    <groupId>wsdl4j</groupId>
    <artifactId>wsdl4j</artifactId>
</dependency>

4.2. The XSD File

The contract-first approach requires us to create the domain (methods and parameters) for our service first. We’re going to use an XML schema file (XSD) that Spring-WS will export automatically as a WSDL:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.baeldung.com/springsoap/gen"
           targetNamespace="http://www.baeldung.com/springsoap/gen" elementFormDefault="qualified">

    <xs:element name="getCountryRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="getCountryResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="country" type="tns:country"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="country">
        <xs:sequence>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="population" type="xs:int"/>
            <xs:element name="capital" type="xs:string"/>
            <xs:element name="currency" type="tns:currency"/>
        </xs:sequence>
    </xs:complexType>

    <xs:simpleType name="currency">
        <xs:restriction base="xs:string">
            <xs:enumeration value="GBP"/>
            <xs:enumeration value="EUR"/>
            <xs:enumeration value="PLN"/>
        </xs:restriction>
    </xs:simpleType>
</xs:schema>

In this file, we see the format of the getCountryRequest web service request. We define it to accept one parameter of the type string.

Next, we define the format of the response, which contains an object of the type country.

At last, we see the currency object, used within the country object.

4.3. Generate the Domain Java Classes

We’re now going to generate the Java classes from the XSD file defined in the previous section. The jaxb2-maven-plugin will do this automatically during build time. The plugin uses the XJC tool as code generation engine. XJC compiles the XSD schema file into fully annotated Java classes.

Let’s add and configure the plugin in our pom.xml:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
    <version>1.6</version>
    <executions>
        <execution>
            <id>xjc</id>
            <goals>
                <goal>xjc</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <schemaDirectory>${project.basedir}/src/main/resources/</schemaDirectory>
        <outputDirectory>${project.basedir}/src/main/java</outputDirectory>
        <clearOutputDir>false</clearOutputDir>
    </configuration>
</plugin>

Here we notice two important configurations:

  • <schemaDirectory>${project.basedir}/src/main/resources</schemaDirectory> – The location of the XSD file
  • <outputDirectory>${project.basedir}/src/main/java</outputDirectory> – Where we want our Java code to be generated to

To generate the Java classes, we could simply use the xjc tool from our Java installation. Though in our Maven project things are even more simple, as the classes will be automatically generated during the usual Maven build:

mvn compile

4.4. Add the SOAP Web Service Endpoint

The SOAP web service endpoint class will handle all the incoming requests for the service. It will initiate the processing and will send the response back.

Before defining this, we create a Country repository in order to provide data to the web service.

@Component
public class CountryRepository {

    private static final Map<String, Country> countries = new HashMap<>();

    @PostConstruct
    public void initData() {
        // initialize countries map
    }

    public Country findCountry(String name) {
        return countries.get(name);
    }
}

Next, let’s configure the endpoint:

@Endpoint
public class CountryEndpoint {

    private static final String NAMESPACE_URI = "http://www.baeldung.com/springsoap/gen";

    private CountryRepository countryRepository;

    @Autowired
    public CountryEndpoint(CountryRepository countryRepository) {
        this.countryRepository = countryRepository;
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
    @ResponsePayload
    public GetCountryResponse getCountry(@RequestPayload GetCountryRequest request) {
        GetCountryResponse response = new GetCountryResponse();
        response.setCountry(countryRepository.findCountry(request.getName()));

        return response;
    }
}

Here are a few details to notice:

  • @Endpoint – registers the class with Spring WS as a Web Service Endpoint
  • @PayloadRootdefines the handler method according to the namespace and localPart attributes
  • @ResponsePayload – indicates that this method returns a value to be mapped to the response payload
  • @RequestPayload – indicates that this method accepts a parameter to be mapped from the incoming request

4.5. The SOAP Web Service Configuration Beans

Let’s now create a class for configuring the Spring message dispatcher servlet to receive the request:

@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
    // bean definitions
}

@EnableWs enables SOAP Web Service features in this Spring Boot application. The WebServiceConfig class extends the WsConfigurerAdapter base class, which configures the annotation-driven Spring-WS programming model.

Let’s create a MessageDispatcherServlet which is used for handling SOAP requests:

@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
    MessageDispatcherServlet servlet = new MessageDispatcherServlet();
    servlet.setApplicationContext(applicationContext);
    servlet.setTransformWsdlLocations(true);
    return new ServletRegistrationBean(servlet, "/ws/*");
}

We set the injected ApplicationContext object of the servlet so that Spring-WS can find other Spring beans.

We also enable the WSDL location servlet transformation. This transforms the location attribute of soap:address in the WSDL so that it reflects the URL of the incoming request.

Finally, let’s create a DefaultWsdl11Definition object. This exposes a standard WSDL 1.1 using an XsdSchema. The WSDL name will be the same as the bean name.

@Bean(name = "countries")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
    DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
    wsdl11Definition.setPortTypeName("CountriesPort");
    wsdl11Definition.setLocationUri("/ws");
    wsdl11Definition.setTargetNamespace("http://www.baeldung.com/springsoap/gen");
    wsdl11Definition.setSchema(countriesSchema);
    return wsdl11Definition;
}

@Bean
public XsdSchema countriesSchema() {
    return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
}

5. Testing the SOAP Project

Once the project configuration has been completed, we’re ready to test it.

5.1. Build and Run the Project

It would be possible to create a WAR file and deploy it to an external application server. We’ll instead use Spring Boot, which is a faster and easier way to get the application up and running.

First, we add the following class to make the application executable:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Notice that we’re not using any XML files (like web.xml) to create this application. It’s all pure Java.

Now we’re ready to build and run the application:

mvn spring-boot:run

To check if the application is running properly, we can open the WSDL through the URL: http://localhost:8080/ws/countries.wsdl

5.2. Test a SOAP Request

To test a request, we create the following file and name it request.xml:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:gs="http://www.baeldung.com/springsoap/gen">
    <soapenv:Header/>
    <soapenv:Body>
        <gs:getCountryRequest>
            <gs:name>Spain</gs:name>
        </gs:getCountryRequest>
    </soapenv:Body>
</soapenv:Envelope>

To send the request to our test server, we could use external tools like SoapUI or the Google Chrome extension Wizdler. Another way is to run the following command in our shell:

curl --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws

The resulting response might not be easy to read without indentation or line breaks.

To see it formatted, we can copy paste it to our IDE or another tool. If we’ve installed xmllib2, we can pipe the output of our curl command to xmllint:

curl [command-line-options] | xmllint --format -

The response should contain information about Spain:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
    <ns2:getCountryResponse xmlns:ns2="http://www.baeldung.com/springsoap/gen">
        <ns2:country>
            <ns2:name>Spain</ns2:name>
            <ns2:population>46704314</ns2:population>
            <ns2:capital>Madrid</ns2:capital>
            <ns2:currency>EUR</ns2:currency>
        </ns2:country>
    </ns2:getCountryResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

6. Conclusion

In this article, we learned how to create a SOAP web service using Spring Boot. We also learned how to generate Java code from an XSD file, and we saw how to configure the Spring beans needed to process the SOAP requests.

The complete source code is available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

Leave a Reply

avatar
  Subscribe  
Notify of