Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:


1. Overview

In this tutorial, we're going to examine the WebClient, which is a reactive web client introduced in Spring 5.

We're also going to look at the WebTestClient, a WebClient designed to be used in tests.

Further reading:

Spring WebClient Filters

Learn about WebClient filters in Spring WebFlux

Spring WebClient Requests with Parameters

Learn how to reactively consume REST API endpoints with WebClient from Spring Webflux.

2. What Is the WebClient?

Simply put, WebClient is an interface representing the main entry point for performing web requests.

It was created as part of the Spring Web Reactive module, and will be replacing the classic RestTemplate in these scenarios. In addition, the new client is a reactive, non-blocking solution that works over the HTTP/1.1 protocol.

Finally, the interface has a single implementation, the DefaultWebClient class, which we'll be working with.

3. Dependencies

Since we are using a Spring Boot application, we need the spring-boot-starter-webflux dependency, as well as the Reactor project.

3.1. Building with Maven

Let's add the following dependencies to the pom.xml file:


3.2. Building with Gradle

With Gradle, we need to add the following entries to the build.gradle file:

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-webflux'
    compile 'org.projectreactor:reactor-spring:1.0.1.RELEASE'

4. Working with the WebClient

To work properly with the client, we need to know how to:

  • create an instance
  • make a request
  • handle the response

4.1. Creating a WebClient Instance

There are three options to choose from. The first one is creating a WebClient object with default settings:

WebClient client1 = WebClient.create();

The second option is to initiate a WebClient instance with a given base URI:

WebClient client2 = WebClient.create("http://localhost:8080");

The third option (and the most advanced one) is building a client by using the DefaultWebClientBuilder class, which allows full customization:

WebClient client3 = WebClient
    .defaultCookie("cookieKey", "cookieValue")
    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 
    .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080"))

4.2. Creating a WebClient Instance with Timeouts

Oftentimes, the default HTTP timeouts of 30 seconds are too slow for our needs, so let's see how to configure them for our WebClient instance.

The core class we use is TcpClient.

There we can set the connection timeout via the ChannelOption.CONNECT_TIMEOUT_MILLIS value. We can also set the read and write timeouts using a ReadTimeoutHandler and a WriteTimeoutHandler, respectively:

TcpClient tcpClient = TcpClient
  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
  .doOnConnected(connection -> {
      connection.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS));
      connection.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS));

WebClient client = WebClient.builder()
  .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))

Note that while we can call timeout on our client request as well, this is a signal timeout, not an HTTP connection, or read/write timeout; it's a timeout for the Mono/Flux publisher.

4.3. Preparing a Request

First we need to specify an HTTP method of a request by invoking method(HttpMethod method) or calling its shortcut methods such as get, post, and delete:

WebClient.UriSpec<WebClient.RequestBodySpec> request1 = client3.method(HttpMethod.POST);
WebClient.UriSpec<WebClient.RequestBodySpec> request2 =;

The next step is to provide a URL. We can pass it to the uri API as a String or a instance:

WebClient.RequestBodySpec uri1 = client3

WebClient.RequestBodySpec uri2 = client3

Then we can set a request body, content type, length, cookies, or headers if we need to.

For example, if we want to set a request body, there are two available ways: filling it with a BodyInserter or delegating this work to a Publisher:

WebClient.RequestHeadersSpec requestSpec1 = WebClient
  .body(BodyInserters.fromPublisher(Mono.just("data")), String.class);

WebClient.RequestHeadersSpec<?> requestSpec2 = WebClient

The BodyInserter is an interface responsible for populating a ReactiveHttpOutputMessage body with a given output message and a context used during the insertion. A Publisher is a reactive component that is in charge of providing a potentially unbounded number of sequenced elements.

The second way is the body method, which is a shortcut for the original body(BodyInserter inserter) method.

To alleviate the process of filling a BodyInserter, there is a BodyInserters class with a number of useful utility methods:

BodyInserter<Publisher<String>, ReactiveHttpOutputMessage> inserter1 = BodyInserters
  .fromPublisher(Subscriber::onComplete, String.class);

It is also possible with a MultiValueMap:

LinkedMultiValueMap map = new LinkedMultiValueMap();

map.add("key1", "value1");
map.add("key2", "value2");

BodyInserter<MultiValueMap, ClientHttpRequest> inserter2
 = BodyInserters.fromMultipartData(map);

Or by using a single object:

BodyInserter<Object, ReactiveHttpOutputMessage> inserter3
 = BodyInserters.fromObject(new Object());

After we set the body, we can set headers, cookies, and acceptable media types. Values will be added to those that have already been set when instantiating the client.

Also, there is additional support for the most commonly used headers like “If-None-Match”, “If-Modified-Since”, “Accept”, and “Accept-Charset”.

Here's an example of how these values can be used:

WebClient.ResponseSpec response1 = uri1
    .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)

4.4. Getting a Response

The final stage is sending the request and receiving a response. This can be done with either the exchange or the retrieve method.

These methods differ in return types; the exchange method provides a ClientResponse along with its status and headers, while the retrieve method is the shortest path to fetching a body directly:

String response2 =
String response3 = request2

It's important to pay attention to the bodyToMono method, which will throw a WebClientException if the status code is 4xx (client error) or 5xx (server error). We use the block method on Monos to subscribe and retrieve actual data that was sent with the response.

5. Working with the WebTestClient

The WebTestClient is the main entry point for testing WebFlux server endpoints. It has a very similar API to the WebClient, and it delegates most of the work to an internal WebClient instance focusing mainly on providing a test context. The DefaultWebTestClient class is a single interface implementation.

The client for testing can be bound to a real server or work with specific controllers or functions.

5.1. Binding to a Server

To complete end-to-end integration tests with actual requests to a running server, we can use the bindToServer method:

WebTestClient testClient = WebTestClient

5.2. Binding to a Router

We can test a particular RouterFunction by passing it to the bindToRouterFunction method:

RouterFunction function = RouterFunctions.route(
  request -> ServerResponse.ok().build()


5.3. Binding to a Web Handler

The same behavior can be achieved with the bindToWebHandler method, which takes a WebHandler instance:

WebHandler handler = exchange -> Mono.empty();

5.4. Binding to an Application Context

A more interesting situation occurs when we're using the bindToApplicationContext method. It takes an ApplicationContext and analyses the context for controller beans and @EnableWebFlux configurations.

If we inject an instance of the ApplicationContext, a simple code snippet may look like this:

private ApplicationContext context;

WebTestClient testClient = WebTestClient.bindToApplicationContext(context)

5.5. Binding to a Controller

A shorter approach would be providing an array of controllers we want to test by the bindToController method. Assuming we've got a Controller class and we injected it into a needed class, we can write:

private Controller controller;

WebTestClient testClient = WebTestClient.bindToController(controller).build();

5.6. Making a Request

After building a WebTestClient object, all following operations in the chain are going to be similar to the WebClient until the exchange method (one way to get a response), which provides the WebTestClient.ResponseSpec interface to work with useful methods like the expectStatus, expectBody, and expectHeader:

    .expectHeader().valueEquals("Content-Type", "application/json")

6. Conclusion

In this article, we explored the WebClient, a new enhanced Spring mechanism for making requests on the client-side.

We also looked at the benefits it provides by going through configuring the client, preparing the request, and processing the response.

All of the code snippets mentioned in the article can be found in our GitHub repository.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

Comments are closed on this article!