Spring WebFlux Retry Mechanism
Tags: retry, spring, webflux, java
Published: 2024-04-14 13:37:58

Microservices and other external dependencies are vulnerable to occasional failures brought on by temporary service outages, network problems, or temporary faults. These errors may only occur temporarily, and repeating the procedure again after a little break frequently results in a successful outcome. Retry mechanisms are designed to deal with these brief interruptions by repeatedly attempting to carry out a particular task until it completes or a predetermined number of retries are used up.

## Maven Dependencies

Import the most recent version of the maven repository’s spring-retry dependency. Since spring retry is AOP-based, you should also use the most recent version of spring-aspects.

```
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.11</version>
</dependency>
```

**1. The Basics of Retry in Spring WebFlux**

Retry and retryWhen are Spring WebFlux’s two main ways for building retry mechanisms, which help handle certain scenarios gracefully.

**1.1 Using retry Method:**

The retry method is a simple technique in which, in the event that the web client returns an error, the application retries the request a predetermined number of times. Let’s examine a straightforward illustration:

```
public Mono<Item> getItemData(String itemId) {
    return webClient.get()
        .uri("/getItemData", itemId)
        .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .bodyToMono(Item.class)
        .retrieve()
        .retry(3); // Here we retry for 3 Attempts
}
```
**1.2 Using retryWhenMethod:**

We can make use of the retryWhen method for a more dynamic and flexible retry strategy. We are able to construct a more complex retry logic with this way. As an illustration, consider this:

```
public Mono<Item> getItemData(String itemId) {
    return webClient.get()
        .uri("/getItemData", itemId)
        .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .bodyToMono(Item.class)
        .retrieve()
        .retryWhen(Retry.max(3)); // Retry up to 3 attempts
}
```

## Tailoring Retry Options

Let’s now customize retry techniques to better fit the unique requirements of the application on your blog. To build a more adaptable and responsive retry mechanism, we’ll utilize the backoff function inside **retryWhen**.

```
public Mono<Item> getData(String itemId) {
    return webClient.get()
        .uri("/getItemData", itemId)
        .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .retrieve()
        .bodyToMono(Item.class)
        .retryWhen(Retry
            .backoff(3, Duration.ofSeconds(1)) // Retry up to 3 times with a backoff of 1 second
            .maxBackoff(Duration.ofSeconds(5)) // Maximum backoff of 5 seconds
            .doBeforeRetry(retrySignal -> {
                // You can add a custom logic here before each retry if you prefer
            })
        );
}
```

## Filtering Errors and Handling Exhausted Retries

A retry attempt will now be made in the event of any service problems, including 4xx errors like 400:Bad Request or 401:Unauthorized.

Since the server response won’t change, it is obvious that we shouldn’t try again with such client problems. Let’s see how we may limit the use of the retry technique to certain types of faults.

```
public Mono<Item> getData(String itemId) {
    return webClient.get()
        .uri("/getItemData", itemId)
        .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .retrieve()
        .bodyToMono(Item.class)
        .retryWhen(Retry
            .backoff(3, Duration.ofSeconds(1)) // Retry up to 3 times with a backoff of 1 second
            .maxBackoff(Duration.ofSeconds(5)) // Maximum backoff of 5 seconds
            .doAfterRetry(retrySignal -> log.info("RETRY_ATTEMPTED | {} | Get Item Data Retry Attempted")) /
            .filter(throwable -> throwable instanceof WebClientResponseException
                && ((WebClientResponseException) throwable).getStatusCode().is5xxServerError()
                || throwable.getCause() instanceof TimeoutException)
            .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
                log.info("SERVICE_UNAVAILABLE | External Service failed to process after max retries");
                throw new GeneralStatusCodeException(ApiError.SERVICE_UNAVAILABLE);
            })
        );
}
```
You can customize the exception handling according to your requirements.

## Conclusion

This blog covered the use of the retry and retryWhen methods to add retries to a Spring WebFlux application. We first introduced a limit on the amount of times an operation might be attempted again. Next, we used and configured a variety of ways to add a delay between tries. Finally, we implemented retrying for specific errors and customizing the behaviour when all attempts have been exhausted.

Additionally, To enable Spring Retry in an application, we need to add the @EnableRetry annotation to the configuration class. (https://www.baeldung.com/spring-retry)

## References

https://www.baeldung.com/spring-webflux-retry

https://www.baeldung.com/spring-retry

## Github

https://github.com/tharindu1998/retry-mechanism-springwebflux