This guide walks you through the process of consuming a SOAP-based web service with Spring.

What You Will Build

You will build a client that fetches country data data from a remote, WSDL-based web service by using SOAP. You can find out more about the country service and run the service yourself by following this guide.

The service provides country data. You will be able to query data about a country based on its name.

What You Need

How to complete this guide

Like most Spring Getting Started guides, you can start from scratch and complete each step or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.

To start from scratch, move on to Starting with Spring Initializr.

To skip the basics, do the following:

When you finish, you can check your results against the code in gs-consuming-web-service/complete.

If you read Producing a SOAP web service, you might wonder why this guide does not use spring-boot-starter-ws? That Spring Boot starter is only for server-side web services. That starter brings on board such things as embedded Tomcat, which is not needed to make a web call.

Run the Target Web Service Locally

Follow the steps in the companion guide or clone the repository and run the service (for example, by using mvn spring-boot:run) from its complete directory. You can verify that it works by visiting http://localhost:8080/ws/countries.wsdl in your browser.

Starting with Spring Initializr

For all Spring applications, you should start with the Spring Initializr. The Initializr offers a fast way to pull in all the dependencies you need for an application and does a lot of the setup for you. This example needs only the Spring Web Services dependency. The following image shows the Initializr set up for this sample project:

initializr
The preceding image shows the Initializr with Maven chosen as the build tool. You can also use Gradle. It also shows values of com.example and consuming-web-service as the Group and Artifact, respectively. You will use those values throughout the rest of this sample.

The following listing shows the pom.xml file that is created when you choose Maven:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>consuming-web-service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>consuming-web-service</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

The following listing shows the build.gradle file that is created when you choose Gradle:

plugins {
	id 'org.springframework.boot' version '2.2.0.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

Modify the Build Files

The build files created by the Spring Initializr need quite a bit of work for this guide. Also, the modifications to pom.xml (for Maven) and build.gradle (for Gradle) differ substantially.

Maven

For Maven, you need to add a dependency, a profile, and a WSDL generation plugin.

The following listing shows the dependency you need to add in Maven:

The following listing shows the profile you need to add in Maven:

Note that the profile also lets this guide work with Java 11.

The Generate Domain Objects Based on a WSDL section describes the WSDL generation plugin.

The following listing shows the final pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>consuming-web-service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>consuming-web-service</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<!-- tag::dependency -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web-services</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-tomcat</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<!-- end::dependency -->

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<!-- tag::profile -->
	<profiles>
		<profile>
			<id>java11</id>
			<activation>
				<jdk>[11,)</jdk>
			</activation>

			<dependencies>
				<dependency>
					<groupId>org.glassfish.jaxb</groupId>
					<artifactId>jaxb-runtime</artifactId>
				</dependency>
			</dependencies>
		</profile>
	</profiles>
	<!-- end::profile -->

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<!-- tag::wsdl[] -->
			<plugin>
					<groupId>org.jvnet.jaxb2.maven2</groupId>
					<artifactId>maven-jaxb2-plugin</artifactId>
					<version>0.14.0</version>
					<executions>
						<execution>
							<goals>
								<goal>generate</goal>
							</goals>
						</execution>
					</executions>
					<configuration>
						<schemaLanguage>WSDL</schemaLanguage>
						<generatePackage>com.example.consumingwebservice.wsdl</generatePackage>
						<schemas>
							<schema>
								<url>http://localhost:8080/ws/countries.wsdl</url>
							</schema>
						</schemas>
					</configuration>
			</plugin>
			<!-- end::wsdl[] -->
		</plugins>
	</build>

</project>

Gradle

For Gradle, you need to add a dependency, a configuration, a bootJar section, and a WSDL generation plugin.

The following listing shows the dependency you need to add in Gradle:

implementation ('org.springframework.boot:spring-boot-starter-web-services') {
	exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
implementation 'org.springframework.ws:spring-ws-core'
compile(files(genJaxb.classesDir).builtBy(genJaxb))

jaxb "com.sun.xml.bind:jaxb-xjc:2.1.7"

Note the exclusion of Tomcat. If Tomcat is allowed to run in this build, you get a port collision with the Tomcat instance that provides the country data.

The following listing shows the configuration you need to add in Gradle:

The following listing shows the bootJar section you need to add in Gradle:

bootJar {
	baseName = 'gs-consuming-web-service'
	version =  '0.0.1'

	from genJaxb.classesDir
}

The Generate Domain Objects Based on a WSDL section describes the WSDL generation plugin.

The following listing shows the final build.gradle file:

plugins {
	id 'org.springframework.boot' version '2.2.0.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

// tag::configurations[]
configurations {
	jaxb
}
// end::configurations[]

repositories {
	mavenCentral()
}

// tag::wsdl[]
task genJaxb {
	ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
	ext.classesDir = "${buildDir}/classes/jaxb"
	ext.schema = "http://localhost:8080/ws/countries.wsdl"

	outputs.dir classesDir

	doLast() {
		project.ant {
			taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",
					classpath: configurations.jaxb.asPath
			mkdir(dir: sourcesDir)
			mkdir(dir: classesDir)

				xjc(destdir: sourcesDir, schema: schema,
						package: "com.example.consumingwebservice.wsdl") {
						arg(value: "-wsdl")
					produces(dir: sourcesDir, includes: "**/*.java")
				}

				javac(destdir: classesDir, source: 1.8, target: 1.8, debug: true,
						debugLevel: "lines,vars,source",
						classpath: configurations.jaxb.asPath) {
					src(path: sourcesDir)
					include(name: "**/*.java")
					include(name: "*.java")
					}

				copy(todir: classesDir) {
						fileset(dir: sourcesDir, erroronmissingdir: false) {
						exclude(name: "**/*.java")
				}
			}
		}
	}
}
// end::wsdl[]

dependencies {
// tag::dependency[]
	implementation ('org.springframework.boot:spring-boot-starter-web-services') {
		exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
	}
	implementation 'org.springframework.ws:spring-ws-core'
	compile(files(genJaxb.classesDir).builtBy(genJaxb))

	jaxb "com.sun.xml.bind:jaxb-xjc:2.1.7"
// end::dependency[]
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

// tag::bootjar[]
bootJar {
	baseName = 'gs-consuming-web-service'
	version =  '0.0.1'

	from genJaxb.classesDir
}
// end::bootjar[]

Generate Domain Objects Based on a WSDL

The interface to a SOAP web service is captured in WSDL. JAXB provides a way to generate Java classes from WSDL (or rather, the XSD contained in the <Types/> section of the WSDL). You can find the WSDL for the country service at http://localhost:8080/ws/countries.wsdl.

To generate Java classes from the WSDL in Maven, you need the following plugin setup:

<plugin>
		<groupId>org.jvnet.jaxb2.maven2</groupId>
		<artifactId>maven-jaxb2-plugin</artifactId>
		<version>0.14.0</version>
		<executions>
			<execution>
				<goals>
					<goal>generate</goal>
				</goals>
			</execution>
		</executions>
		<configuration>
			<schemaLanguage>WSDL</schemaLanguage>
			<generatePackage>com.example.consumingwebservice.wsdl</generatePackage>
			<schemas>
				<schema>
					<url>http://localhost:8080/ws/countries.wsdl</url>
				</schema>
			</schemas>
		</configuration>
</plugin>

This setup will generate classes for the WSDL found at the specified URL, putting those classes in the com.example.consumingwebservice.wsdl package.

To do the same with Gradle, you will need the following in your build file:

task genJaxb {
  ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
  ext.classesDir = "${buildDir}/classes/jaxb"
  ext.schema = "http://localhost:8080/ws/countries.wsdl"

  outputs.dir classesDir

  doLast() {
    project.ant {
      taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",
          classpath: configurations.jaxb.asPath
      mkdir(dir: sourcesDir)
      mkdir(dir: classesDir)

        xjc(destdir: sourcesDir, schema: schema,
            package: "com.example.consumingwebservice.wsdl") {
            arg(value: "-wsdl")
          produces(dir: sourcesDir, includes: "**/*.java")
        }

        javac(destdir: classesDir, source: 1.8, target: 1.8, debug: true,
            debugLevel: "lines,vars,source",
            classpath: configurations.jaxb.asPath) {
          src(path: sourcesDir)
          include(name: "**/*.java")
          include(name: "*.java")
          }

        copy(todir: classesDir) {
            fileset(dir: sourcesDir, erroronmissingdir: false) {
            exclude(name: "**/*.java")
        }
      }
    }
  }
}

As Gradle does not (yet) have a JAXB plugin, it involves an Ant task, which makes it a bit more complex than in Maven.

In both cases, the JAXB domain object generation process has been wired into the build tool’s lifecycle, so you need not run any extra steps.

Create a Country Service Client

To create a web service client, you have to extend the WebServiceGatewaySupport class and code your operations, as the following example (from src/main/java/com/example/consumingwebservice/CountryClient.java) shows:

package com.example.consumingwebservice;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.soap.client.core.SoapActionCallback;

import com.example.consumingwebservice.wsdl.GetCountryRequest;
import com.example.consumingwebservice.wsdl.GetCountryResponse;

public class CountryClient extends WebServiceGatewaySupport {

  private static final Logger log = LoggerFactory.getLogger(CountryClient.class);

  public GetCountryResponse getCountry(String country) {

    GetCountryRequest request = new GetCountryRequest();
    request.setName(country);

    log.info("Requesting location for " + country);

    GetCountryResponse response = (GetCountryResponse) getWebServiceTemplate()
        .marshalSendAndReceive("http://localhost:8080/ws/countries", request,
            new SoapActionCallback(
                "http://spring.io/guides/gs-producing-web-service/GetCountryRequest"));

    return response;
  }

}

The client contains one method (getCountry) that does the actual SOAP exchange.

In this method, both the GetCountryRequest and the GetCountryResponse classes are derived from the WSDL and were generated in the JAXB generation process (described in Generate Domain Objects Based on a WSDL). It creates the GetCountryRequest request object and sets it up with the country parameter (the name of the country). After printing out the country name, it uses the WebServiceTemplate supplied by the WebServiceGatewaySupport base class to do the actual SOAP exchange. It passes the GetCountryRequest request object (as well as a SoapActionCallback to pass on a SOAPAction header with the request) as the WSDL described that it needed this header in the <soap:operation/> elements. It casts the response into a GetCountryResponse object, which is then returned.

Configuring Web Service Components

Spring WS uses Spring Framework’s OXM module, which has the Jaxb2Marshaller to serialize and deserialize XML requests, as the following example (from src/main/java/com/example/consumingwebservice/CountryConfiguration.java) shows:

package com.example.consumingwebservice;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

@Configuration
public class CountryConfiguration {

  @Bean
  public Jaxb2Marshaller marshaller() {
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    // this package must match the package in the <generatePackage> specified in
    // pom.xml
    marshaller.setContextPath("com.example.consumingwebservice.wsdl");
    return marshaller;
  }

  @Bean
  public CountryClient countryClient(Jaxb2Marshaller marshaller) {
    CountryClient client = new CountryClient();
    client.setDefaultUri("http://localhost:8080/ws");
    client.setMarshaller(marshaller);
    client.setUnmarshaller(marshaller);
    return client;
  }

}

The marshaller is pointed at the collection of generated domain objects and will use them to both serialize and deserialize between XML and POJOs.

The countryClient is created and configured with the URI of the country service shown earlier. It is also configured to use the JAXB marshaller.

Run the Application

This application is packaged up to run from the console and retrieve the data for a given country name, as the following listing (from src/main/java/com/example/consumingwebservice/ConsumingWebServiceApplication.java) shows:

package com.example.consumingwebservice;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.example.consumingwebservice.wsdl.GetCountryResponse;

@SpringBootApplication
public class ConsumingWebServiceApplication {

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

  @Bean
  CommandLineRunner lookup(CountryClient quoteClient) {
    return args -> {
      String country = "Spain";

      if (args.length > 0) {
        country = args[0];
      }
      GetCountryResponse response = quoteClient.getCountry(country);
      System.err.println(response.getCountry().getCurrency());
    };
  }

}

The main() method defers to the SpringApplication helper class, providing CountryConfiguration.class as an argument to its run() method. This tells Spring to read the annotation metadata from CountryConfiguration and to manage it as a component in the Spring application context.

This application is hard-coded to look up 'Spain'. Later in this guide, you will see how to enter a different symbol without editing the code.

Build an executable JAR

You can run the application from the command line with Gradle or Maven. You can also build a single executable JAR file that contains all the necessary dependencies, classes, and resources and run that. Building an executable jar so makes it easy to ship, version, and deploy the service as an application throughout the development lifecycle, across different environments, and so forth.

If you use Gradle, you can run the application by using ./gradlew bootRun. Alternatively, you can build the JAR file by using ./gradlew build and then run the JAR file, as follows:

java -jar build/libs/gs-consuming-web-service-0.1.0.jar

If you use Maven, you can run the application by using ./mvnw spring-boot:run. Alternatively, you can build the JAR file with ./mvnw clean package and then run the JAR file, as follows:

java -jar target/gs-consuming-web-service-0.1.0.jar
The steps described here create a runnable JAR. You can also build a classic WAR file.

Logging output is displayed. The service should be up and running within a few seconds.

The following listing shows the initial response:

Requesting country data for Spain

<getCountryRequest><name>Spain</name>...</getCountryRequest>

You can plug in a different country by running the following command:

java -jar build/libs/gs-consuming-web-service-0.1.0.jar Poland

Then the response changes to the following:

Requesting location for Poland

<getCountryRequest><name>Poland</name>...</getCountryRequest>

Summary

Congratulations! You have just developed a client to consume a SOAP-based web service with Spring.

See Also

The following guides may also be helpful:

Want to write a new guide or contribute to an existing one? Check out our contribution guidelines.

All guides are released with an ASLv2 license for the code, and an Attribution, NoDerivatives creative commons license for the writing.