javaspringspring-bootspring-retry

How does spring-retry determine which methods to retry when @Retryable is placed at the class level?


I have a @Retryable placed at the class level.

@Import({GrpcClientRetryConfig.class})
@Retryable(interceptor = "grpcClientRetryInterceptor")
@Component
public class Profile {
    public void method1() {
        method2();
    }

    public void method2() {
        val msg = "Hello World";
        System.out.println(msg);
        throw new RunTimeException("Testing");
    }
}

@EnableRetry
@Configuration
public class GrpcClientRetryConfig {
 @Bean
 public RetryOperationsInterceptor grpcClientRetryInterceptor() {
    val template = new RetryTemplate();
    val backOffPolicy = new ExponentialBackOffPolicy();
    // Set the exponential backoff parameter
    val interceptor = new RetryOperationsInterceptor();
    val simpleRetry = new SimpleRetryPolicy();
    simpleRetry.setMaxAttempts(2);
    template.setRetryPolicy(simpleRetry);
    template.setBackOffPolicy(backOffPolicy);
    template.setListeners(new GrpcClientRetryListener[] {new GrpcClientRetryListener()});
    interceptor.setRetryOperations(template);
    return interceptor;
 }
}

Case 1

val profile = new Profile();
profile.method2(); // Hello World printed twice an exception is thrown

Case 2

val profile = new Profile();
profile.method1(); // Hello World printed twice and then an exception is thrown

A sample project is placed here. Please check com.example.helloworld.controller.HelloWorldController#sendGreetings

Should not Hello World be printed 4 times? method1() calls method2(). method2() gets retried 2 times and then throws the exception to method1() which again calls method2() one more time?

Can someone let me know why method1() is not retried twice in the second case? How does spring determine which methods to be retried when a @Retryable is placed at the class level?


Solution

  • I cannot reproduce it. Works as expected:

    @Bean
    ApplicationRunner applicationRunner(ServiceWithRetry serviceWithRetry) {
        return args -> {
            System.out.println("Test method2");
            try {
                serviceWithRetry.method2();
            }
            catch (Exception e) {
    
            }
            System.out.println("Test method1");
    
            try {
                serviceWithRetry.method1();
            }
            catch (Exception e) {
    
            }
        };
    }
    

    and the output:

    Test method2
    2024-04-01T14:40:57.956-04:00 TRACE 34592 --- [           main] o.s.retry.support.RetryTemplate          : RetryContext retrieved: [RetryContext: count=0, lastException=null, exhausted=false]
    2024-04-01T14:40:57.956-04:00 DEBUG 34592 --- [           main] o.s.retry.support.RetryTemplate          : Retry: count=0
    Method2
    2024-04-01T14:40:57.957-04:00 DEBUG 34592 --- [           main] o.s.r.backoff.ExponentialBackOffPolicy   : Sleeping for 100
    2024-04-01T14:40:58.068-04:00 DEBUG 34592 --- [           main] o.s.retry.support.RetryTemplate          : Checking for rethrow: count=1
    2024-04-01T14:40:58.068-04:00 DEBUG 34592 --- [           main] o.s.retry.support.RetryTemplate          : Retry: count=1
    Method2
    2024-04-01T14:40:58.069-04:00 DEBUG 34592 --- [           main] o.s.retry.support.RetryTemplate          : Checking for rethrow: count=2
    2024-04-01T14:40:58.069-04:00 DEBUG 34592 --- [           main] o.s.retry.support.RetryTemplate          : Retry failed last attempt: count=2
    Test method1
    2024-04-01T14:40:58.069-04:00 TRACE 34592 --- [           main] o.s.retry.support.RetryTemplate          : RetryContext retrieved: [RetryContext: count=0, lastException=null, exhausted=false]
    2024-04-01T14:40:58.069-04:00 DEBUG 34592 --- [           main] o.s.retry.support.RetryTemplate          : Retry: count=0
    Method1
    Method2
    2024-04-01T14:40:58.070-04:00 DEBUG 34592 --- [           main] o.s.r.backoff.ExponentialBackOffPolicy   : Sleeping for 100
    2024-04-01T14:40:58.176-04:00 DEBUG 34592 --- [           main] o.s.retry.support.RetryTemplate          : Checking for rethrow: count=1
    2024-04-01T14:40:58.176-04:00 DEBUG 34592 --- [           main] o.s.retry.support.RetryTemplate          : Retry: count=1
    Method1
    Method2
    2024-04-01T14:40:58.176-04:00 DEBUG 34592 --- [           main] o.s.retry.support.RetryTemplate          : Checking for rethrow: count=2
    2024-04-01T14:40:58.176-04:00 DEBUG 34592 --- [           main] o.s.retry.support.RetryTemplate          : Retry failed last attempt: count=2
    

    Any chances that you can share a simple Spring Boot project we can play with?

    UPDATE

    Thank you for the sample!

    I have modified your method1 like:

      public String method1() {
        System.out.println("method1");
        return method2();
      }
    

    and added this property:

    logging.level.org.springframework.retry=debug
    

    After running HelloWorldControllerTest I got this output:

    2024-04-02T11:03:31.347-04:00 DEBUG 20888 --- [           main] o.s.retry.support.RetryTemplate          : Retry: count=0
    method1
    Hello World
    2024-04-02T11:03:31.348-04:00 DEBUG 20888 --- [           main] o.s.retry.support.RetryTemplate          : Checking for rethrow: count=1
    2024-04-02T11:03:31.348-04:00 DEBUG 20888 --- [           main] o.s.retry.support.RetryTemplate          : Retry: count=1
    method1
    Hello World
    2024-04-02T11:03:31.348-04:00 DEBUG 20888 --- [           main] o.s.retry.support.RetryTemplate          : Checking for rethrow: count=2
    2024-04-02T11:03:31.348-04:00 DEBUG 20888 --- [           main] o.s.retry.support.RetryTemplate          : Retry failed last attempt: count=2
    

    So, works as expected.

    The retry advice is applied on this method1() and calling that method2() from here does not cause the second, nested retry to be initiated.