Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

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

2. SOAP Web Services

In short, a web service is 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’s 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 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’ll 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>
</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>

Finally, we will also need the jakarta XML bind dependencies to support the classes generated by our schema and additionally the jaxb-runtime:

<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
    <version>4.0.0</version>
</dependency>

<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</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’ll 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 can see the format of the getCountryRequest web service request. We’ll define it to accept one parameter of the type string.

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

Finally, we can see the currency object used within the country object.

4.3. Generate the Domain Java Classes

Now we’ll 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 a 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>3.1.0</version>
    <executions>
        <execution>
            <id>xjc</id>
            <goals>
                <goal>xjc</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <sources>
            <source>src/main/resources/countries.xsd</source>
        </sources>
        <outputDirectory>src/main/java</outputDirectory>
        <clearOutputDir>false</clearOutputDir>
    </configuration>
</plugin>

Here we notice two important configurations:

  • <source>src/main/resources/countries.xsd</source> – The location of the XSD file
  • <outputDirectory>src/main/java</outputDirectory> – Where we want our Java code to be generated to

To generate the Java classes, we could use the XJC tool from our Java installation. It’s even more simple in our Maven project, 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’ll initiate the processing and send the response back.

Before defining this, we’ll 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, we’ll 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

Now let’s 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> messageDispatcherServlet(ApplicationContext applicationContext) {
    MessageDispatcherServlet servlet = new MessageDispatcherServlet();
    servlet.setApplicationContext(applicationContext);
    servlet.setTransformWsdlLocations(true);
    return new ServletRegistrationBean<>(servlet, "/ws/*");
}

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

We’ll 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, we’ll 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’s possible to create a WAR file and deploy it to an external application server. Instead, we’ll use Spring Boot, which is a faster and easier way to get the application up and running.

First, we’ll 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’ll 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 can 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 and 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 demonstrated how to generate Java code from an XSD file. Finally, we configured the Spring beans needed to process the SOAP requests.

The complete source code is available on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.