@Retryable annotation
1. What is Spring Retry?
Spring Retry is a module that provides declarative retry support in Spring applications. It allows methods to be automatically re-invoked when they throw exceptions. Spring Retry provides a flexible API for retry policies, recovery logic, and handling exceptions.
Spring Dependency to add
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>2. What is @Retryable?
@Retryable is a declarative annotation provided by Spring Retry that can be applied to any method in a Spring-managed bean to automatically retry that method when an exception occurs. The annotation can be customized with several attributes to define the retry behavior, including:
backoff: Defines the backoff policy for retries (delay and multiplier).
exceptionExpression: A SpEL (Spring Expression Language) expression to determine if the retry should occur based on the exception thrown.
exclude and include: Specifies the list of exceptions that will trigger retries. We can also define exceptions that will not trigger retries.
interceptor: Define retry interceptors for custom retry logic. A retry interceptor is used to customize or extend the behavior of the retry process. It intercepts each retry attempt and can add custom logic.
label: Define a label for better tracking.
stateful: If true, retry state is tracked for each invocation. This is useful for stateful retries where we don’t want to retry if the method succeeded once. Stateful retries keep track of the state of the method being retried across multiple attempts, allowing the retry process to resume where it left off.
maxAttempts: Defines how many times the operation should be retried before giving up.
maxAttemptsExpression: A dynamic expression to calculate max attempts.
value: The main exceptions that trigger retry logic.
Retry Policy:
Retry policies define the logic of when to retry and how. Some common retry policies include:
Simple retry policy: Retries a method a fixed number of times.
Exception classifier retry policy: Retries based on different exceptions. Different retry behaviors can be applied for different exceptions.
Timeout retry policy: Retries the method until a specific timeout period is reached.
By default, Spring Retry uses a simple retry policy with a max attempt of 3, but this can be customized.
Stateful vs Stateless Retry:
Stateless retry: Each method invocation is considered independent. If a method succeeds after retries, future invocations will still start from scratch.
Every retry starts fresh.
No memory of previous progress.
Useful for simple, non-idempotent tasks that need to restart fully on failure.
Stateful retry: Keeps track of the state between method invocations. If the method already succeeded for a particular input, it won’t retry again for the same input. To use stateful retry, we need to set the
statefulattribute to true.Retries resume from where they left off.
Retains state across retry attempts.
Best for cases where progress must not be lost (e.g., partial success should not be redone).
The stateful retry mechanism in Spring only maintains state within the current application context or runtime session. If the application restarts (in-memory state is lost), the retry state is lost unless we implement a way to persist this state externally.
Without persistence, stateful retries will not resume where they left off after a system restart. They will start the retry process from scratch, as if it’s the first attempt. To make stateful retries resilient to restarts, we would need to persist retry state externally, in a database or other persistent storage. This allows the application to resume the retry sequence even after a restart.
Backoff Strategies :
Fixed backoff: A constant delay between retries.
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 2000))
Exponential backoff: The delay increases exponentially after each failure.
@Retryable(maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2))
3. What is @Recover?
If all retry attempts fail, Spring allows us to define a recovery method using the @Recover annotation. This method is invoked after all retries are exhausted, acting as a fallback mechanism.
4. What is RetryTemplate?
RetryTemplate allows programmatic control over retry logic, making it suitable for complex applications where flexible, fine-grained retry handling is required as compared to the @Retryable annotation.
RetryTemplateExample.java class
Usage of RetryTemplate in service class
Result Output
5. What is Retry Interceptor?
Retry interceptors in Spring Retry allow's us to customize and extend the behavior of retry operations. They are part of the Spring Retry infrastructure and act as middleware, intercepting each retry attempt to execute custom logic before or after each attempt. This makes interceptors powerful tools for enhancing retries with features like monitoring, logging, or even dynamically altering retry behavior.
Interception of Retry Attempts:
Interceptors capture each retry attempt and allow additional logic to be applied, such as logging retries, updating counters, or sending alerts.
For example, we can log each retry attempt with details like the exception causing the retry, the retry count, and the method being retried.
Customizing Retry Logic:
Interceptors provide hooks to adjust retry logic dynamically, such as modifying backoff settings or retry conditions on-the-fly based on custom logic.
We can implement specific actions on each retry (e.g., throttling if the retry count exceeds a limit).
Retry Listeners:
Spring Retry includes
RetryListener, a specific type of interceptor for adding logic at different stages of retry attempts:Before Retry: Logic before the first retry attempt.
On Retry: Logic on each retry attempt (e.g., logging each attempt).
On Recovery: Logic after all retry attempts are exhausted (e.g., sending alerts).
CustomRetryListener.java class
RetryTemplateConfig.java class
SampleService.java class
Hit the service class method via any controller class and observe the output.
Result Output
6. Example
Basic Usage of @Retryable
@RetryableStateful vs Stateless Retries Example
Below are two examples that demonstrate the difference between stateful and stateless retries using a scenario where we process orders. The state of the retry in each case determines whether it remembers the current progress or starts fresh on each retry.
Stateful Retry
Suppose a controller class calls the above service class method then
Attempt 1 result output for the API call: Failure (but progress remembered)
Attempt 2 result output for the API call: Failure (progress still remembered)
Attempt 3 result output for the API call: Success (without redoing previous work)
Stateful Retry with Persistence (to cover application restart scenario)
Stateless Retry
Result output for the API call
6. Best Practices
Retry Only for Transient Failures: Ensure that we’re only retrying for transient failures (e.g., network issues, timeouts) and not for permanent errors (e.g., validation errors).
Limit Retry Attempts: Avoid indefinite retries. Always cap the number of retry attempts or use a timeout to prevent the application from being stuck in a retry loop.
Backoff Strategy: Use an exponential backoff strategy to reduce load on external services during failures.
Recovery Fallbacks: Always provide a recovery method for better user experience in case retries are exhausted.
Last updated