Spring SOAP Endpoints
Introduction
SOAP (Simple Object Access Protocol) is a messaging protocol specification for exchanging structured information in web services. Spring Web Services provides comprehensive support for building SOAP-based web services using annotations, which simplifies the development process significantly.
In this tutorial, we'll explore how to create and configure SOAP endpoints in Spring Web Services. By the end, you'll understand how to build, deploy, and test SOAP endpoints that follow best practices for web service development.
What are SOAP Endpoints?
SOAP endpoints are the entry points for SOAP messages in your web service. They handle incoming SOAP requests, process them according to your business logic, and return SOAP responses. In Spring Web Services, endpoints are typically implemented as Spring beans with methods annotated to indicate which SOAP messages they can handle.
Prerequisites
Before we start, ensure you have:
- JDK 8 or higher installed
- Maven or Gradle for dependency management
- Basic understanding of Spring Framework
- Familiarity with XML and XSD schemas
Setting Up Your Spring Web Services Project
Let's start by setting up a basic Spring Web Services project with Maven dependencies.
Add the following dependencies to your pom.xml
:
<dependencies>
<!-- Spring Web Services -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
<version>2.7.3</version>
</dependency>
<!-- WSDL4J -->
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.3</version>
</dependency>
<!-- JAXB API -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
For generating Java classes from XSD, add the JAXB2 Maven plugin:
<build>
<plugins>
<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>
</plugins>
</build>
Creating an XSD Schema
Before creating endpoints, we need to define our data model using XSD schema. Let's create a simple schema for a student management system.
Create a file named students.xsd
in src/main/resources/schemas
:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://www.example.org/students"
targetNamespace="http://www.example.org/students"
elementFormDefault="qualified">
<xs:element name="getStudentRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:long"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="getStudentResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="student" type="tns:student"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="student">
<xs:sequence>
<xs:element name="id" type="xs:long"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="grade" type="xs:int"/>
<xs:element name="email" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Run the Maven build to generate Java classes from this schema:
mvn generate-sources
This will generate classes like GetStudentRequest
, GetStudentResponse
, and Student
in your target directory.
Creating a Spring SOAP Endpoint
Now, let's create our SOAP endpoint. This is a Spring bean that processes SOAP requests.
package com.example.soap.endpoint;
import org.example.students.GetStudentRequest;
import org.example.students.GetStudentResponse;
import org.example.students.Student;
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 StudentEndpoint {
private static final String NAMESPACE_URI = "http://www.example.org/students";
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getStudentRequest")
@ResponsePayload
public GetStudentResponse getStudent(@RequestPayload GetStudentRequest request) {
GetStudentResponse response = new GetStudentResponse();
Student student = new Student();
student.setId(request.getId());
student.setName("John Doe");
student.setGrade(90);
student.setEmail("[email protected]");
response.setStudent(student);
return response;
}
}
In this example:
@Endpoint
marks this class as a Spring WS endpoint@PayloadRoot
maps incoming SOAP messages to this method based on namespace and local part@RequestPayload
indicates the method parameter should be bound to the request payload@ResponsePayload
indicates the method return value should be bound to the response payload
Configuring Web Services
Now, let's configure our web service to expose the WSDL and handle SOAP requests:
package com.example.soap.config;
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.config.annotation.WsConfigurerAdapter;
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 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 = "students")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema studentsSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("StudentsPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://www.example.org/students");
wsdl11Definition.setSchema(studentsSchema);
return wsdl11Definition;
}
@Bean
public XsdSchema studentsSchema() {
return new SimpleXsdSchema(new ClassPathResource("schemas/students.xsd"));
}
}
This configuration:
- Sets up the MessageDispatcherServlet to handle SOAP requests
- Creates a WSDL definition based on our XSD schema
- Maps the WSDL to be available at
/ws/students.wsdl
Spring Boot Application Class
Create the main application class:
package com.example.soap;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StudentServiceApplication {
public static void main(String[] args) {
SpringApplication.run(StudentServiceApplication.class, args);
}
}
Testing the SOAP Endpoint
Now that our SOAP service is ready, let's test it using a SOAP client. You can use tools like SoapUI, Postman, or even a simple curl command.
Here's a sample SOAP request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:stud="http://www.example.org/students">
<soapenv:Header/>
<soapenv:Body>
<stud:getStudentRequest>
<stud:id>123</stud:id>
</stud:getStudentRequest>
</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:getStudentResponse xmlns:ns2="http://www.example.org/students">
<ns2:student>
<ns2:id>123</ns2:id>
<ns2:name>John Doe</ns2:name>
<ns2:grade>90</ns2:grade>
<ns2:email>[email protected]</ns2:email>
</ns2:student>
</ns2:getStudentResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Adding Database Integration
In a real-world scenario, you would typically fetch data from a database. Let's enhance our example to include a repository layer:
First, create a repository interface:
package com.example.soap.repository;
import org.example.students.Student;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Repository
public class StudentRepository {
private static final Map<Long, Student> students = new HashMap<>();
@PostConstruct
public void initData() {
Student student1 = new Student();
student1.setId(1);
student1.setName("Alice Smith");
student1.setGrade(95);
student1.setEmail("[email protected]");
students.put(student1.getId(), student1);
Student student2 = new Student();
student2.setId(2);
student2.setName("Bob Johnson");
student2.setGrade(85);
student2.setEmail("[email protected]");
students.put(student2.getId(), student2);
}
public Student findStudent(Long id) {
return students.get(id);
}
}
Now update the endpoint to use this repository:
package com.example.soap.endpoint;
import org.example.students.GetStudentRequest;
import org.example.students.GetStudentResponse;
import org.example.students.Student;
import com.example.soap.repository.StudentRepository;
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 StudentEndpoint {
private static final String NAMESPACE_URI = "http://www.example.org/students";
private StudentRepository studentRepository;
@Autowired
public StudentEndpoint(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getStudentRequest")
@ResponsePayload
public GetStudentResponse getStudent(@RequestPayload GetStudentRequest request) {
GetStudentResponse response = new GetStudentResponse();
Student student = studentRepository.findStudent(request.getId());
response.setStudent(student);
return response;
}
}
Error Handling in SOAP Endpoints
SOAP services should return proper fault messages when errors occur. Let's add error handling:
First, add a fault element to your XSD schema:
<xs:element name="studentFault">
<xs:complexType>
<xs:sequence>
<xs:element name="message" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
Create a custom exception class:
package com.example.soap.exception;
import org.springframework.ws.soap.server.endpoint.annotation.FaultCode;
import org.springframework.ws.soap.server.endpoint.annotation.SoapFault;
@SoapFault(faultCode = FaultCode.CLIENT)
public class StudentNotFoundException extends RuntimeException {
public StudentNotFoundException(String message) {
super(message);
}
}
Update the endpoint to use this exception:
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getStudentRequest")
@ResponsePayload
public GetStudentResponse getStudent(@RequestPayload GetStudentRequest request) {
GetStudentResponse response = new GetStudentResponse();
Student student = studentRepository.findStudent(request.getId());
if (student == null) {
throw new StudentNotFoundException("Student not found with ID: " + request.getId());
}
response.setStudent(student);
return response;
}
Security in SOAP Endpoints
For production applications, you'll want to add security. Spring WS supports WS-Security. Here's a simple example using HTTP Basic Authentication:
Add the spring-boot-starter-security dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Create a security configuration:
package com.example.soap.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/ws/**").authenticated()
.and()
.httpBasic();
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("USER")
.build()
);
}
}
Best Practices for SOAP Endpoints
- Contract-First Design: Always start with the XSD schema definition before implementing endpoints
- Proper Namespaces: Use meaningful namespaces to avoid conflicts
- Versioning: Include version information in your namespace or schema
- Error Handling: Provide meaningful fault messages for different error scenarios
- Validation: Validate incoming requests against your schema
- Documentation: Annotate your WSDL and schema with documentation
- Security: Implement appropriate security measures
- Logging: Add logging for debugging and monitoring
- Testing: Create automated tests for your endpoints
Summary
In this tutorial, we've learned:
- How to set up a Spring Web Services project
- Creating an XSD schema for our data model
- Implementing SOAP endpoints using Spring WS annotations
- Configuring the web service to expose WSDL
- Adding a repository layer for data access
- Implementing error handling
- Adding security to SOAP endpoints
- Best practices for designing and implementing SOAP web services
By following these steps, you can create robust, maintainable, and secure SOAP web services using Spring Web Services.
Additional Resources
- Spring Web Services Documentation
- SOAP 1.1 Specification
- Web Services Description Language (WSDL)
- XML Schema Definition
Exercises
- Extend the schema to add operations for creating, updating, and deleting students
- Implement the corresponding endpoints for these operations
- Add validation to ensure student data meets certain criteria (e.g., valid email format)
- Implement a more sophisticated security model, such as WS-Security with SAML tokens
- Create a client application that consumes your SOAP web service
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)