Spring WebClient exchange vs. retrieve Comparison

Last Updated:  May 25, 2021 | Published: July 6, 2020

As the Spring Framework team won't add new features to the RestTemplate, the general recommendation is to start using the Spring WebFlux WebClient. Besides the reactive and non-blocking nature of the WebClient,  you can seamlessly include it to your existing (blocking) application. Apart from learning the basics about the reactive types Mono and Flux, it might be difficult to understand .retrieve() and .exchange() when using the Spring WebClient for the first time. With this blog post, I want to give you an overview of the Spring WebClient functions exchange and retrieve their differences, and when to use them.

TL;DR: Always try to use the WebClient .retrieve(). If you need more fine-grain control, use the .exchange() method but understand your additional responsibilities of always releasing the body.

UPDATE: As of Spring Framework 5.3, using .exchange() is deprecated due to potential memory and connection leaks. Prefer .exchangeToMono(), .exchangeToFlux(), or .retrieve() instead.

Spring Boot project setup for Spring WebClient

To compare both methods I'm using a sample Spring Boot application containing both the Web and WebFlux starter. In such scenarios where both Web Starters are available on the classpath, the autoconfiguration mechanism of Spring Boot will start the embedded Tomcat (non-reactive).

Nevertheless, the application can still use parts of WebFlux, but the overall execution is blocking. Hence all code examples use the WebClient in a non-reactive fashion. Everything further mentioned also applies to scenarios where you use the WebClient in a full-stack reactive application.

The application uses Java 11 and Spring Boot 2.3:

Spring WebClient configuration

First, let's configure a WebClient bean, that we'll use throughout the different examples.

Besides configuring the base URL, we're setting different timeouts while creating our WebClient bean. There is nothing worse than long-running operations in a distributed system. For this showcase, I'm using a placeholder REST API that allows us to fetch and create different entities.

The injected WebClient.Builder is autoconfigured by Spring Boot for us and in general good practice to use this for creating WebClient beans.

Furthermore, there is not different configuration for the WebClient when it comes to .retrieve() or .exchange(). With the bean definition above we can use both methods.

Fetching data with retrieve from Spring WebClient

Let's start with a trivial example: fetching data with HTTP GET.

Using .retrieve() we can achieve this operation with the following code:

The internal return type of .retrieve() is ResponseSpec which is part of the WebClient interface. The ResponseSpec offers several methods to proceed after sending the HTTP request:

  • wrapping the response body to reactive types using: .bodyToMono(), .bodyToFlux()
  • wrapping the response inside a Mono of the well-known (from using RestTemplate or your controller layer) ResponseEntity with: .toEntity(), .toEntityList(), .toBodilessEntity (
  • register callback handlers on different HTTP response status: .onStatus() on .rawStatus()

Here we are converting the HTTP response to the Jackson JsonNode class and with calling .block() await the response to use it in our non-reactive application.

Fetching data with exchange from Spring WebClient

The same operation looks like the following when using .exchange():

Until calling .exchange() everything is similar compared to using .retrieve(). The main difference is the return type of the .exchange() method. As a result, you have access to Mono<ClientResponse> instead of ResponseSpec.

Having this ClientResponse wrapped into a Mono allows us to use all available methods of Mono. In the example above I'm flattening the result and wrap the response to the exact data type like we did before.

The Javadoc of ClientResponse contains an important note saying:

NOTE: When using a ClientResponse through the WebClient exchange() method, you have to make sure that the body is consumed or released by using one of the following methods: body(BodyExtractor), bodyToMono(Class), …

So here we are warned to actively release the body of the HTTP response when using .exchange(). Not doing so can result in memory leaks as the Javadoc of .exchange() states the following:

NOTE: Unlike retrieve(), when using exchange(), it is the responsibility of the application to
consume any response content regardless of the scenario (success, error, unexpected data, etc). Not doing so can cause a memory leak. See ClientResponse for a list of all the available options for consuming the body

Apart from the body extraction function (similar to what's available on ResponseSpec): .bodyToMono(), .toEntity(), etc. we have additional functions to e.g. access the HTTP response headers. This allows you to check the HTTP response headers or the status code first, before deciding if and how to convert the body.

Accessing fields of the HTTP response

Let's add another example for both methods and see how we can react to different HTTP response status codes.

Using .retrieve(),  we can specify a function using .onStatus() to define the behavior of different error status codes. By default, every status code >= 400 (Bad Request) is internally mapped to a WebClientResponseException. Using the following setup, we can override this and specify our custom logic:

This allows us to only define the behavior for error status codes, as our provided function has to return Mono<? extends Throwable>. But what if we e.g. want to check that the response code is 201 (when creating entities) and add custom logic if not?

As we can extract the HTTP body to a ResponseEntity, we can write the following code for such scenarios:

In the scenario above we had to define the actual payload type (JsonNode) before we could access the HTTP header and the status code.

The same operation with .exchange() instead can look like the following:

Using .exchange() in this example frees us from defining the payload type first.

Summary of the exchange and retrieve comparison

The examples above should now give you a first impression of both methods and their differences. Basically .retrieve() is the method you should aim for using most of the time. The Spring WebClient exchange method provides more control than the retrieve method but makes you responsible for consuming the body in every scenario.

For most of your use cases .retrieve() should be your go-to solution and if you need access to headers and the status code, you can still convert the response to a ResponseEntity. A good reason to use .exchange() is to check the response status and headers before deciding how or if to consume the response.

Technically speaking, .retrieve() is a shortcut to using .exchange() and decoding the response body through ClientResponse, as the implementation of .retrieve() is the following:

Furthermore, there is no difference in preparing the HTTP request (e.g setting URL, HTTP method, or payload) when it comes to using both methods.

The Source code for these Spring WebClient examples is available on GitHub.

Have fun using both exchange and retrieve of the Spring WebClient,

Phil

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >