spring-retry

Spring @Retryable - reference thrown exception in delayExpression


I have a service that calls a 3rd party rate-limited API. The API follows the common pattern of returning a 429 response code w/ a Retry-After header containing the amount of time to wait before making another API call when the allowed rate has been exceeded.

I want to use spring-retry's @Retryable annotation with delayExpression to dynamically set the delay for each retry based on the Retry-After value returned from the API. I reviewed the spring-retry documentation but could not figure out how (or if it is even possible) to reference the thrown exception in the delayExpression.

It seems you can reference the thrown exception as #root in the @Retryable.exceptionExpression, but in @Backoff.delayExpression I can only access the method arguments on the #root object - I can't figure out how to reference the thrown exception.

Is what I am trying to do possible? I know about RetryTemplate but would like to keep this simple w/ @Retryable if I can!

@Component
public class MyService {

    @Retryable(retryFor = {RateLimitExceededException.class},
            backoff = @Backoff(delayExpression = "#root.retryAfter")) // I want the value from thrownException.retryAfter
    public void doSomeApiCall(String parameters) {
        // do some API call, get a 429 response
        long retryAfter = 30000; // dynamic, parsed from API response's Retry-After header
        throw new RateLimitExceededException(retryAfter);
    }

    public static class RateLimitExceededException extends RuntimeException {
        protected long retryAfter;
        public RateLimitExceededException(long retryAfter) {
            this.retryAfter = retryAfter;
        }
        public long getRetryAfter() { return this.retryAfter; }
    }

}

Solution

  • You are correct; currently, only the method arguments are available for evaluating the expression at runtime.

    Feel free to open a new feature request issue on GitHub.

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

    You can, however, use @Retryable with a custom interceptor (using a RetryTemplate) so the business code can remain simple.

        /**
         * Retry interceptor bean name to be applied for retryable method. Is mutually
         * exclusive with other attributes.
         * @return the retry interceptor bean name
         */
        String interceptor() default "";