Spring SOAP WebService Producers Using Gradle

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 MethodPOST

Request URLhttp://localhost:9999/ws

Content-typetext/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.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *