Spring WebClient Timeout: Global vs. Request-Level

Spring WebClient is a powerful tool for making HTTP requests in a reactive way, and it provides flexible options for setting timeouts. This article discusses options to manage timeouts in Spring WebClient, both at a global level and for specific requests, with code examples.

1. Spring WebClient: Setting Timeouts Globally

Global timeouts are applied to all requests made with a specific WebClient instance. This is useful when we want to set a timeout for all requests to a particular service or API.

To set a global timeout, we must configure the timeouts at the WebClient instance level, and use this instance in all services. One of the most natural ways to configure timeouts at the WebClient level is configuring the underlying HTTP client. Spring provides built-in support for some HTTP client libraries, and the Reactor Netty is used by default.

In the following code, we are setting:

  • Connection timeout: to 10,000 milliseconds (10 seconds) using ChannelOption.CONNECT_TIMEOUT_MILLIS.
  • Read timeout: of 10 seconds using ReadTimeoutHandler class. This means if no data is received within 10 seconds of making a request, a ReadTimeoutException exception will be thrown.
  • Write timeout: of 10 seconds using WriteTimeoutHandler class. When a write operation cannot finish in the specified period of time, a WriteTimeoutException exception will be thrown.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.client.WebClient;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import reactor.netty.http.client.HttpClient;
 
@Configuration
@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer {  
   
  @Bean
  public WebClient getWebClient() {

    HttpClient httpClient = HttpClient.create()
      .tcpConfiguration(client ->
          client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
          .doOnConnected(conn -> conn
              .addHandler(new ReadTimeoutHandler(10, TimeUnit.SECONDS))
              .addHandlerLast(new WriteTimeoutHandler(10, TimeUnit.SECONDS))));
     
    ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient.wiretap(true));     
 
    return WebClient.builder()
      .baseUrl("http://localhost:3000")
      .clientConnector(connector)
      .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
      .build();
  }
}

2. Spring WebClient: Setting Timeouts at Request Level

There are situations where we may want different timeouts for different requests. We can set request-specific timeouts by chaining the timeout() method to the Mono or Flux returned by the WebClient request. Request-level timeouts override the global timeout settings, if any.

The timeout(Duration) method in both classes is overloaded with many options. Check out the latest Spring docs for Mono and Flux for more details.

In the following example, we set a timeout of 10 seconds specifically for the request made with uri("/employees"). In that situation, the TimeoutException is thrown in case no item is received within the given 10 seconds.

The timeout() method applies the timeout to the whole operation, from initiating the connection to receiving the response. It does not allow configuring feature-specific timeout settings as we set at HttpClient level.

public Flux<Employee> findAll() {

  return webClient.get()
    .uri("/employees")
    .retrieve()
    .bodyToFlux(Employee.class)
    .timeout(Duration.ofMillis(10_000));
}

public Mono<Employee> create(Employee empl) {

  return webClient.post()
    .uri("/employees")
    .body(Mono.just(empl), Employee.class)
    .retrieve()
    .bodyToMono(Employee.class)
    .timeout(Duration.ofMillis(10_000));
}

By using .onError() blocks, we can gracefully handle timeout exceptions and other specific exceptions that may occur during the WebClient requests while providing appropriate error-handling strategies.

Setting Mono.timeout() does not change the global timeout, so it will work only when the overridden timeout is shorter than global timeout set with HttpClient.

Alternatively, you can create a new instance of WebClient, by copying all settings fr globally configured WebClient, and then only resetting the HttpClient instance with new global timeouts.

HttpClient customHttpClient = ...; // With new timeouts

WebClient customizedWebClient = existingWebClient.mutate()
.clientConnector(new ReactorClientHttpConnector(customHttpClient))
.build();

3. Handling Timeout Exceptions

When a timeout occurs, the code throws an exception. We can handle these timeout exceptions by providing an error handler in the .subscribe() method and/or onError() blocks.

webClient.get()
  .uri("/employees")
  .retrieve()
  .bodyToFlux(Employee.class)
  .timeout(Duration.ofSeconds(10))
  .onErrorResume(ConnectTimeoutException.class, ex -> {
      // Handle ConnectTimeoutException here
      System.err.println("Connect timeout occurred: " + ex.getMessage());
      return Flux.empty();
  })
  .onErrorResume(ReadTimeoutException.class, ex -> {
      // Handle ReadTimeoutException here
      System.err.println("Read timeout occurred: " + ex.getMessage());
      return Flux.empty();
  })
  .subscribe(
      response -> {
          // Handle the successful response
          System.out.println("Response: " + response);
      },
      error -> {
          // Handle other errors, if any
          System.err.println("Unhandled error: " + error.getMessage());
      }
  );

4. Conclusion

Spring WebClient provides both global and request-specific timeout options to give us flexibility in handling different scenarios. By understanding and using these timeout settings effectively, we can improve the reliability of the reactive applications when making HTTP requests.

  • Global timeouts can be set using the HttpClient configuration on the WebClient instance and apply to all requests made with that instance.
  • Request-specific timeouts are set on a per-request basis using the timeout() method on the Mono or Flux returned by WebClient requests.
  • Handle timeouts in the error handler of the .subscribe() or .onError() blocks to implement appropriate error-handling strategies.

Drop me your questions related to setting timeouts in Spring 5 WebClient.

Happy Learning !!

Reference: Spring boot doc

Sourcecode Download

Comments

Subscribe
Notify of
guest
9 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode