Search code examples
javaspringspring-bootresilience4jresilience4j-retry

Handle exception after reaching max attempts in resilience4j-retry using Spring Boot


I have a scenario I want to log each retry attempt and when the last one fails (i.e. maxAttempts reached) a exception is thrown and let's say an entry to a database is created.

I try to achieve this using Resilience4j-retry with Spring Boot, therefore I use application.yml and annotations.

@Retry(name = "default", fallbackMethod="fallback")
@CircuitBreaker(name = "default", fallbackMethod="fallback")
public ResponseEntity<List<Person>> person() {
    return restTemplate.exchange(...);               // let's say this always throws 500
}

The fallback logs the cause of the exception into an application log.

public ResponseEntity<?> fallback(Exception e) {
    var status = HttpStatus.INTERNAL_SERVER_ERROR;
    var cause = "Something unknown";
    if (e instanceof ResourceAccessException) {
        var resourceAccessException = (ResourceAccessException) e;
        if (e.getCause() instanceof ConnectTimeoutException) {
            cause = "Connection timeout";
        }
        if (e.getCause() instanceof SocketTimeoutException) {
            cause = "Read timeout";
        }
    } else if (e instanceof HttpServerErrorException) {
        var httpServerErrorException = (HttpServerErrorException) e;
        cause = "Server error";
    } else if (e instanceof HttpClientErrorException) {
        var httpClientErrorException = (HttpClientErrorException) e;
        cause = "Client error";
     } else if (e instanceof CallNotPermittedException) {
        var callNotPermittedException = (CallNotPermittedException) e;
        cause = "Open circuit breaker";
    }
    var message = String.format("%s caused fallback, caught exception %s", 
        cause, e.getMessage());
    log.error(message);                                         // application log entry
    throw new MyRestException (message, e);
}

When I call this method person() the retry happens as maxAttempt configured. I expect my custom runtime MyRestException is caught on each retry and thrown on the last one (when maxAttempt is reached), so I wrap the call in the try-catch.

public List<Person> person() {
    try {
        return myRestService.person().getBody();
    } catch (MyRestException ex) {
        log.error("Here I am ready to log the issue into the database");
        throw new ex;
    }
}

Unfortunatelly, the retry seems to be ignored as the fallback encounters and rethrows the exception that is immediatelly caught with my try-catch instead of the Resilience4j-retry mechanism.

How to achieve the behavior when the maxAttempts is hit? Is there a way to define a specific fallback method for such case?


Solution

  • Why don't you catch and map exceptions to MyRestException inside of your Service methods, e.g. myRestService.person()? It makes your configuration even simpler, because you only have to add MyRestException to the configuration of your RetryConfig and CircuitBreakerConfig.

    Spring RestTemplate also has mechanisms to register a custom ResponseErrorHandler, if you don't want to add the boilerplate code to every Service method. -> https://www.baeldung.com/spring-rest-template-error-handling

    I would not map CallNotPermittedException to MyRestException. You don't want to retry when the CircuitBreaker is open. Add CallNotPermittedException to the list of ignored exceptions in your RetryConfig.

    I think you don't need the fallback mechanism at all. I thing mapping an exception to another exception is not a "fallback".