@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>

We need enable retry feature before we can use @Retryable

// EnableRetry annotaion will enable spring boot retry pattern and then 
// only @Retryable annotation will work
@SpringBootApplication
@EnableRetry
public class MainApplication {
   public static void main(String[] args) {
    SpringApplication.run(RetryApplication.class, args);
   }
}

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.

In order to use @Retryable,our method needs to called from outside of the class, because under the hood @Retryable makes use of spring's AOP which makes use of proxy to call retires on our target method.

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

We can add Retry Listeners which are useful for tracking the retry process, like logging attempts or handling specific actions on retries.

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

Stateful 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)

  • The retry state (orderId) ensures that the retry behavior applies specifically to the same orderId.

  • If we were to call processOrder with a different orderId, it would treat it as a separate retry sequence and start fresh with attempt resetting to 1.

  • stateful = true is effective for cases like this where it’s critical that retries apply uniquely to specific operations, ensuring that if there's partial progress, it remembers and avoids redundant processing.

Stateful Retry with Persistence (to cover application restart scenario)

Stateless Retry

Result output for the API call

6. Best Practices

  1. 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).

  2. 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.

  3. Backoff Strategy: Use an exponential backoff strategy to reduce load on external services during failures.

  4. Recovery Fallbacks: Always provide a recovery method for better user experience in case retries are exhausted.

Last updated