Spring boot SOAP web service

In this article, we will understand how to create a SOAP web service using Spring-WS project step by step with examples.
After going through this article, you will be able to create your own SOAP web service using Spring boot.
For understanding, we will create a SOAP web service that will accept a number in request and return its square in response.

Following are the high level steps required. We will go through each of these steps in detail one by one

  1. Create a new Spring Boot project using Spring Initializer or your preferred method.
  2. Add dependencies for Spring boot starter and other to your project pom.xml or build.gradle file according to the build tool.
    Below are the dependencies

    // GRADLE 
    implementation 
         'org.springframework.boot:spring-boot-starter-parent:2.7.8' 
    implementation 
         'org.springframework.boot:spring-boot-starter-web:2.7.8' 
    implementation 
         'org.springframework.boot:spring-boot-starter-web-services:2.7.8' 
    implementation 'wsdl4j:wsdl4j:16.3' 
    implementation 'javax.xml.bind:jaxb-api:2.3.1' 
    implementation 'com.sun.xml.bind:jaxb-core:2.3.0.1' 
    implementation 'com.sun.xml.bind:jaxb-impl:2.3.1'
    
    // MAVEN
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.8</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web-services</artifactId>
        <version>2.7.8</version>
      </dependency>
      <dependency>
        <groupId>wsdl4j</groupId>
        <artifactId>wsdl4j</artifactId>
        <version>1.6.3</version>
      </dependency>
      <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.1</version>
      </dependency>
      <dependency>
        <groupId>com.sun.xml.bind</groupId>
        <artifactId>jaxb-core</artifactId>
        <version>2.3.0.1</version>
      </dependency>
      <dependency>
        <groupId>com.sun.xml.bind</groupId>
        <artifactId>jaxb-impl</artifactId>
        <version>2.3.1</version>
      </dependency>
    </dependencies>
  3. Create an XSD (XML Schema Definition) file that defines the structure of the XML messages that the web service will handle.
    This XSD file will be used to generate the Java domain classes for your service or if you generate them manually, then they should follow this XSD.
    Below is the XSD for this example

    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
      xmlns:tns="http://codippa.com/ws/soap" 
      targetNamespace="http://codippa.com/ws/soap" 
      elementFormDefault="qualified">
      <xs:element name="squareNumberRequest">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="number" type="xs:int" />
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="squareNumberResponse">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="number" type="xs:int" />
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:schema>
  4. Use JAXB (Java Architecture for XML Binding) to generate the Java domain classes from the XSD file.
    You can do this using the xjc command-line tool or by using a plugin for your IDE.
  5. Create a service class that will handle the business logic for your web service. This class will be an endpoint and should be annotated with @Endpoint annotation.It should include methods that are annotated with @PayloadRoot and @ResponsePayload.
    These methods will handle incoming SOAP requests and return SOAP responses. This is covered in detail below.
  6. Create a configuration class that will configure the web service.
    This class should be annotated with @EnableWs and should include a method that is annotated with @Bean and returns a Wsdl11Definition.
    This bean will define the WSDL for your web service.
  7. Create a controller class that will handle the incoming SOAP requests and forward them to the service class for processing.

Domain Classes
Domain classes represent the classes which map to the SOAP request and SOAP response.
These classes contain the fields matching with the elements of request and have JAXB annotations.
Below are the classes for request and response in this example.
You can generate these classes automatically
1. Using JAXB2-Maven plugin(Only for Maven).
2. Installing JAXB tool and xjc command.
3. Using Gradle-jaxb plugin for gradle.

Below classes have been created manually, since they did not have many fields.

Request

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "squareNumberRequest", 
                namespace = "http://codippa.com/ws/soap")
public class SquareNumberRequest {
  @XmlElement(namespace = "http://codippa.com/ws/soap", 
              required = true)
  private int number;

  public int getNumber() {
    return number;
  }

  public void setNumber(int number) {
    this.number = number;
  }
}

Response

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "squareNumberResponse", 
                namespace = "http://codippa.com/ws/soap")
public class SquareNumberResponse {
  private int number;

  public int getNumber() {
    return number;
  }

  public void setNumber(int number) {
    this.number = number;
  }
}

Here,
@XmlAccessorType
Indicates that this entity will be serialized or not, that is, it will be converted to an XML).
@XmlRootElement
Indicates that this entity will be an XML element.
@XmlElement
Maps a field to XML element. This element is required over fields of request class since, they need to be populated with the values from incoming request.

These JAXB annotations need to be applied over the request class, so that XML request is deserialized directly to object and on the response class so that response object is converted(serialized) to XML.

Note that the value of namespace attribute matches with the namespace mentioned in xsd file.

Creating Endpoint
An endpoint represents the point of communication. It is a unique addressable URI that represents a specific service or resource.
Clients interact with an endpoint in order to access the functionality of the web service

An endpoint in Spring web service is created using @EndPoint annotation.
This annotation is used to indicate that a class is a web service endpoint and it is used by the Spring Web Services framework to manage the endpoint and handle incoming SOAP message.

We also need to create a method that will act as the handler for a specific web service operation. You can say that this method will contain the business logic of the operation.
In this example, it will calculate the square of number received in the request.

This method must have following two annotations:
A. @PayloadRoot
This annotation is used to map an incoming SOAP message to a specific method in the endpoint class.
It has a localPart attribute which maps the root element of the message to this method and a namespace attribute which corresponds to the namespace of the payload element.

B. @RequestPayload
This annotation is applied before the endpoint method argument. This method argument represents the request payload of the incoming SOAP message.

C. @ResponsePayload
This annotation is applied over the endpoint method.
It indicates the return value of the endpoint method that represents the response payload of the outgoing SOAP message.

Example of endpoint in this example is

@Endpoint
public class SquareNumberEndPoint {
  private static final String NAMESPACE_URI = 
                         "http://codippa.com/ws/soap";
  
  @PayloadRoot(localPart = "squareNumberRequest", 
               namespace = NAMESPACE_URI)
  @ResponsePayload
  public SquareNumberResponse getSquareNumber(@RequestPayload 
    SquareNumberRequest squareNumberRequest) {
    SquareNumberResponse squareNumberResponse = 
                     new SquareNumberResponse();
    // calculate square and set in response 
    squareNumberResponse.setNumber(
      squareNumberRequest.getNumber() * 
      squareNumberRequest.getNumber()
    );
    return squareNumberResponse;
  }
}

Note that the namespace should match with the namespace of element in incoming SOAP request.
Creating Configuration
This is the final step in creating a SOAP web service using Spring web service project.
The configuration class contains configuration for your application. You will need to annotate it with @EnableWs to add Spring web service configuration and @Configuration class to mark this class as a source of spring beans.

This class needs to extend Spring’s WsConfigurerAdapter class which provides you the ability to customize your web service such as adding interceptors, return value handlers etc.
Remember that the WsConfigurerAdapter contains empty implementation of all the methods, so you must override these methods if required.
For basic web services, this is generally not required.

An example of configuration class for this example is given below

@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {

  @Bean
  public ServletRegistrationBean<MessageDispatcherServlet> 
                        messageServlet(ApplicationContext context) {
   MessageDispatcherServlet servlet = 
                        new MessageDispatcherServlet();
   servlet.setApplicationContext(context);
   servlet.setTransformWsdlLocations(true);
   return new ServletRegistrationBean<>(servlet, "/ws/*");
  }
  
  @Bean(name="square")
  DefaultWsdl11Definition getWSDLDefinition(XsdSchema schema) {
    DefaultWsdl11Definition wsdl = new DefaultWsdl11Definition();
    wsdl.setPortTypeName("SquareNumberPort");
    wsdl.setTargetNamespace("http://codippa.com/ws/soap");
    wsdl.setLocationUri("/ws");
    wsdl.setSchema(schema);
    return wsdl;
  }
  
  @Bean
  public XsdSchema getSchema() {
    return new SimpleXsdSchema(new ClassPathResource("WS.xsd"));
  }
}

It has two methods:

A. messageServlet()
This method is used to configure the MessageDispatcherServlet, which is the servlet that handles incoming SOAP requests and forwards them to the appropriate endpoint for processing.
In this example, new ServletRegistrationBean<>(servlet, "/ws/*") will configure the application to handle all incoming requests that contain /ws/ URL pattern.
B. getWsdl11Definition()
This method is used to configure the WSDL definition for the web service.
It creates a DefaultWsdl11Definition object and sets various properties on it, such as the port type name, location URI, target namespace, and schema.
This method is only required if you want to expose your SOAP web service WSDL to be accessible with a URL.
For normal functioning of web service, this is not require.

We will also need to define a XsdSchema bean in order to reference the XSD file that we defined earlier.
C. getSchema()
Creates a schema bean that refers to the XSD file.

In this case, the XSD file is located on the classpath and is using Spring’s ClassPathResource class. The xsd file should be located in src/main/resources folder.

You can use other ways to get the XSD file, for example, you can use UrlResource to get the XSD file from an external location or you can use ByteArrayResource to get the XSD file from a byte array.

Main class
If you are creating a spring boot project, then here is the main class for running the application

@SpringBootApplication
public class WebServicesApplication {

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

Once you run the application, you can access the WSDL file at URL http://localhost:8080/ws/square.wsdl, assuming that the application is running on port 8080 and the URL pattern configured in configuration class is /ws/.
Also, the name of wsdl file is the name of bean defined over getWSDLDefinition() method in configuration class.
Request and Response
Below is a sample SOAP XML request, that this example has been tested with

<soap:Envelope 
      xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Header/>
   <soap:Body>
      <squareNumberRequest xmlns="http://codippa.com/ws/soap">
         <number>5</number>
      </squareNumberRequest>
   </soap:Body>
</soap:Envelope>

You can send this request using SOAPUI or Postman.
The URL, that the request will be sent to http://localhost:8080/ws.

Below is the sample response that you would get

<SOAP-ENV:Envelope 
       xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header/>
    <SOAP-ENV:Body>
        <ns3:squareNumberResponse xmlns:ns3="http://codippa.com/ws/soap">
            <number>25</number>
        </ns3:squareNumberResponse>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Common Errors
While creating the application, you can get below errors.
Try with the solutions given below these and it should work.

1. java.lang.ClassNotFoundException: javax.wsdl.extensions.ExtensibilityElement
Check if wsdl4j dependency if added and wsdl4j jar is present on the classpath.

2. No adapter for endpoint Is your endpoint annotated with @Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
There are multiple reasons of this error. Please ensure below points in the application.

A. The @Endpoint class is not being added to the Spring context.
B. The endpoint URL is incorrect.
Make sure that the endpoint URL you are using in the @PayloadRoot annotation matches the URL of your web service, including the correct namespace and local part.
C. The endpoint class is not annotated with @Endpoint.
Make sure that the class that you are using as the endpoint is annotated with @Endpoint.
D. The spring-ws-core dependency is missing.
Make sure that the spring-ws-core dependency is added to your project’s classpath.
E. The endpoint class is in a different package.
Make sure that the endpoint class is in the same package or a subpackage of the configuration class.
F. The endpoint class is not public: Make sure that the endpoint class is public.
G. Appropriate annotations are added over request and response classes.

Hope the article was helpful