Spring Boot WebClient Cheat Sheet

On the eve of the start of the course “Spring Framework Developer” prepared a traditional translation of useful material.

We also offer absolutely free to watch the recording of a demo lesson on the topic Introducing the Cloud, Creating a Cluster in Mongo DB Atlas


WebClient is a non-blocking, reactive client for making HTTP requests.

RestTemplate time has come to an end

You may have heard that the RestTemplate is running out of time. Now this is indicated in the official documentation:

NOTE: As of 5.0 this class is in maintenance mode, with only minor requests for changes and bugs to be accepted going forward. Please, consider using the org.springframework.web.reactive.client.WebClient which has a more modern API and supports sync, async, and streaming scenarios.

NOTE: Since version 5.0, this class has been mothballed and in the future only minor change requests and bug fixes will be accepted. Please consider using org.springframework.web.reactive.client.WebClientwhich has a more modern API and supports synchronous, asynchronous and streaming.

Of course, we understand that RestTemplate will not disappear instantly, but there will be no new functionality in it. For this reason, there are dozens of questions on the Internet about what a WebClient is and how to use it. This article provides tips for using the new library.

Differences Between WebClient and RestTemplate

In a nutshell, the main difference between these technologies is that the RestTemplate runs synchronously (blocking), while the WebClient runs asynchronously (not blocking).

RestTemplate – this synchronous a client for making HTTP requests, it provides a simple API with a templated method on top of basic HTTP libraries like HttpURLConnection (JDK), HttpComponents (Apache) and others.

Spring WebClient – this asynchronous, a reactive client for making HTTP requests, part of Spring WebFlux.

You are probably wondering how you can replace a synchronous client with an asynchronous one. WebClient has a solution to this problem. We’ll look at a few examples of using the WebClient.

Now is the time to say goodbye to RestTemplate, say thank you, and continue exploring the WebClient.

Getting Started with WebClient

Preconditions

Project preparation

Let’s create a basic project with dependencies using Spring initializr

Now let’s take a look at the dependencies of our project. The most important addiction for us is spring-boot-starter-webflux

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

Spring WebFlux is part of Spring 5 and provides reactive programming support for web applications.

It’s time to set up the WebClient.

Configuring WebClient

There are several ways to customize the WebClient. The first and easiest is to create it with the default settings.

WebClient client = WebClient.create();

You can also specify the base URL:

WebClient client = WebClient.create("http://base-url.com");

The third and most advanced option is to create a WebClient using the builder. We’ll be using a configuration that includes a base URL and timeouts.

@Configuration
public class WebClientConfiguration {
    private static final String BASE_URL = "https://jsonplaceholder.typicode.com";
    public static final int TIMEOUT = 1000;

    @Bean
    public WebClient webClientWithTimeout() {
        final var tcpClient = TcpClient
                .create()
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, TIMEOUT)
                .doOnConnected(connection -> {
                    connection.addHandlerLast(new ReadTimeoutHandler(TIMEOUT, TimeUnit.MILLISECONDS));
                    connection.addHandlerLast(new WriteTimeoutHandler(TIMEOUT, TimeUnit.MILLISECONDS));
                });

        return WebClient.builder()
                .baseUrl(BASE_URL)
                .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
                .build();
    }
}

Parameters supported by WebClient.Builder can be viewed here

Preparing a request with Spring WebClient

WebClient supports methods: get(), post(), put(), patch(), delete(), options() and head()

You can also specify the following parameters:

  • Path variables (path variables) and request parameters using the method uri()

  • Request headers using the method headers()

  • Cookies using the method cookies()

After specifying the parameters, you can execute the query using retrieve() or exchange()… Next, we convert the result to Mono using bodyToMono() or in Flux with bodyToFlux()

Asynchronous request

Let’s create a service that uses the WebClient bean and makes an asynchronous request.

@Service
@AllArgsConstructor
public class UserService {
    private final WebClient webClient;

    public Mono<User> getUserByIdAsync(final String id) {
        return webClient
                .get()
                .uri(String.join("", "/users/", id))
                .retrieve()
                .bodyToMono(User.class);
    }
}

As you can see, we don’t get the User model right away. Instead of User, we get Mono wrapper, with which we perform various actions. Let’s sign in a non-blocking way using subscribe()

userService
  .getUserByIdAsync("1")
  .subscribe(user -> log.info("Get user async: {}", user));

Execution continues immediately without blocking on the method subscribe()even if it takes some time to get the value.

Synchronous request

If you need a good old synchronous call, it can be easily done using the method block()

public User getUserByIdSync(final String id) {
    return webClient
            .get()
            .uri(String.join("", "/users/", id))
            .retrieve()
            .bodyToMono(User.class)
            .block();
}

Here, the thread is blocked until the request is completed. In this case, we get the requested model immediately after the completion of the method.

Retry attempts

We all know that a network call may not always be successful. But we can play it safe and in some cases perform it again. For this, the method is used retryWhen()which takes a class as an argument response.util.retry.Retry

public User getUserWithRetry(final String id) {
    return webClient
            .get()
            .uri(String.join("", "/users/", id))
            .retrieve()
            .bodyToMono(User.class)
            .retryWhen(Retry.fixedDelay(3, Duration.ofMillis(100)))
            .block();
}

With the help of the builder, you can configure the parameters and various repeat strategies (for example, exponential). If you need to try again successfully, then use repeatWhen() or repeatWhenEmpty() instead retryWhen()

Error processing

In the event of an error, where retrying does not help, we can still control the situation with the fallback option. The following methods are available:

  • doOnError() – fires when Mono exits with an error.

  • onErrorResume() – when an error occurs, subscribes to the fallback publisher, using the function to select an action based on the error.

You can use these functions to call another service, throw an exception, write to the log, or perform any action depending on the error.

public User getUserWithFallback(final String id) {
    return webClient
            .get()
            .uri(String.join("", "/broken-url/", id))
            .retrieve()
            .bodyToMono(User.class)
            .doOnError(error -> log.error("An error has occurred {}", error.getMessage()))
            .onErrorResume(error -> Mono.just(new User()))
            .block();
}

In some situations, it can be helpful to respond to a specific error code. To do this, you can use the method onStatus()

public User getUserWithErrorHandling(final String id) {
  return webClient
          .get()
          .uri(String.join("", "/broken-url/", id))
          .retrieve()
              .onStatus(HttpStatus::is4xxClientError,
                      error -> Mono.error(new RuntimeException("API not found")))
              .onStatus(HttpStatus::is5xxServerError,
                      error -> Mono.error(new RuntimeException("Server is not responding")))
          .bodyToMono(User.class)
          .block();
}

Client filters

To intercept and modify the request, you can configure filters through the WebClient builder.

WebClient.builder()
  .baseUrl(BASE_URL)
  .filter((request, next) -> next
          .exchange(ClientRequest.from(request)
                  .header("foo", "bar")
                  .build()))
  .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
  .build();

Below is an example of a filter for basic authentication using a static factory method.

WebClient.builder()
  .baseUrl(BASE_URL)
  .filter(basicAuthentication("user", "password")) // org.springframework.web.reactive.function.client.basicAuthentication()
  .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
  .build();

Conclusion

In this article, we learned how to configure the WebClient and make both synchronous and asynchronous HTTP requests. All code snippets mentioned in the article can be found in GitHub repos… The Spring WebClient documentation you can find here

To summarize, we can see that WebClient is easy to use and includes all the essential features required in modern programming.

Good luck with your new Spring WebClient!


Learn more about the course Spring Framework Developer.

Watch the demo lesson on Introducing the Cloud, Creating a Cluster in Mongo DB Atlas.

Similar Posts

Leave a Reply

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