spring-retry icon indicating copy to clipboard operation
spring-retry copied to clipboard

Add a possibility to use listener per @Retryable method instead of global listeners

Open yura-arab4uk opened this issue 10 months ago • 5 comments

In my project I wanted to add a listener and use it along with @Retryable annotation. But when I add a listener, it's automatically considered by Spring Retry as global one, meaning it will be applied at least to every method marked with @Retryable annotation, which prevents me from creating listener, because I don't want to impact an existing code.

Example: @Retryable(retryFor = SomeException.class, maxAttempts = 5, listeners = "myListenerBean")

When I create myListenerBean, it will be automatically applied to all existing methods with @Retryable annotation, which is not desired behavior. The only way to prevent this is to either specify an empty string on existing code, which is not always possible to modify an existing code

@Retryable(listeners = "")

or use interceptor attribute which cannot be used with any other attributes and which requires interceptor bean along with all retry configurations code:

@Retryable(interceptor = "myInterceptorBean")

As a result instead of using neat single line: @Retryable(retryFor = SomeException.class, maxAttempts = 5, listeners = "myListenerBean")

I'm forced to use: @Retryable(interceptor = "myInterceptorBean")

along with whole retry configuration, for example:

@Slf4j
@Configuration
@RequiredArgsConstructor
public class ConnectionErrorRetryConfig {
    public static final String CONNECTION_ERROR_RETRY_INTERCEPTOR = "connectionErrorRetryInterceptor";

    @Value("${request.max-attempts:5}")
    private int maxAttempts;

    private final MyService myService;

    @Bean(CONNECTION_ERROR_RETRY_INTERCEPTOR)
    public RetryOperationsInterceptor connectionErrorRetryInterceptor() {
        SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
        simpleRetryPolicy.setMaxAttempts(maxAttempts);

        RetryTemplate retryTemplate = RetryTemplate.builder()
            .customPolicy(simpleRetryPolicy)
            .retryOn(SomeException.class)
            // forced to create a new RetryListener here as NOT a bean, because bean is impacting globally every @Retryable method
            .withListener(new RetryListener() {
                @Override
                public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable)
                {
                    if (throwable instanceof SomeException someException)
                    {
                        myService.doImportantAction(someException);
                    }

                    RetryListener.super.onError(context, callback, throwable);
                }
            })
            .build();

        return RetryInterceptorBuilder.stateless()
            .retryOperations(retryTemplate)
            .recoverer(new ConnectionErrorRecoverer())
            .build();
    }
}

As it's been already discussed here, we could think of any alternative, for example deprecating existing listeners attribute and/or providing some other attribute(s) etc. in order to not use global listeners.

Thanks.

yura-arab4uk avatar Feb 08 '25 10:02 yura-arab4uk

By @artembilan we can solve this in the future: https://github.com/spring-projects/spring-retry/discussions/484#discussioncomment-12085232

yura-arab4uk avatar Feb 08 '25 10:02 yura-arab4uk

Thank you, Yuriy! Please, take into account that words starting with @ have to be present as a code (wrapped into back-ticks), otherwise GitHub treats it as a user mention.

I mean that gas to be like:

methods with @Retryable annotation.

artembilan avatar Feb 08 '25 14:02 artembilan

@artembilan sure, corrected, thx

yura-arab4uk avatar Feb 08 '25 14:02 yura-arab4uk

It doesn’t matter now in this post: a notification has already been sent to that GH user 😅.

artembilan avatar Feb 08 '25 14:02 artembilan

Good for that user : ), if such user exists then will learn about this issue 😅

yura-arab4uk avatar Feb 10 '25 11:02 yura-arab4uk

The project goes into a maintenance mode, therefore all users should consider to migrate to Spring Framework Retry API instead: https://spring.io/blog/2025/09/09/core-spring-resilience-features.

artembilan avatar Sep 10 '25 15:09 artembilan