0

I am trying to get to the best way to wrap spring-retry @Retryable annotation around external service call. Here is my code:

@Retryable(exclude = HttpClientErrorException.BadRequest.class, value = RestClientException.class)
private ResponseEntity<Item> retrieveItemById(String id) 
{
    HttpHeaders headers = new HttpHeaders();
    try {
        return restTemplate.exchange(httpConnectionProperties.getBaseUrl() + "/items",
                HttpMethod.GET, new HttpEntity<>(item, headers), Item.class, id);
    } 
    catch (RestClientException e) {
        log.error("Exception occurred while retrieving an item" , e);
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
}

I have a few questions on what happens when a RestClientException occurs :

  1. Is the catch block executed before retry kicks in or does the retry kicks in before the catch block execution? Do I need a recovery block?
  2. Probably more of an exception handling question - Is there a way to differentiate between an actual retry worthy scenario (service momentarily down, network issues, I/O error etc.) vs exception occurring due to lack of presence of an item in the above case?
linuxNoob
  • 600
  • 2
  • 14
  • 30

1 Answers1

1

Since you are catching and "handling" the exception, retry is disabled; retry will only work if the method throws an exception.

To change the result (instead of throwing the exception to the caller when retries are exhausted, you need a @Recover method.

Not retryable exceptions will go straight there; you can have multiple @Recover methods for different exception types, or one generic one and you can check the exception type yourself.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Does it matter if the `@Retryable` or `@Recover` methods are private? – linuxNoob Jul 05 '22 at 21:05
  • 1
    `@Retryable` methods must be public or package protected - otherwise you wouldn't be able to call them; calling a `@Retryable` method from within the same class won't work because you would bypass the interceptor. `@Recover` methods can be private. – Gary Russell Jul 05 '22 at 21:19
  • Another question - If I don't specify a value in `@Retryable` block and specify a `@Recover` method having `Exception` type as the arg then by default all `Exception` type exceptions will be redirected there? – linuxNoob Jul 06 '22 at 15:51
  • 1
    Yes, that is correct, if `value` or `include` is not provided, all exceptions (except those in `exclude`) are retryable. Those in `exclude` go straight to the `@Recover` method without retries. – Gary Russell Jul 06 '22 at 16:10
  • Is catching `RestClientException`, doing `instanceof` checks on them for obtaining child exceptions logging it and re-throwing it considered an anti-pattern? – linuxNoob Jul 06 '22 at 21:47
  • 1
    `instanceof` checks in `try/catch` blocks are generally frowned upon; it's better to have multiple catch blocks (or a multi-catch block if you want to take the same action for several types). You could extend the same concept to `@Retryable/@Recover` methods, but it's a bit more tricky there because there is no equivalent of a multi-catch (`catch (Ex1 | Ex2 | Ex3 ex)`). So I would just use judgement there, depending on the complexity of multiple `@Recover` methods Vs. one with `instanceof` tests. – Gary Russell Jul 06 '22 at 21:59
  • If I use `RetryTemplate` can I call the method which invokes external service in the same class? – linuxNoob Jul 07 '22 at 15:14
  • 1
    If you mean instead of `@Retryable` then, yes. `RetryTemplate` was the original mechanism before the annotation was added to the framework. – Gary Russell Jul 07 '22 at 15:22