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
:
<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
:
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:
- Define your service contract using XML Schema (XSD)
- Generate Java classes from the XSD
- 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:
<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:
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:
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:
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:
- Sets up the MessageDispatcherServlet at "/ws/*" to handle SOAP requests
- Creates a WSDL definition based on our XSD schema
- Exposes the WSDL at http://localhost:8080/ws/countries.wsdl
Step 6: Create a Spring Boot Application
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
<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
<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:
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:
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:
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
- Use Contract-First Development: Always start with the XML schema definition
- Secure Your Services: Consider implementing WS-Security for sensitive services
- Handle Exceptions Properly: Create appropriate SOAP faults for error handling
- Validate Requests: Use Spring's validation framework to ensure request integrity
- Version Your Services: Include versioning in your namespace to manage changes
- Document Your Services: Annotate your WSDL properly for client developers
- Test Thoroughly: Use Spring's testing framework to test your endpoints
Real-world Applications
SOAP web services are still widely used in:
- Financial Services: Banking interfaces often use SOAP for transaction processing
- Enterprise Resource Planning (ERP): Systems like SAP expose SOAP interfaces
- Legacy System Integration: Connecting to older systems that only support SOAP
- Healthcare Systems: Many healthcare data exchange standards are SOAP-based
- 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
- Spring Web Services Documentation
- SOAP 1.1 Specification
- WS-Security Specification
- Spring Web Services Tutorial
Exercises
- Extend the country service to include additional operations like listing all countries or searching by currency
- Add input validation to ensure requests contain valid data
- Implement WS-Security to protect the web service with username/password authentication
- Create a more complex client that performs multiple operations
- 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! :)