Skip to main content

Spring SOAP Best Practices

Introduction

SOAP (Simple Object Access Protocol) remains a critical protocol for enterprise applications, particularly in sectors like finance, healthcare, and government services where formal contracts and strict message exchange patterns are essential. While RESTful services have gained popularity, SOAP still offers unique advantages including formal contracts through WSDL, built-in error handling, and stateful operations when needed.

Spring Web Services provides robust support for building SOAP web services using the "Contract-First" approach. This guide walks you through the best practices for designing, implementing, and maintaining Spring SOAP web services that are secure, maintainable, and performant.

Contract-First Development Approach

What is Contract-First?

Contract-first development means starting with the XML Schema (XSD) and WSDL definitions before writing any Java code. This contrasts with "code-first" approaches where you start with Java classes and generate the service contracts afterward.

Why Use Contract-First?

  1. Better service design: Forces you to think about the interface before implementation
  2. Improved interoperability: Creates standards-compliant contracts
  3. Version control: Makes it easier to evolve contracts while maintaining compatibility
  4. Technology independence: The contract is independent of your implementation

Contract-First Implementation Example

Start by creating your XSD schema:

xml
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:tns="http://example.org/customer"
targetNamespace="http://example.org/customer"
elementFormDefault="qualified">

<xs:element name="getCustomerRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="customerId" type="xs:long"/>
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:element name="getCustomerResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="customer" type="tns:customer"/>
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:complexType name="customer">
<xs:sequence>
<xs:element name="id" type="xs:long"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="email" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

Then generate Java classes from this schema using the Maven JAXB plugin:

xml
<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>${project.basedir}/src/main/resources/schemas</source>
</sources>
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<clearOutputDir>false</clearOutputDir>
</configuration>
</plugin>

Proper Project Structure

Organizing your SOAP web service project properly makes maintenance easier and helps separate concerns:

src/
├── main/
│ ├── java/
│ │ └── com/example/
│ │ ├── config/ # Configuration classes
│ │ ├── endpoint/ # SOAP endpoints
│ │ ├── exception/ # Custom exceptions
│ │ ├── model/ # Generated JAXB classes
│ │ ├── repository/ # Data access layer
│ │ └── service/ # Business logic
│ └── resources/
│ ├── schemas/ # XSD schema files
│ └── wsdl/ # WSDL files (if manual)

Configuring Spring Web Services

Basic Configuration

Here's a basic Spring Web Services configuration using Java-based configuration:

java
@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {

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

@Bean(name = "customers")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema customersSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("CustomersPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://example.org/customer");
wsdl11Definition.setSchema(customersSchema);
return wsdl11Definition;
}

@Bean
public XsdSchema customersSchema() {
return new SimpleXsdSchema(new ClassPathResource("schemas/customer.xsd"));
}
}

Creating Clean Endpoints

Spring Web Services endpoints should be focused on XML processing and delegating business logic to service classes:

java
@Endpoint
public class CustomerEndpoint {

private static final String NAMESPACE_URI = "http://example.org/customer";

private final CustomerService customerService;

public CustomerEndpoint(CustomerService customerService) {
this.customerService = customerService;
}

@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCustomerRequest")
@ResponsePayload
public GetCustomerResponse getCustomer(@RequestPayload GetCustomerRequest request) {
Customer customer = customerService.getCustomerById(request.getCustomerId());

GetCustomerResponse response = new GetCustomerResponse();
response.setCustomer(customer);

return response;
}
}

Best practices for endpoints:

  1. Single Responsibility: Each endpoint method handles one request type
  2. Thin Controllers: Delegate business logic to service classes
  3. Use Spring's Annotations: Leverage @PayloadRoot, @RequestPayload and @ResponsePayload
  4. Proper Error Handling: Use exception handlers (discussed later)

Service Layer Implementation

The service layer contains your business logic and should be separated from endpoints:

java
@Service
public class CustomerServiceImpl implements CustomerService {

private final CustomerRepository customerRepository;

public CustomerServiceImpl(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}

@Override
public Customer getCustomerById(long id) {
return customerRepository.findById(id)
.orElseThrow(() -> new CustomerNotFoundException("Customer not found with id: " + id));
}

// Other business methods...
}

Robust Error Handling

Proper error handling is critical for SOAP services. Spring Web Services provides mechanisms to handle and transform exceptions into SOAP faults.

Custom SOAP Fault Exception Resolver

java
@Component
public class DetailSoapFaultDefinitionExceptionResolver extends SoapFaultMappingExceptionResolver {

private static final String DEFAULT_LOCALE = "en";

public DetailSoapFaultDefinitionExceptionResolver() {
setOrder(1);
}

@Override
protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) {
if (ex instanceof CustomerNotFoundException) {
SoapFaultDetail detail = fault.addFaultDetail();
detail.addFaultDetailElement(new QName("http://example.org/faults", "CustomerFault"))
.addText(ex.getMessage());
}
}

@Override
public void afterPropertiesSet() throws Exception {
setExceptionMappings(Collections.singletonMap(
CustomerNotFoundException.class.getName(),
SoapFaultDefinitionEditor.DEFAULT_NAMESPACE_URI + ":CLIENT"
));
setDefaultFault(
SoapFaultDefinitionEditor.DEFAULT_NAMESPACE_URI + ":SERVER",
"A server error occurred"
);
super.afterPropertiesSet();
}
}

Security Best Practices

WS-Security Implementation

Spring Web Services supports WS-Security for authentication and message security. Here's how to configure username token authentication:

java
@Bean
public XwsSecurityInterceptor securityInterceptor() {
XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
securityInterceptor.setCallbackHandler(callbackHandler());
securityInterceptor.setPolicyConfiguration(new ClassPathResource("security-policy.xml"));
return securityInterceptor;
}

@Bean
public SimplePasswordValidationCallbackHandler callbackHandler() {
SimplePasswordValidationCallbackHandler callbackHandler = new SimplePasswordValidationCallbackHandler();
// In production, use a secure password store
callbackHandler.setUsersMap(Collections.singletonMap("admin", "secret"));
return callbackHandler;
}

@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(securityInterceptor());
}

And the corresponding security policy:

xml
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="true"/>
</xwss:SecurityConfiguration>

SSL Configuration

Always use HTTPS in production. Configure SSL in your Spring Boot application:

properties
# application.properties
server.port=8443
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=password
server.ssl.keyStoreType=PKCS12
server.ssl.keyAlias=tomcat

Performance Optimization

MTOM for Binary Data

For services that need to transmit binary data like files or images, use MTOM (Message Transmission Optimization Mechanism):

java
@Bean
public Wss4jSecurityInterceptor securityInterceptor() {
// ... other configuration

// Enable MTOM
securityInterceptor.setEnableMtom(true);

return securityInterceptor;
}

Connection Pooling

For services that connect to databases or other services, configure connection pooling:

java
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/customer_db");
config.setUsername("user");
config.setPassword("password");
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setIdleTimeout(30000);
return new HikariDataSource(config);
}

Logging and Monitoring

Logging SOAP Messages

For debugging, it's helpful to log SOAP messages:

java
@Bean
public SoapMessageLoggingInterceptor loggingInterceptor() {
SoapMessageLoggingInterceptor interceptor = new SoapMessageLoggingInterceptor();
interceptor.setLogRequest(true);
interceptor.setLogResponse(true);
// Only log SOAP envelopes in development
interceptor.setLogSoapMessage(true);
return interceptor;
}

@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(loggingInterceptor());
// Add other interceptors...
}

Health Checks and Metrics

Implement health endpoints and metrics using Spring Boot Actuator:

xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Configure in application.properties:

properties
# Enable selective endpoints
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always

Versioning Strategies

As your service evolves, versioning becomes important:

  1. Namespace Versioning: Include version in namespace URI

    xml
    <xs:schema targetNamespace="http://example.org/customer/v2">
  2. Side-by-side Deployment: Deploy multiple versions simultaneously

    java
    @Bean(name = "customersV1")
    public DefaultWsdl11Definition customersV1Wsdl(XsdSchema customersV1Schema) {
    // Configuration for V1
    }

    @Bean(name = "customersV2")
    public DefaultWsdl11Definition customersV2Wsdl(XsdSchema customersV2Schema) {
    // Configuration for V2
    }

Testing SOAP Services

Unit Testing Endpoints

Test endpoints in isolation using MockWebServiceClient:

java
@WebAppConfiguration
@SpringJUnitConfig(classes = {WebServiceConfig.class})
public class CustomerEndpointTest {

@Autowired
private ApplicationContext applicationContext;

private MockWebServiceClient mockClient;

@MockBean
private CustomerService customerService;

@BeforeEach
public void setup() {
mockClient = MockWebServiceClient.createClient(applicationContext);
}

@Test
public void testGetCustomer() {
Customer customer = new Customer();
customer.setId(1L);
customer.setName("John Doe");
customer.setEmail("[email protected]");

when(customerService.getCustomerById(1L)).thenReturn(customer);

Source requestPayload = new StringSource(
"<c:getCustomerRequest xmlns:c='http://example.org/customer'>" +
"<c:customerId>1</c:customerId>" +
"</c:getCustomerRequest>"
);

Source responsePayload = new StringSource(
"<ns2:getCustomerResponse xmlns:ns2='http://example.org/customer'>" +
"<ns2:customer><ns2:id>1</ns2:id><ns2:name>John Doe</ns2:name>" +
"<ns2:email>[email protected]</ns2:email></ns2:customer>" +
"</ns2:getCustomerResponse>"
);

mockClient
.sendRequest(withPayload(requestPayload))
.andExpect(payload(responsePayload));
}
}

Integration Testing

Test the entire SOAP service using a real HTTP client:

java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class CustomerIntegrationTest {

private static final String WSDL_URI = "http://localhost:8080/ws/customers.wsdl";

private CustomerServicePort port;

@BeforeEach
public void setUp() {
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(CustomerServicePort.class);
factory.setAddress(WSDL_URI);
port = (CustomerServicePort) factory.create();
}

@Test
public void testGetCustomer() {
GetCustomerRequest request = new GetCustomerRequest();
request.setCustomerId(1);

GetCustomerResponse response = port.getCustomer(request);

assertNotNull(response.getCustomer());
assertEquals(1, response.getCustomer().getId());
assertEquals("John Doe", response.getCustomer().getName());
}
}

Documentation

Always document your SOAP services thoroughly:

  1. Add documentation in XSD: Use <xs:annotation> and <xs:documentation> tags
  2. Generate HTML documentation: Use tools like xs3p or stylesheet transformations
  3. Add Swagger for endpoints: Although primarily for REST APIs, can provide an overview
xml
<xs:element name="getCustomerRequest">
<xs:annotation>
<xs:documentation>
Request to get customer details by ID.
The customerId must be a valid non-negative integer.
</xs:documentation>
</xs:annotation>
<!-- Element definition -->
</xs:element>

Summary

Building robust SOAP web services with Spring requires following established best practices:

  1. Use Contract-First Development: Start with XSD, generate Java classes
  2. Organize Code Properly: Separate endpoints, services, and repositories
  3. Handle Errors Gracefully: Use the SOAP fault framework
  4. Implement Security: Use WS-Security and HTTPS
  5. Optimize Performance: Use MTOM for binary data and connection pooling
  6. Monitor and Log: Track service health and debug issues
  7. Version Appropriately: Plan for evolution from the beginning
  8. Test Extensively: Unit and integration tests
  9. Document Thoroughly: Make your service easy to understand and use

By following these best practices, you can create SOAP web services that are maintainable, secure, and performant.

Additional Resources

Exercises

  1. Create a Spring SOAP web service with a Customer and Order schema using contract-first approach
  2. Implement WS-Security with username token authentication
  3. Add error handling for common scenarios like "not found" and "validation errors"
  4. Write unit and integration tests for your service
  5. Enable MTOM for a file upload operation that accepts binary data


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