Mastering WebClient Retries in Spring Boot 3: From Basics to Circuit Breaker

This article explains how to use Spring WebFlux's WebClient for HTTP calls, demonstrates basic retry, backoff, custom retry logic, jitter to avoid retry storms, and integrates circuit breakers with Resilience4j to build resilient Spring Boot 3 applications.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering WebClient Retries in Spring Boot 3: From Basics to Circuit Breaker

1. Introduction

Spring WebFlux provides WebClient for HTTP requests. It offers a reactive, non‑blocking API that can compose asynchronous logic without dealing with threads.

In distributed systems, network calls may fail; retry mechanisms improve resilience. RestTemplate and RestClient require custom interceptors or Spring Retry, while WebClient has built‑in reactive retry operators like retryWhen and RetryBackoffSpec.

2. Practical Examples

2.1 Basic Usage

WebClient does not retry by default. Use .retry(n) to retry n times after a failure.

HttpClient httpClient = HttpClient.create()
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000);
WebClient webClient = WebClient.builder()
    .baseUrl("http://localhost:9999")
    .clientConnector(new ReactorClientHttpConnector(httpClient))
    .build();

webClient.get()
    .uri("/api/query")
    .retrieve()
    .bodyToMono(String.class)
    .doOnError(err -> System.err.printf("Error: %s%n", err.getMessage()))
    .retry(2)
    .subscribe(System.out::println, error -> System.err.printf("Request failed: %s%n", error.getMessage()));

This retries immediately, which may overload the target service.

Basic retry example diagram
Basic retry example diagram

2.2 Adding Backoff

Use Retry.backoff with retryWhen to introduce a delay that can increase on each attempt.

webClient.get()
    .uri("/api/query")
    .retrieve()
    .bodyToMono(String.class)
    .doOnError(err -> System.err.printf("Error at %s: %s%n",
        DateTimeFormatter.ofPattern("mm:ss").format(LocalDateTime.now()), err.getMessage()))
    .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
    .subscribe(System.out::println, error -> System.err.printf("Request failed: %s%n", error.getMessage()));
Backoff retry diagram
Backoff retry diagram

2.3 Custom Retry Logic

Filter which status codes should be retried (e.g., retry only 5xx or timeout errors).

Retry retryStrategy = Retry.fixedDelay(2, Duration.ofMillis(500))
    .filter(throwable -> {
        if (throwable instanceof WebClientResponseException) {
            int status = ((WebClientResponseException) throwable).getStatusCode().value();
            return status >= 500;
        }
        return throwable instanceof WebClientRequestException;
    });

webClient.get()
    .uri("/api/query")
    .retrieve()
    .bodyToMono(String.class)
    .doOnError(err -> System.err.printf("Error at %s: %s%n",
        DateTimeFormatter.ofPattern("mm:ss").format(LocalDateTime.now()), err.getMessage()))
    .retryWhen(retryStrategy)
    .subscribe(System.out::println, error -> System.err.printf("Request failed: %s%n", error.getMessage()));
Custom retry filter diagram
Custom retry filter diagram

2.4 Avoiding Retry Storms

Combine backoff with jitter to randomize delays and prevent synchronized retries.

Retry retryWithJitter = Retry.backoff(4, Duration.ofMillis(500))
    .jitter(0.8)
    .filter(throwable -> {
        if (throwable instanceof WebClientResponseException) {
            int status = ((WebClientResponseException) throwable).getStatusCode().value();
            return status >= 500;
        }
        return throwable instanceof WebClientRequestException;
    });

webClient.get()
    .uri("/api/query")
    .retrieve()
    .bodyToMono(String.class)
    .doOnError(err -> System.err.printf("Error at %s: %s%n",
        DateTimeFormatter.ofPattern("mm:ss").format(LocalDateTime.now()), err.getMessage()))
    .retryWhen(retryWithJitter)
    .subscribe(System.out::println, error -> System.err.printf("Request failed: %s%n", error.getMessage()));
Retry with jitter diagram
Retry with jitter diagram

2.5 Fallback with Circuit Breaker

If retries fail, a circuit breaker can stop further calls. Spring Cloud Circuit Breaker or Resilience4j can be integrated.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
    <version>3.3.0</version>
</dependency>

Configuration example (YAML):

resilience4j:
  circuitbreaker:
    configs:
      order-service:
        minimum-number-of-calls: 1
        failure-rate-threshold: 10
        wait-duration-in-open-state: 10s

Sample code using ReactiveCircuitBreakerFactory:

private final ReactiveCircuitBreakerFactory<?, ?> rcbFactory;

public Mono<String> invoke() {
    return rcbFactory.create("order-service")
        .run(webClient.get()
            .uri("/api/query")
            .retrieve()
            .bodyToMono(String.class)
            .doOnError(err -> System.err.printf("Error at %s: %s%n",
                DateTimeFormatter.ofPattern("mm:ss").format(LocalDateTime.now()), err.getMessage())),
            ex -> Mono.just("fallback response"));
}
Circuit breaker result diagram
Circuit breaker result diagram

These patterns help build resilient Spring Boot 3 applications that handle transient failures gracefully.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring Bootretryreactivewebclientcircuit breakerResilience4j
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.