Spring SOAP Client
Introduction
SOAP (Simple Object Access Protocol) is a messaging protocol that allows programs running on disparate operating systems to communicate using HTTP and XML. While RESTful services have gained popularity, many enterprise systems still rely on SOAP-based services. Spring Web Services provides excellent support for creating SOAP clients that can consume these services.
In this guide, you'll learn how to build a Spring SOAP client that can communicate with SOAP web services. We'll cover the entire process from configuration to implementation, with practical examples to help you understand how to apply these concepts in real-world scenarios.
Prerequisites
Before diving into Spring SOAP clients, you should have:
- Basic knowledge of Spring Framework
- Familiarity with XML and XSD (XML Schema Definition)
- Java Development Kit (JDK) 8 or higher
- Maven or Gradle for dependency management
- An IDE like IntelliJ IDEA or Eclipse
Setting Up Your Project
Let's start by setting up a Spring Boot project with the necessary dependencies for Spring Web Services.
Maven Dependencies
Add the following dependencies to your pom.xml
file:
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Web Services -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<!-- JAXB API -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<!-- JAXB Runtime -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
Generating Java Classes from WSDL
To communicate with a SOAP service, we first need to generate Java classes from the Web Service Definition Language (WSDL) file. Maven provides a plugin for this:
<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>
<schemas>
<schema>
<url>http://www.example.com/weather/WeatherService.wsdl</url>
</schema>
</schemas>
<generatePackage>com.example.weather.wsdl</generatePackage>
</configuration>
</plugin>
Replace the URL with the actual WSDL endpoint of the service you want to consume.
Creating a SOAP Client
Now, let's create a simple SOAP client to consume a weather service. We'll use a fictional weather service for this example.
Step 1: Configure WebServiceTemplate
The WebServiceTemplate
is the core class for client-side SOAP access in Spring WS. First, let's configure it:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.WebServiceTemplate;
@Configuration
public class WeatherClientConfig {
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
// Set the package name where our generated JAXB classes are
marshaller.setContextPath("com.example.weather.wsdl");
return marshaller;
}
@Bean
public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller) {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setMarshaller(marshaller);
webServiceTemplate.setUnmarshaller(marshaller);
webServiceTemplate.setDefaultUri("http://www.example.com/weather");
return webServiceTemplate;
}
}
The Jaxb2Marshaller
handles the conversion between Java objects and XML. The contextPath
should point to the package where your generated JAXB classes are located.
Step 2: Create the Client Class
Now, let's create our weather client class that will use WebServiceTemplate
to make SOAP calls:
import com.example.weather.wsdl.GetWeatherRequest;
import com.example.weather.wsdl.GetWeatherResponse;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.stereotype.Component;
@Component
public class WeatherClient {
private final WebServiceTemplate webServiceTemplate;
public WeatherClient(WebServiceTemplate webServiceTemplate) {
this.webServiceTemplate = webServiceTemplate;
}
public GetWeatherResponse getWeather(String city, String country) {
GetWeatherRequest request = new GetWeatherRequest();
request.setCity(city);
request.setCountry(country);
// Call the SOAP service
GetWeatherResponse response = (GetWeatherResponse) webServiceTemplate.marshalSendAndReceive(request);
return response;
}
}
Step 3: Using the Client
Let's create a simple service class that uses our WeatherClient:
import com.example.weather.wsdl.GetWeatherResponse;
import com.example.weather.wsdl.Weather;
import org.springframework.stereotype.Service;
@Service
public class WeatherService {
private final WeatherClient weatherClient;
public WeatherService(WeatherClient weatherClient) {
this.weatherClient = weatherClient;
}
public String getCurrentTemperature(String city, String country) {
GetWeatherResponse response = weatherClient.getWeather(city, country);
Weather weather = response.getWeather();
return String.format("The current temperature in %s, %s is %s°C with %s",
city, country, weather.getTemperature(), weather.getCondition());
}
}
Adding Error Handling
SOAP clients should handle errors gracefully. Let's enhance our client with error handling:
import com.example.weather.wsdl.GetWeatherRequest;
import com.example.weather.wsdl.GetWeatherResponse;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.soap.client.SoapFaultClientException;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class WeatherClient {
private static final Logger logger = LoggerFactory.getLogger(WeatherClient.class);
private final WebServiceTemplate webServiceTemplate;
public WeatherClient(WebServiceTemplate webServiceTemplate) {
this.webServiceTemplate = webServiceTemplate;
}
public GetWeatherResponse getWeather(String city, String country) {
GetWeatherRequest request = new GetWeatherRequest();
request.setCity(city);
request.setCountry(country);
try {
// Call the SOAP service
GetWeatherResponse response = (GetWeatherResponse) webServiceTemplate.marshalSendAndReceive(request);
return response;
} catch (SoapFaultClientException e) {
logger.error("SOAP fault: {}", e.getFaultStringOrReason());
throw new WeatherServiceException("Error fetching weather data: " + e.getFaultStringOrReason(), e);
} catch (Exception e) {
logger.error("Error during SOAP call", e);
throw new WeatherServiceException("Error fetching weather data", e);
}
}
}
Don't forget to create a custom exception class:
public class WeatherServiceException extends RuntimeException {
public WeatherServiceException(String message) {
super(message);
}
public WeatherServiceException(String message, Throwable cause) {
super(message, cause);
}
}
Customizing the SOAP Client
Let's look at some advanced customizations for your SOAP client.
Setting HTTP Authentication
Many SOAP services require authentication:
@Bean
public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller) {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setMarshaller(marshaller);
webServiceTemplate.setUnmarshaller(marshaller);
webServiceTemplate.setDefaultUri("http://www.example.com/weather");
// Add HTTP Basic Authentication
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
HttpClient httpClient = HttpClientBuilder.create()
.setDefaultCredentialsProvider(createCredentialsProvider())
.build();
((HttpComponentsClientHttpRequestFactory) requestFactory).setHttpClient(httpClient);
webServiceTemplate.setMessageSender(new HttpComponentsMessageSender(httpClient));
return webServiceTemplate;
}
private CredentialsProvider createCredentialsProvider() {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials("username", "password")
);
return credentialsProvider;
}
Don't forget to add the HttpComponents dependency:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
Setting Timeouts
For production systems, set appropriate timeouts:
@Bean
public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller) {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setMarshaller(marshaller);
webServiceTemplate.setUnmarshaller(marshaller);
webServiceTemplate.setDefaultUri("http://www.example.com/weather");
HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender();
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionTimeToLive(30, TimeUnit.SECONDS)
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectTimeout(5000)
.setSocketTimeout(5000)
.build())
.build();
messageSender.setHttpClient(httpClient);
webServiceTemplate.setMessageSender(messageSender);
return webServiceTemplate;
}
Real-World Example: Currency Conversion Service
Let's create a practical example using a public SOAP service for currency conversion:
// Configuration
@Configuration
public class CurrencyClientConfig {
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.example.currency.wsdl");
return marshaller;
}
@Bean
public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller) {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setMarshaller(marshaller);
webServiceTemplate.setUnmarshaller(marshaller);
webServiceTemplate.setDefaultUri("http://www.example.com/currency-service");
return webServiceTemplate;
}
}
// Client
@Component
public class CurrencyClient {
private final WebServiceTemplate webServiceTemplate;
public CurrencyClient(WebServiceTemplate webServiceTemplate) {
this.webServiceTemplate = webServiceTemplate;
}
public ConvertCurrencyResponse convertCurrency(String fromCurrency, String toCurrency, double amount) {
ConvertCurrencyRequest request = new ConvertCurrencyRequest();
request.setFromCurrency(fromCurrency);
request.setToCurrency(toCurrency);
request.setAmount(amount);
return (ConvertCurrencyResponse) webServiceTemplate.marshalSendAndReceive(request);
}
}
// Service
@Service
public class CurrencyService {
private final CurrencyClient currencyClient;
public CurrencyService(CurrencyClient currencyClient) {
this.currencyClient = currencyClient;
}
public String convertAmount(String from, String to, double amount) {
ConvertCurrencyResponse response = currencyClient.convertCurrency(from, to, amount);
return String.format("%.2f %s = %.2f %s (Rate: %.4f)",
amount, from, response.getConvertedAmount(), to, response.getExchangeRate());
}
}
// Controller
@RestController
@RequestMapping("/api/currency")
public class CurrencyController {
private final CurrencyService currencyService;
public CurrencyController(CurrencyService currencyService) {
this.currencyService = currencyService;
}
@GetMapping("/convert")
public String convertCurrency(
@RequestParam String from,
@RequestParam String to,
@RequestParam double amount) {
return currencyService.convertAmount(from, to, amount);
}
}
For an example response:
Request:
GET /api/currency/convert?from=USD&to=EUR&amount=100
Response:
100.00 USD = 85.20 EUR (Rate: 0.8520)
Testing Your SOAP Client
It's important to test your SOAP client. Spring provides a mock web service server for testing:
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.ws.test.client.MockWebServiceServer;
import org.springframework.xml.transform.StringSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.ws.test.client.RequestMatchers.payload;
import static org.springframework.ws.test.client.ResponseCreators.withPayload;
@SpringBootTest
public class WeatherClientTest {
@Autowired
private WeatherClient weatherClient;
@Autowired
private WebServiceTemplate webServiceTemplate;
private MockWebServiceServer mockServer;
@BeforeEach
public void setUp() {
mockServer = MockWebServiceServer.createServer(webServiceTemplate);
}
@Test
public void testGetWeather() {
// Expected request
String request =
"<getWeatherRequest>" +
"<city>New York</city>" +
"<country>USA</country>" +
"</getWeatherRequest>";
// Mock response
String response =
"<getWeatherResponse>" +
"<weather>" +
"<temperature>22</temperature>" +
"<condition>Sunny</condition>" +
"</weather>" +
"</getWeatherResponse>";
mockServer.expect(payload(new StringSource(request)))
.andRespond(withPayload(new StringSource(response)));
GetWeatherResponse weatherResponse = weatherClient.getWeather("New York", "USA");
assertThat(weatherResponse).isNotNull();
assertThat(weatherResponse.getWeather().getTemperature()).isEqualTo("22");
assertThat(weatherResponse.getWeather().getCondition()).isEqualTo("Sunny");
mockServer.verify();
}
}
Best Practices for SOAP Clients
- Set appropriate timeouts - Never use the default timeouts in production.
- Implement proper error handling - Catch and handle SOAP faults properly.
- Use logging - Log requests and responses for debugging, but be careful with sensitive information.
- Add circuit breakers - Use libraries like Resilience4j to handle service unavailability gracefully.
- Pool connections - For high-volume applications, use connection pooling.
- Secure credentials - Never hardcode credentials; use a secure configuration source.
Summary
In this guide, we've learned how to create a SOAP client using Spring Web Services. We've covered:
- Setting up the necessary dependencies
- Generating Java classes from WSDL
- Configuring WebServiceTemplate
- Creating a client class for SOAP service consumption
- Implementing error handling
- Customizing the SOAP client with authentication and timeouts
- Creating a real-world currency conversion example
- Testing SOAP clients using MockWebServiceServer
- Best practices for working with SOAP clients
By following these steps, you can build robust SOAP clients that effectively communicate with SOAP web services in various enterprise environments.
Additional Resources
- Spring Web Services Documentation
- WSDL 1.1 Specification
- SOAP 1.2 Specification
- Open Source SOAP Services for Practice
- Spring Web Services Github Repository
Exercises
- Create a SOAP client for a public weather service that displays the forecast for the next 5 days.
- Implement a retry mechanism using Spring Retry for your SOAP client.
- Build a simple web application that uses a SOAP client to convert currencies with a proper UI.
- Add a caching layer to your SOAP client to reduce API calls for frequently accessed data.
- Enhance your error handling to provide more specific error messages based on different SOAP faults.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)