Table of Contents
Introduction
This tutorial will show you an example on Spring SOAP WebService producers using Gradle. In other words, how you can create and publish SOAP based webservice in Contract-first approach using Spring and Gradle. I will use here Spring Boot framework to create our SOAP based web service.
There are mainly two approaches to create the Webservice – Contract-first & Contract-last.
The Contract-first approach tells us to create first XSD/WSDL and then create end-point interface and implementation class.
The Contract-last approach tells us to create first end-point interface and implementation class then create WSDL file.
This example will show you mainly Spring SOAP Webservice Producers using Gradle, i.e., it will only publish or deploy the web service into the server.
Related Post:
Prerequisites
Java 1.8/1.9/11/16, Gradle 4.10.2/5.4.1/7.2/7.4, Spring Boot 1.5.9/2.1.6/2.6.4, wsdl 1.6.2/1.6.3, XJC 2.x
Project Setup
Create gradle project called spring-boot-soap-producer in your favorite IDE or tool.
Update the default generated build.gradle file to include required dependencies for our project.
Here you will see three different build scripts – one is with Spring Boot 2.6.4 version, one is with Spring Boot 2.1.6 version and another with Spring Boot 1.5.9 version.
Spring Boot 2.6.4
Here is the following build.gradle script that does not need any task to be created manually and it uses XJC plugin to generate the required Java (JAXB) classes from xsd (schema) file. Also remember the default schema location for XJC plugin is src/main/schema. You can change also and for this you can read the documentation. Also the JAXB classes will be generated in different package now.
I have used Java 11/16, Gradle 7.2/7.4 and XJC plugin 2.x for Spring Boot 2.6.4.
buildscript {
ext {
springBootVersion = '2.6.4'
}
repositories {
mavenCentral()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
}
}
plugins {
id 'java-library'
id 'org.springframework.boot' version "${springBootVersion}"
id 'org.unbroken-dome.xjc' version '2.0.0'
}
sourceCompatibility = 11
targetCompatibility = 11
sourceSets.main.java.srcDirs "src/generated-sources/java"
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web-services:${springBootVersion}")
implementation("wsdl4j:wsdl4j:1.6.3")
implementation 'javax.xml.bind:jaxb-api:2.3.0'
}
Spring Boot 2.1.6
For Spring Boot 2.1.6, I have used the following configurations:
buildscript {
ext {
springBootVersion = '2.1.6.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
sourceSets.main.java.srcDirs "src/generated-sources/java"
sourceSets.main.resources.excludes = ['user.xsd']
configurations {
jaxb
}
dependencies {
implementation('org.jvnet.mimepull:mimepull:1.9.11')
implementation("org.springframework.boot:spring-boot-starter-web-services:${springBootVersion}")
implementation("wsdl4j:wsdl4j:1.6.3")
jaxb (
'com.sun.xml.bind:jaxb-xjc:2.2.7',
'com.sun.xml.bind:jaxb-impl:2.2.7'
)
}
task jaxb {
System.setProperty('javax.xml.accessExternalSchema', 'all')
def jaxbTargetDir = file("src/generated-sources/java")
doLast {
jaxbTargetDir.mkdirs()
ant.taskdef(
name: 'xjc',
classname: 'com.sun.tools.xjc.XJCTask',
classpath: configurations.jaxb.asPath
)
ant.jaxbTargetDir = jaxbTargetDir
ant.xjc(
destdir: '${jaxbTargetDir}',
package: 'com.roytuts.jaxb',
schema: 'src/main/resources/xsd/user.xsd'
)
}
}
compileJava.dependsOn jaxb
Spring Boot 1.5.9
For Spring Boot 1.5.9, I have used the following configurations:
buildscript {
ext {
springBootVersion = '1.5.9.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
sourceSets.main.java.srcDirs "src/generated-sources/java"
sourceSets.main.resources.excludes = ['user.xsd']
configurations {
jaxb
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web-services")
compile("wsdl4j:wsdl4j:1.6.2")
jaxb (
'com.sun.xml.bind:jaxb-xjc:2.2.7',
'com.sun.xml.bind:jaxb-impl:2.2.7'
)
}
task jaxb {
System.setProperty('javax.xml.accessExternalSchema', 'all')
def jaxbTargetDir = file("src/generated-sources/java")
doLast {
jaxbTargetDir.mkdirs()
ant.taskdef(
name: 'xjc',
classname: 'com.sun.tools.xjc.XJCTask',
classpath: configurations.jaxb.asPath
)
ant.jaxbTargetDir = jaxbTargetDir
ant.xjc(
destdir: '${jaxbTargetDir}',
package: 'com.roytuts.jaxb',
schema: 'src/main/resources/xsd/user.xsd'
)
}
}
compileJava.dependsOn jaxb
In the above build scripts we have defined Ant task to generate necessary Java classes from XSD file.
The right approach to generate Java classes is do this automatically during build time using a gradle plugin. For this you need to write such Ant task into build.gradle file. As gradle does not have a JAXB plugin (yet), it involves an ant task, which makes it a bit more complex than in maven.
In the above Ant task we have specified the location of XSD file (user.xsd), we have specified the location of generated Java classes along with other dependencies for JAXB.
Spring Boot Main Class
Creating a main would be sufficient to deploy our application into the Tomcat server. This is a great advantage that you just need to let Spring know that it is your Spring Boot Application using @SpringBootApplication
in main class.
You should be able to build the blank project. Please ensure that the overall state is “BUILD SUCCESS” before continuing.
Note: You won’t be able to import Spring Boot dependencies or @SpringBootApplication
until your project downloads all dependencies. So first create main class with empty main method and later when your project is successfully built then you can import required dependencies.
package com.roytuts.spring.boot.soap.producer.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.roytuts.spring.boot.soap.producer")
public class SpringSoapProducerApp {
public static void main(String[] args) {
SpringApplication.run(SpringSoapProducerApp.class, args);
}
}
Execute command – gradle clean build
on the project root directory from cmd prompt.
You will see the required jar files get downloaded and finally you would get “BUILD SUCCESSFUL” message.
For Spring Boot 2.6.4 version I have not used scanBasePackages as this main class has been kept under base package already.
package com.roytuts.spring.boot.soap.producer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringSoapProducerApp {
public static void main(String[] args) {
SpringApplication.run(SpringSoapProducerApp.class, args);
}
}
XSD Schema
Create an XSD (schema definition) file called user.xsd under src/main/resources/xsd directory.
Remember the following user.xsd schema has been kept under the src/main/schema folder while using Spring Boot 2.6.4 and XJC plugin 2.x.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="https://roytuts.com/UserService"
targetNamespace="https://roytuts.com/UserService"
elementFormDefault="qualified">
<xs:element name="getUserDetailsRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="getUserDetailsResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="users" type="tns:user" minOccurs="0"
maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="user">
<xs:sequence>
<xs:element name="id" type="xs:int" />
<xs:element name="name" type="xs:string" />
<xs:element name="email" type="xs:string" />
<xs:element name="address" type="tns:address" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="address">
<xs:sequence>
<xs:element name="street" type="xs:string" />
<xs:element name="city" type="xs:string" />
<xs:element name="state" type="xs:string" />
<xs:element name="zip" type="xs:int" />
<xs:element name="country" type="xs:string" />
<xs:element name="addressType" type="tns:addressType" />
</xs:sequence>
</xs:complexType>
<xs:simpleType name="addressType">
<xs:restriction base="xs:string">
<xs:enumeration value="PERMANENT" />
<xs:enumeration value="COMMUNICATION" />
<xs:enumeration value="OFFICIAL" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
In the above XSD we have basically two main elements – getUserDetailsRequest
and getUserDetailsResponse
. So getUserDetailsRequest
will act as an input and getUserDetailsResponse
will act as an output based on input.
Repository
In order to provide data to the web service, create a user repository.
In this guide we will create a dummy user repository implementation with dummy data.
package com.roytuts.spring.boot.soap.producer.repository;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import com.roytuts.jaxb.Address;
import com.roytuts.jaxb.AddressType;
import com.roytuts.jaxb.User;
@Component
public class UserRepository {
private List<User> users = new ArrayList<User>();
public UserRepository() {
User u1 = new User();
u1.setId(1);
u1.setName("Sumit Ghosh");
u1.setEmail("sumit.ghosh@email.com");
Address a1 = new Address();
a1.setStreet("Garfa");
a1.setCity("Kolkata");
a1.setState("WB");
a1.setCountry("India");
a1.setZip(700030);
a1.setAddressType(AddressType.COMMUNICATION);
u1.setAddress(a1);
User u2 = new User();
u2.setId(2);
u2.setName("Loku Poddar");
u2.setEmail("debabrata.poddar@email.com");
Address a2 = new Address();
a2.setStreet("Birati");
a2.setCity("Kolkata");
a2.setState("WB");
a2.setCountry("India");
a2.setZip(700130);
a2.setAddressType(AddressType.COMMUNICATION);
u2.setAddress(a2);
User u3 = new User();
u3.setId(3);
u3.setName("Souvik Sanyal");
u3.setEmail("souvik.sanyal@email.com");
Address a3 = new Address();
a3.setStreet("Kalighat");
a3.setCity("Kolkata");
a3.setState("WB");
a3.setCountry("India");
a3.setZip(700150);
a3.setAddressType(AddressType.COMMUNICATION);
u3.setAddress(a3);
User u4 = new User();
u4.setId(4);
u4.setName("Liton Sarkar");
u4.setEmail("liton.sarkar@email.com");
Address a4 = new Address();
a4.setStreet("Sukanta Nagar");
a4.setCity("Kolkata");
a4.setState("WB");
a4.setCountry("India");
a4.setZip(700098);
a4.setAddressType(AddressType.COMMUNICATION);
u4.setAddress(a4);
User u5 = new User();
u5.setId(5);
u5.setName("Rushikesh Mukund Fanse");
u5.setEmail("rushikesh.fanse@email.com");
Address a5 = new Address();
a5.setStreet("Nasik");
a5.setCity("Mumbai");
a5.setState("MH");
a5.setCountry("India");
a5.setZip(400091);
a5.setAddressType(AddressType.COMMUNICATION);
u5.setAddress(a5);
users.add(u1);
users.add(u2);
users.add(u3);
users.add(u4);
users.add(u5);
}
public List<User> getUsers(String name) {
List<User> userList = new ArrayList<>();
for (User user : users) {
if (user.getName().toLowerCase().contains(name.toLowerCase())) {
userList.add(user);
}
}
return userList;
}
}
Service Endpoint
To create a service endpoint, we only need a POJO with a few Spring WS annotations to handle the incoming SOAP requests.
@Endpoint
registers the class with Spring WS as a potential candidate for processing incoming SOAP messages.
@PayloadRoot
is then used by Spring WS to pick the handler method based on the message’s namespace and localPart.
@RequestPayload
indicates that the incoming message will be mapped to the method’s request parameter.
The @ResponsePayload
annotation makes Spring WS map the returned value to the response payload.
package com.roytuts.spring.boot.soap.producer.endpoint;
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;
import com.roytuts.jaxb.GetUserDetailsRequest;
import com.roytuts.jaxb.GetUserDetailsResponse;
import com.roytuts.spring.boot.soap.producer.repository.UserRepository;
@Endpoint
public class UserServiceEndpoint {
private final String NAMESPACE = "https://roytuts.com/UserService";
@Autowired
private UserRepository userRepository;
@PayloadRoot(namespace = NAMESPACE, localPart = "getUserDetailsRequest")
@ResponsePayload
public GetUserDetailsResponse getUser(@RequestPayload final GetUserDetailsRequest request) {
GetUserDetailsResponse response = new GetUserDetailsResponse();
response.getUsers().addAll(userRepository.getUsers(request.getName()));
return response;
}
}
Configuring SOAP Service
Create a new class with Spring Web Service related bean configuration.
Spring WS uses a different servlet type for handling SOAP messages: MessageDispatcherServlet
. It is important to inject and set ApplicationContext
to MessageDispatcherServlet
. Without that, Spring WS will not detect Spring beans automatically.
By naming this bean messageDispatcherServlet
, it does not replace Spring Boot’s default DispatcherServlet
bean.
DefaultMethodEndpointAdapter
configures annotation driven Spring WS programming model. This makes it possible to use the various annotations like @Endpoint
mentioned earlier.
DefaultWsdl11Definition
exposes a standard WSDL 1.1 using XsdSchema.
package com.roytuts.spring.boot.soap.producer.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 SoapWebServiceConfig extends WsConfigurerAdapter {
@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
@Bean(name = "users")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("UserPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("https://roytuts.com/UserService");
wsdl11Definition.setSchema(countriesSchema);
return wsdl11Definition;
}
@Bean
public XsdSchema helloSchema() {
return new SimpleXsdSchema(new ClassPathResource("xsd/user.xsd"));
}
}
It’s important to notice that we need to specify bean names for MessageDispatcherServlet and DefaultWsdl11Definition. Bean names determine the URL under which web service and the generated WSDL file is available. In this case, the WSDL will be available under http://<host>:<port>/ws/user.wsdl
.
For Spring Boot 2.6.4 as I have used XJC plugin, so the bean definition for helloSchema() has been changed as follows:
@Bean
public XsdSchema helloSchema() {
try {
return new SimpleXsdSchema(
new InputStreamResource(new FileInputStream(new File("src/main/schema/user.xsd"))));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
Changing Server Port
We don’t want tomcat server in spring boot application to be started on random port, so set the server port in src/main/resources/application.properties.
server.port=9999
Running SOAP Application
Run the main class that will deploy the application into embedded Tomcat server.
Testing SOAP Application
Open SOAPUI and use the WSDL URL as http://localhost:9999/ws/users.wsdl
and Endpoint as http://localhost:9999/ws
.
You can also use Postman to test the service.
Request Method – POST
Request URL – http://localhost:9999/ws
Content-type – text/xml
Request Body:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:user="https://roytuts.com/UserService">
<soapenv:Header/>
<soapenv:Body>
<user:getUserDetailsRequest>
<user:name>l</user:name>
</user:getUserDetailsRequest>
</soapenv:Body>
</soapenv:Envelope>
Response
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns2:getUserDetailsResponse xmlns:ns2="https://roytuts.com/UserService">
<ns2:users>
<ns2:id>2</ns2:id>
<ns2:name>Loku Poddar</ns2:name>
<ns2:email>debabrata.poddar@email.com</ns2:email>
<ns2:address>
<ns2:street>Birati</ns2:street>
<ns2:city>Kolkata</ns2:city>
<ns2:state>WB</ns2:state>
<ns2:zip>700130</ns2:zip>
<ns2:country>India</ns2:country>
<ns2:addressType>COMMUNICATION</ns2:addressType>
</ns2:address>
</ns2:users>
<ns2:users>
<ns2:id>3</ns2:id>
<ns2:name>Souvik Sanyal</ns2:name>
<ns2:email>souvik.sanyal@email.com</ns2:email>
<ns2:address>
<ns2:street>Kalighat</ns2:street>
<ns2:city>Kolkata</ns2:city>
<ns2:state>WB</ns2:state>
<ns2:zip>700150</ns2:zip>
<ns2:country>India</ns2:country>
<ns2:addressType>COMMUNICATION</ns2:addressType>
</ns2:address>
</ns2:users>
<ns2:users>
<ns2:id>4</ns2:id>
<ns2:name>Liton Sarkar</ns2:name>
<ns2:email>liton.sarkar@email.com</ns2:email>
<ns2:address>
<ns2:street>Sukanta Nagar</ns2:street>
<ns2:city>Kolkata</ns2:city>
<ns2:state>WB</ns2:state>
<ns2:zip>700098</ns2:zip>
<ns2:country>India</ns2:country>
<ns2:addressType>COMMUNICATION</ns2:addressType>
</ns2:address>
</ns2:users>
</ns2:getUserDetailsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Issue in JDK 9
If are using Java 9 and if you face issue java.lang.NoClassDefFoundError: javax/activation/DataSource or java.lang.Error: java.lang.reflect.InvocationTargetException then add below line to
gradle.properties
file and build the application.
org.gradle.jvmargs=--add-modules=java.xml.bind,java.activation
Now while refreshing project in Eclipse, if you face problem creating virtual machine then you can remove the above line from gradle.properties
after build and refresh the project in Eclipse.
That’s all. Hope you got idea on Spring SOAP WebService Producers using Gradle.