Skip to main content

Spring SOAP Introduction

What is SOAP?

SOAP (Simple Object Access Protocol) is an XML-based messaging protocol for exchanging structured information between web services. It's a mature technology widely used in enterprise environments for creating interoperable web services.

Spring Web Services (Spring-WS) provides a powerful framework for developing SOAP-based web services that follow best practices like contract-first development. This approach means you design the XML schema first, then implement the service based on that contract.

Why Use SOAP in Spring?

While REST has become more prevalent for web APIs, SOAP still offers several advantages:

  • Formal Contracts: WSDL (Web Services Description Language) provides a formal definition of the service
  • Enterprise Standards: Built-in support for WS-Security, WS-ReliableMessaging, and other enterprise standards
  • Type Safety: Strong typing through XML Schema
  • Legacy System Integration: Many enterprise systems expose SOAP interfaces

Spring-WS simplifies SOAP service development by providing abstractions that handle the complexities of SOAP message processing.

Setting Up Spring SOAP Web Services

Prerequisites

To get started with Spring SOAP web services, you'll need:

  • Java 8 or higher
  • Maven or Gradle
  • IDE (like IntelliJ IDEA, Eclipse, or VS Code)

Adding Dependencies

For a Maven project, add these dependencies to your pom.xml:

xml
<dependencies>
<!-- Spring Web Services -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>

<!-- WSDL4J -->
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Maven Plugin for XSD to Java -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.5.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>
</plugins>
</build>

For Gradle users, add to your build.gradle:

groovy
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web-services'
implementation 'wsdl4j:wsdl4j'
}

Contract-First Development

Spring-WS encourages the contract-first approach, which means:

  1. Define your service contract using XML Schema (XSD)
  2. Generate Java classes from the XSD
  3. Implement your service based on these generated classes

Step 1: Define the XML Schema

Create an XSD file that defines the structure of your SOAP messages. For example, a simple country information service:

xml
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:tns="http://www.example.org/countries"
targetNamespace="http://www.example.org/countries"
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="EUR"/>
<xs:enumeration value="USD"/>
<xs:enumeration value="GBP"/>
<xs:enumeration value="JPY"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>

Save this as src/main/resources/countries.xsd.

Step 2: Generate Java Classes

When you run mvn compile or gradle build, the JAXB plugin will generate Java classes from your XSD. The generated classes will be used to handle the XML conversion.

Step 3: Create an Endpoint

Endpoints handle incoming SOAP requests. Here's how to implement a simple endpoint:

java
import org.example.countries.GetCountryRequest;
import org.example.countries.GetCountryResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

@Endpoint
public class CountryEndpoint {
private static final String NAMESPACE_URI = "http://www.example.org/countries";

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;
}
}

The key annotations in this endpoint are:

  • @Endpoint: Registers the class as a SOAP endpoint
  • @PayloadRoot: Maps the endpoint method to a specific request type
  • @RequestPayload: Indicates the parameter should be populated from the request
  • @ResponsePayload: Indicates the return value becomes the response payload

Step 4: Create a Repository

Let's create a simple repository to provide country data:

java
import org.example.countries.Country;
import org.example.countries.Currency;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@Component
public class CountryRepository {
private static final Map<String, Country> countries = new HashMap<>();

@PostConstruct
public void initData() {
Country spain = new Country();
spain.setName("Spain");
spain.setCapital("Madrid");
spain.setCurrency(Currency.EUR);
spain.setPopulation(46704314);
countries.put(spain.getName(), spain);

Country uk = new Country();
uk.setName("United Kingdom");
uk.setCapital("London");
uk.setCurrency(Currency.GBP);
uk.setPopulation(67215293);
countries.put(uk.getName(), uk);

Country usa = new Country();
usa.setName("United States");
usa.setCapital("Washington D.C.");
usa.setCurrency(Currency.USD);
usa.setPopulation(331002651);
countries.put(usa.getName(), usa);
}

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

Step 5: Configure Web Service Beans

Create a configuration class to set up the web service:

java
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;

@EnableWs
@Configuration
public class WebServiceConfig {

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

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

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

This configuration:

Step 6: Create a Spring Boot Application

java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

Testing the SOAP Service

After starting the application, you can test your SOAP service using tools like SoapUI, Postman, or a simple client application.

Example SOAP Request

xml
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:cou="http://www.example.org/countries">
<soapenv:Header/>
<soapenv:Body>
<cou:getCountryRequest>
<cou:name>Spain</cou:name>
</cou:getCountryRequest>
</soapenv:Body>
</soapenv:Envelope>

Expected SOAP Response

xml
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns2:getCountryResponse xmlns:ns2="http://www.example.org/countries">
<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>

Creating a SOAP Client

Let's also see how to consume a SOAP service using Spring:

java
import org.example.client.CountryClient;
import org.example.client.generated.CountryResponse;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class CountryClientApplication {

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

@Bean
CommandLineRunner lookup(CountryClient countryClient) {
return args -> {
String country = "Spain";
if (args.length > 0) {
country = args[0];
}
CountryResponse response = countryClient.getCountry(country);
System.out.println("Country: " + response.getCountry().getName());
System.out.println("Capital: " + response.getCountry().getCapital());
System.out.println("Population: " + response.getCountry().getPopulation());
System.out.println("Currency: " + response.getCountry().getCurrency());
};
}
}

And the client implementation:

java
import org.example.client.generated.CountryRequest;
import org.example.client.generated.CountryResponse;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.soap.client.core.SoapActionCallback;

public class CountryClient extends WebServiceGatewaySupport {

public CountryResponse getCountry(String country) {
CountryRequest request = new CountryRequest();
request.setName(country);

return (CountryResponse) getWebServiceTemplate()
.marshalSendAndReceive(
"http://localhost:8080/ws/countries",
request,
new SoapActionCallback("http://www.example.org/countries/GetCountryRequest"));
}
}

And finally, configure the client:

java
import org.example.client.CountryClient;
import org.example.client.generated.ObjectFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

@Configuration
public class CountryClientConfig {

@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("org.example.client.generated");
return marshaller;
}

@Bean
public CountryClient countryClient(Jaxb2Marshaller marshaller) {
CountryClient client = new CountryClient();
client.setDefaultUri("http://localhost:8080/ws");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
}

Best Practices for Spring SOAP Services

  1. Use Contract-First Development: Always start with the XML schema definition
  2. Secure Your Services: Consider implementing WS-Security for sensitive services
  3. Handle Exceptions Properly: Create appropriate SOAP faults for error handling
  4. Validate Requests: Use Spring's validation framework to ensure request integrity
  5. Version Your Services: Include versioning in your namespace to manage changes
  6. Document Your Services: Annotate your WSDL properly for client developers
  7. Test Thoroughly: Use Spring's testing framework to test your endpoints

Real-world Applications

SOAP web services are still widely used in:

  1. Financial Services: Banking interfaces often use SOAP for transaction processing
  2. Enterprise Resource Planning (ERP): Systems like SAP expose SOAP interfaces
  3. Legacy System Integration: Connecting to older systems that only support SOAP
  4. Healthcare Systems: Many healthcare data exchange standards are SOAP-based
  5. Telecommunications: Provisioning and billing systems often use SOAP

Summary

In this introduction to Spring SOAP Web Services, we've covered:

  • The basics of SOAP and when to use it
  • Setting up a Spring-WS project with proper dependencies
  • Creating a contract-first web service with XSD
  • Building an endpoint to handle requests
  • Configuring the web service
  • Testing the service
  • Creating a client to consume the service

SOAP remains relevant for enterprise integration scenarios where formal contracts, security, and reliability are paramount concerns. Spring Web Services provides a robust framework that simplifies the development of standards-compliant SOAP services.

Additional Resources

Exercises

  1. Extend the country service to include additional operations like listing all countries or searching by currency
  2. Add input validation to ensure requests contain valid data
  3. Implement WS-Security to protect the web service with username/password authentication
  4. Create a more complex client that performs multiple operations
  5. Implement error handling in both the service and client to gracefully handle failures


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)