Spring Retry

repository·main·Indexed 25 days ago

https://github.com/spring-projects/spring-retry

Spring Retry provides declarative (@Retryable) and imperative (RetryTemplate) support for retrying failed operations in Spring-based applications. It handles transient failures through stateless and stateful retry scenarios, offering configurable retry policies, backoff strategies (fixed, exponential, random), and recovery mechanisms via @Recover or RecoveryCallback. Note: This project is currently archived and superseded by Spring Framework 7's resilience features.

Tokens
5.1K
Snippets
12
Records
18
Agent score
31%

What's inside Spring Retry

  1. Configure Retry Policies in RetryTemplate

    main

    A RetryPolicy determines whether a RetryTemplate should retry an operation or fail. The RetryTemplate uses the policy to create a RetryContext and asks the policy if another attempt is permitted after a failure.

    If the policy determines the retry limit is reached, the RetryTemplate throws the original exception (or RetryExhaustedException in stateful scenarios).

    Key implementations:

    • SimpleRetryPolicy: Retries up to a fixed number of times for a specific set of exception types.
    • ExceptionClassifierRetryPolicy: Uses an ExceptionClassifier to map different exception types to different RetryPolicy instances, allowing for granular control (e.g., retrying IOException more times than SQLException).
    // Set the max attempts including the initial attempt before retrying
    // and retry on all exceptions (this is the default):
    SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true));
    
    // Use the policy...
    RetryTemplate template = new RetryTemplate();
    template.setRetryPolicy(policy);
    template.execute(new RetryCallback<MyObject, Exception>() {
        public MyObject doWithRetry(RetryContext context) {
            // business logic here
        }
    });
  2. Configure Backoff Policies

    main

    A BackoffPolicy defines how long the RetryTemplate should wait between retry attempts. This is useful for transient failures where waiting allows the underlying issue to resolve.

    Common implementations include:

    • ExponentialBackoffPolicy: Increases the wait period exponentially between attempts to avoid synchronized retries (lock-step).
    • Randomized/Jitter policies: Adds randomness to the delay to prevent resonance between related failures in complex systems.

    All Spring Retry policies use Object.wait() for the delay implementation.

    public interface BackoffPolicy {
    
        BackOffContext start(RetryContext context);
    
        void backOff(BackOffContext backOffContext)
            throws BackOffInterruptedException;
    
    }
  3. Implement RetryCallback and RecoveryCallback

    main

    Spring Retry uses two primary callback interfaces to manage the retry lifecycle:

    1. RetryCallback<T, E>: Contains the business logic to be retried via the doWithRetry(RetryContext context) method.
    2. RecoveryCallback<T>: Contains the fallback logic to be executed if the RetryCallback fails after all retry attempts are exhausted.

    You can pass both to a RetryOperations.execute() method to handle both the main logic and the recovery logic.

    MyObject myObject = template.execute(new RetryCallback<MyObject, Exception>() {
        public MyObject doWithRetry(RetryContext context) {
            // business logic here
        },
      new RecoveryCallback<MyObject>() {
        MyObject recover(RetryContext context) throws Exception {
              // recover logic here
        }
    });
  4. Implement Recovery Methods with @Recover

    main

    When all retry attempts are exhausted, you can provide a fallback mechanism using the @Recover annotation.

    Rules for recovery methods:

    1. They must be declared in the same class as the @Retryable method.
    2. The return type must match the @Retryable method's return type.
    3. The arguments can include the exception that was thrown and the original arguments passed to the @Retryable method.
    4. To resolve conflicts when multiple recovery methods exist, you can explicitly specify the method name using the recover attribute in @Retryable.
    5. (Version 1.3.2+) Spring can match recovery methods based on parameterized (generic) return types.
    @Service
    class Service {
        @Retryable(retryFor = RemoteAccessException.class)
        public void service(String str1, String str2) {
            // ... do something
        }
    
        @Recover
        public void recover(RemoteAccessException e, String str1, String str2) {
           // ... error handling making use of original args if required
        }
    }
  5. Understand Stateless vs. Stateful Retry

    main

    Spring Retry supports two modes of operation:

    • Stateless Retry: The simplest form where the retry is essentially a loop. The RetryContext state exists only on the stack, and the callback is always executed in the same thread that failed. This is suitable for transient errors like network glitches.
    • Stateful Retry: Used when a failure requires a transactional rollback (e.g., database updates with Hibernate). Because a rollback requires leaving the current execution context, the state must be lifted off the stack into heap storage. This is managed using a RetryContextCache and a RetryState object that provides a unique key to identify the item across multiple invocations.
  6. Use Declarative Retry with @Retryable and @Recover

    main

    You can apply retry logic declaratively using annotations. This requires @EnableRetry on a configuration class and an additional runtime dependency on AOP classes.

    1. Annotate a method with @Retryable to specify which exceptions should trigger a retry and how many attempts to make.
    2. Annotate a method with @Recover to define the fallback logic that executes when all retry attempts are exhausted.
    @Configuration
    @EnableRetry
    public class Application {
    
    }
    
    @Service
    class Service {
        @Retryable(retryFor = RemoteAccessException.class)
        public void service() {
            // ... do something
        }
        @Recover
        public void recover(RemoteAccessException e) {
           // ... panic
        }
    }
  7. Install Spring Retry via Maven

    main

    To use Spring Retry in your project, add the following dependency to your pom.xml. Note that this project is currently archived and has been superseded by Spring Framework 7's resilience features.

    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
    </dependency>
  8. Configure Declarative Retry with @Retryable

    main

    You can implement retry logic declaratively using the @Retryable annotation. This requires adding @EnableRetry to one of your @Configuration classes. The @Retryable annotation can be applied to specific methods or at the class level to affect all methods in that type.

    Key attributes for @Retryable:

    • maxAttempts: The maximum number of attempts (including the first).
    • backoff: A @Backoff annotation to control delay, maxDelay, and multiplier.
    • retryFor: Specifies which exceptions should trigger a retry (replaces the deprecated include).
    • noRetryFor: Specifies which exceptions should NOT trigger a retry.
    • notRecoverable: Specifies exceptions that should skip the recovery method and be thrown immediately.
    • stateful: A boolean (default false) determining if the retry is stateful. Stateful retry requires the method to have arguments to construct a cache key.
    • recover: The name of the method to call when retries are exhausted.
    • exceptionExpression: A SpEL expression evaluated against the thrown exception to decide if a retry should occur.
    @Configuration
    @EnableRetry
    public class Application {
        @Bean
        public Service service() {
            return new Service();
        }
    }
    
    @Service
    class Service {
        @Retryable(maxAttempts=12, backoff=@Backoff(delay=100, maxDelay=500))
        public void service() {
            // ... do something
        }
    }
  9. Add AOP Dependencies for Declarative Retry

    main

    Using @Retryable requires AOP (Aspect-Oriented Programming) dependencies at runtime.

    For Spring Boot applications: Add the Spring Boot starter for AOP via Gradle:

    runtimeOnly 'org.springframework.boot:spring-boot-starter-aop'

    For non-Boot applications: Add a runtime dependency on the latest version of AspectJ's aspectjweaver module via Gradle:

    runtimeOnly 'org.aspectj:aspectjweaver:1.9.20.1'
  10. Create Custom Composed Retry Annotations

    main

    Starting from version 1.3.2, you can create custom annotations that compose @Retryable with predefined behaviors. To ensure the recover attribute and other properties can be extended, use @AliasFor to map your custom annotation attributes to the underlying @Retryable attributes.

    Example of a custom @LocalRetryable annotation:

    @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Retryable(maxAttempts = "3", backoff = @Backoff(delay = "500", maxDelay = "2000", random = true))
    public @interface LocalRetryable {
        @AliasFor(annotation = Retryable.class, attribute = "recover")
        String recover() default "";
    
        @AliasFor(annotation = Retryable.class, attribute = "value")
        Class<? extends Throwable>[] value() default {};
    
        @AliasFor(annotation = Retryable.class, attribute = "include")
        Class<? extends Throwable>[] include() default {};
    
        @AliasFor(annotation = Retryable.class, attribute = "exclude")
        Class<? extends Throwable>[] exclude() default {};
    
        @AliasFor(annotation = Retryable.class, attribute = "label")
        String label() default "";
    }
  11. Enable Micrometer metrics for retries

    main

    Starting with version 2.0.8, you can monitor retry operations using the MetricsRetryListener. This listener uses a Micrometer MeterRegistry to expose a spring.retry timer.

    The timer tracks the duration from the open() to the close() listener callbacks, covering the entire retry operation. It automatically includes the following tags:

    • name: Based on the value returned by RetryCallback.getLabel().
    • retry.count: The number of retries performed (0 if the first call succeeds).
    • exception: The last exception thrown if all retry attempts are exhausted.

    You can integrate this listener by:

    1. Injecting it into a RetryTemplate.
    2. Referencing it via the @Retryable(listeners = ...) attribute.

    MetricsRetryListener can be customized with static tags or via a Function<RetryContext, Iterable<Tag>> to add dynamic tags.

  12. Configure Retry via XML AOP

    main

    You can configure declarative retry using Spring AOP XML configuration. This involves defining a pointcut for the target methods and an advisor using RetryOperationsInterceptor.

    <aop:config>
        <aop:pointcut id="transactional"
            expression="execution(* com..*Service.remoteCall(..))" />
        <aop:advisor pointcut-ref="transactional"
            advice-ref="retryAdvice" order="-1"/>
    </aop:config>
    
    <bean id="retryAdvice"
        class="org.springframework.retry.interceptor.RetryOperationsInterceptor"/>

    To customize the retry policies or listeners in XML, inject a configured RetryTemplate into the RetryOperationsInterceptor bean.