As developers, we often encounter errors and failures in our applications due to various reasons such as network issues, server downtime, or timeouts. Handling such errors gracefully is crucial for the user experience and the overall stability of the application. In this blog post, we will discuss the retry mechanism in Spring Boot, which allows us to retry failed operations automatically.
What is Retry Mechanism?
Retry is a common mechanism used in distributed systems to handle transient failures. A transient failure is an error that occurs temporarily and can be resolved by retrying the operation. The retry mechanism retries the failed operation until it succeeds or reaches the maximum number of attempts.
Spring Boot Retry is a mechanism that allows Spring Boot applications to automatically retry failed operations. It provides a simple and flexible way to handle errors and failures by automatically retrying the failed operations with different configurations until they succeed or reach a maximum number of attempts. It allows you to focus on the business logic of your application without worrying about the underlying infrastructure and error handling.
Types of Retries in Spring Boot
Spring Boot Retry is designed to work with any Spring-managed bean and supports various retry policies, including fixed, exponential, and random delays between retries. It also allows developers to customize the backoff and retry policies and provides a way to recover from failures using a recovery callback method.
Spring Boot provides several types of retries that we can use based on our application’s requirements. Let’s explore them in detail:
1. Simple Retry – SimpleRetryPolicy
Simple Retry is the most basic retry mechanism provided by Spring Boot. It retries the operation a fixed number of times with a fixed delay between each retry. We can configure the number of retries and delay using SimpleRetryPolicy.
Example:
@Retryable(retryFor= { Exception.class },maxAttempts=3,backoff= @Backoff(delay=1000))publicvoidmyMethod() {// Perform some operation
Simple Retry
In the above example, we have annotated the method with @Retryable, which indicates that this method should be retried if an exception is thrown. We have also configured the maximum number of attempts to 3 and a delay of 1000 milliseconds (1 second) between each retry.
Exponential Backoff Retry is an enhanced retry mechanism that increases the delay between each retry exponentially. This allows the system to recover from failures more gracefully. We can configure the initial delay, multiplier, and maximum delay using ExponentialBackOffPolicy.
In the above example, we have configured the maximum number of attempts to 5 and an initial delay of 2000 milliseconds (2 seconds). We have also specified a multiplier of 2, which means that the delay will be doubled after each retry. The maximum delay is set to 10000 milliseconds (10 seconds).
Random Backoff Retry is a retry mechanism that introduces some randomness in the delay between retries. This is useful when multiple instances of the same application are running, and we want to avoid retrying at the same time. We can configure the minimum and maximum delay using RandomBackOffPolicy.
In the above example, we have configured the maximum number of attempts to 3 and a delay of 500 milliseconds (5 seconds) for the first retry. We have also set the maximum delay to 10000 milliseconds (10 seconds) and enabled randomness in the delay between retries.
4. Custom Retry – CustomRetryPolicy
If none of the built-in retry mechanisms meet our requirements, we can create a custom retry policy. We can implement the RetryPolicy interface and provide our own logic for deciding whether to retry the operation or not.
This code is an implementation of the Spring Retry
RetryPolicy
interface to provide a custom retry policy for failed operations.
The
CustomRetryPolicy
constructor takes an integer value
maxAttempts
as the maximum number of times the operation can be retried. The
canRetry
method is overridden to check if the number of retry attempts made so far is less than the
maxAttempts
value. If it is, then the method returns
true
indicating that the operation can be retried. If the number of retry attempts exceeds the
maxAttempts
value, the method returns
false
, indicating that the operation should not be retried.
The
open
method is overridden to return a new instance of the
RetryContextSupport
class. This is done to initialize a new retry context for each retry attempt.
The
close
method is overridden, but left empty as it does not require any functionality in this example.
The
registerThrowable
method is overridden to register the exception thrown during the operation’s retry attempt with the retry context. This allows the Spring Retry framework to track the exceptions and decide if another retry attempt should be made.
Finally, the
toString
method is overridden to return a string representation of the
CustomRetryPolicy
class, which includes the
maxAttempts
value.
This code is a method that uses Spring RetryTemplate to execute a retryable operation of fetching movie details by movie ID from an external API.
The method takes a movieId parameter and returns a Movie object. It uses the execute method of RetryTemplate and passes a RetryCallback as a parameter to the execute method. The RetryCallback has two generic parameters: the return type of the retryable operation (Movie), and the exception type that may be thrown (RestClientException).
Within the RetryCallback’s doWithRetry method, a Movie object is assigned a null value. Then, a try-catch block is used to attempt to fetch the movie details by movie ID from an external API via the movieApiClient. If the movie details cannot be fetched due to an HTTP server error, an HTTP client error, or a resource access exception, an appropriate exception is thrown, which will be caught by the RetryTemplate and retried according to the retry policy.
The method returns the movie object fetched from the external API or null if the retry operation fails.
This code is a configuration class that defines a
RetryTemplate
bean with a custom retry policy and backoff policy.
The
RetryTemplate
is a Spring class that provides a way to execute operations that may fail and can be retried in case of failure. It allows customizing the retry behavior through a retry policy and a backoff policy.
The
retryTemplate
method annotated with
@Bean
creates and returns a
RetryTemplate
instance with a
CustomRetryPolicy
and a
FixedBackOffPolicy
. The
CustomRetryPolicy
is a custom retry policy that is defined in a separate class and takes the
maxAttempts
parameter from the application configuration. The
FixedBackOffPolicy
is a backoff policy that sets a fixed time interval between retries and takes the
backoffInMillis
parameter from the application configuration.
Finally, the
RetryTemplate
instance is returned and can be used to execute retryable operations by wrapping them in a
retryTemplate.execute()
call.
@Retryable(value= { Exception.class },maxAttempts=3,backoff= @Backoff(delay=5000))publicvoidmyMethod() {// Perform some operation@RecoverpublicvoidrecoverMethod(Exception e) {// Handle the exception and return some default response in case of non-void methods}
Recover Method
In the above example, we have annotated the method with
@Retryable
and specified the maximum number of attempts and the delay between retries. We have also defined a recover method using the
@Recover
annotation, which takes an Exception parameter to handle the exception that caused the operation to fail.
When all retry attempts fail, the
recoverMethod
will be called with the exception that caused the failure as the parameter. We can then handle the exception appropriately, such as logging the error, sending an alert, or taking some other corrective action.
Note that the recover method must have the same signature as the method being retried. If the method being retried has a return value, the recover method should also have the same return type.
Example with return value:
@Retryable(value= { Exception.class },maxAttempts=3,backoff= @Backoff(delay=5000))publicStringmyMethod() {// Perform some operation@RecoverpublicStringrecoverMethod(Exception e) {// Handle the exception and return some default valuereturn"recovery-value";
Recover Method with Return Type
In this example, both the myMethod and the recoverMethod have a return type of String.
Let’s say we have a movie recommendation service that relies on an external movie database API to fetch information about movies. Sometimes, due to network issues or temporary outages, the API may not be available, and our service may fail to fetch the data. In such cases, we can use the retry and recover mechanisms to automatically retry the operation and recover from failures.
Here’s how we can implement this in our movie recommendation service:
First of all, add the below-mentioned dependencies in pom.xml of the application.
The first dependency
org.springframework.retry:spring-retry
provides support for retrying operations that may fail due to transient faults or errors. It offers a set of configurable retry templates, policies, and backoff strategies to retry failed operations automatically.
The second dependency
org.springframework:spring-aspects
provides Aspect-Oriented Programming (AOP) support in Spring. It includes the necessary classes and APIs to enable AOP in Spring applications, which can be used with the retry support to intercept and retry method invocations automatically.
This code represents the main entry point of a Spring Boot application with retry enabled. The
@SpringBootApplication
annotation enables Spring Boot auto-configuration and component scanning. The
@EnableRetry
annotation enables the Spring Retry framework to provide retry capabilities to any method in the application where the
@Retryable
annotation is used.
importcom.bootcamptoprod.retry.entity.Movie;importcom.bootcamptoprod.retry.rest.client.MovieApiClient;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.retry.annotation.Backoff;importorg.springframework.retry.annotation.Recover;importorg.springframework.retry.annotation.Retryable;importorg.springframework.retry.support.RetryTemplate;importorg.springframework.stereotype.Service;importorg.springframework.web.client.HttpClientErrorException;importorg.springframework.web.client.HttpServerErrorException;importorg.springframework.web.client.ResourceAccessException;importorg.springframework.web.client.RestClientException;@ServicepublicclassMovieService { @AutowiredprivateMovieApiClientmovieApiClient; @Retryable(retryFor= {HttpServerErrorException.class, HttpClientErrorException.class, ResourceAccessException.class},maxAttempts=3,backoff= @Backoff(delay=5000))publicMoviegetMovieDetails(StringmovieId) throwsResourceAccessException {Moviemovie=null;try { movie =movieApiClient.getMovieDetails(movieId); } catch (HttpServerErrorExceptionhttpServerErrorException) {System.out.println("Received HTTP server error exception while fetching the movie details. Error Message: "+httpServerErrorException.getMessage());throw httpServerErrorException; } catch (HttpClientErrorExceptionhttpClientErrorException) {System.out.println("Received HTTP client error exception while fetching the movie details. Error Message: "+httpClientErrorException.getMessage());throw httpClientErrorException; } catch (ResourceAccessExceptionresourceAccessException) {System.out.println("Received Resource Access exception while fetching the movie details.");throw resourceAccessException;return movie; @RecoverpublicMovierecoverMovieDetails(RestClientExceptione) {// Log the errorSystem.out.println("Error fetching movie details: "+e.getMessage());// Return a default moviereturnnewMovie("0000", "Default movie", "Unknown", 0.0);}
Retryable & Recover Example
In this example, we have a
MovieService
that fetches movie details from an external movie API using the
MovieApiClient
class. We have annotated the
getMovieDetails
method with
@Retryable
and specified that it should be retried up to 3 times if a
HttpServerErrorException
or
HttpClientErrorException
or
ResourceAccessException
is thrown (which can occur when there is a network issue or the API is not available due to server or client errors). We have also used the
@Backoff
annotation to specify a delay of 5 seconds between retry attempts.
If all retry attempts fail, the
recoverMovieDetails
method will be called with the exception that caused the failure. In this method, we can log the error and return a default movie to the caller.
So, if the movie API is not available when the
getMovieDetails
method is called, the operation will be retried up to 3 times with a delay of 5 seconds between retry attempts. If all retry attempts fail, the
recoverMovieDetails
method will be called, which will log the error and return a default movie to the caller. In
recoverMovieDetails
method signature, we have specified the parent exception of
HttpServerErrorException
,
HttpClientErrorException
and
ResourceAccessException
class. You can specify the exact same exception as well which you want to handle gracefully.
This is just one example of how retry and recover can be used in a real-life scenario, specifically in a movie recommendation system. By using these mechanisms, we can make our recommendation system more resilient to failures and provide a better user experience to our users.
Retry mechanism is a powerful feature in Spring Boot that allows us to handle errors and failures gracefully. In this post, we have explored different types of retries and their configuration in Spring Boot. We have also provided examples and tips for using retries effectively in our applications. By using the retry mechanism, we can improve the user experience and the overall stability of our application.