I'm using Resilience4J in a Spring Boot project to make a call to a REST client like so:
@Retry(name = "customerService")
public Customer getCustomer(String customerNumber) {
restTemplate.exchange("https://customer-service.net", HttpMethod.GET, ..., customerNumber);
}
With this configuration:
resilience4j.retry:
instances:
customerService:
maxAttempts: 3
waitDuration: 10s
retryExceptions:
- org.springframework.web.client.HttpServerErrorException
My expectation is if restTemplate.exchange()
is invoked and the customer service responds with a HttpServerErrorException
, the getCustomer()
method will be called 3 more times after a wait of ten seconds.
However, this is not the case.
This method is never called again, and the exception is immediately thrown.
Seeing that a fallback method is included in the examples, I decided to add it, even though I don't really want to invoke a different method, I just want my original method called again.
Anyway, I specified a fall back:
@Retry(name = "customerService", fallback = "customerFb")
public Customer getCustomer(String customerNumber) {
return getCustomer();
}
private Customer customerFb(String customerNumber, HttpServerErrorException ex) {
log.error("Could not retrieve customer... retrying...");
return getCustomer();
}
private Customer getCustomer(String customerNumber) {
return restTemplate.exchange("https://customer-service.net", HttpMethod.GET, ..., customerNumber);
}
Now, I am seeing the fallback method being retried, however, the HttpServerErrorException is being thrown each time, meaning that consumers will receive an exception as a response to their calls.
My questions are:
Does a fallback method need to be implemented in order for retry functionality to work?
and
Is it expected behavior for the exceptions to be thrown? Am I doing something wrong? I don't want callers of this service to receive an exception until all retry attempts have been made.
Thanks
Q: Does a fallback method need to be implemented in order for retry functionality to work?
Ans: No, Fallback is optional in Resilience4J Retry feature.
Behaviour of Resilience4J Retry
:
At startup, resilience retry loads the configuration from application.properties/yml if configured else will initialize with default values as mentioned here in the documentation.
When a method annotated with @Retry is invoked, then it will be under retry monitor.
If property resilience4j.retry.instances.<instance name>.retryExceptions
is configured explicitly then only the configured exceptions will be considered as a failure and retry will be triggered for only these failures and for the rest of the exceptions it behaves normal without the retry feature.
When an expected exception is occurred under @retry
annotated method, then it won't log anything about the exception but it retries the same method as per the maxAttempts
property configured. Default value of maxAttempts
property is 3
. Ater the maxAttempts it throws the exceptions and can be seen in the log.
There are also properties like waitDuration
, intervalFunction
, ignoreExceptions
.. e.tc which can be explicitly configured. For more look at the documentation link.
To see actually what's happening during an event of exception in the method annotated with @retry
, Enable the RetryRegistry event consumer to print the events received to it.
Example Code for RetryRegistry event listener:
package com.resilience.retry.config;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import io.github.resilience4j.retry.RetryRegistry;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class RetryRegistryEventListener {
@Autowired
private RetryRegistry registry;
@PostConstruct
public void postConstruct() {
//registry.retry(<resilience retry instance name>)
registry.retry("sample-api").getEventPublisher()
.onRetry(ev -> log.info("#### RetryRegistryEventListener message: {}", ev));
}
}
example log:
2021-11-01 14:55:12.337 INFO 18176 --- [nio-8080-exec-1] c.r.retry.controller.RetryController : Sample Api call receieved
2021-11-01 14:55:12.345 INFO 18176 --- [nio-8080-exec-1] c.r.r.config.RetryRegistryEventListener : #### RetryRegistryEventListener message: 2021-11-01T14:55:12.344325600+05:30[Asia/Calcutta]: Retry 'sample-api', waiting PT10S until attempt '1'. Last attempt failed with exception 'org.springframework.web.client.HttpServerErrorException: 500 INTERNAL_SERVER_ERROR'.
2021-11-01 14:55:12.350 INFO 18176 --- [nio-8080-exec-1] c.r.retry.controller.RetryController : messsage: 2021-11-01T14:55:12.344325600+05:30[Asia/Calcutta]: Retry 'sample-api', waiting PT10S until attempt '1'. Last attempt failed with exception 'org.springframework.web.client.HttpServerErrorException: 500 INTERNAL_SERVER_ERROR'.
2021-11-01 14:55:22.359 INFO 18176 --- [nio-8080-exec-1] c.r.retry.controller.RetryController : Sample Api call receieved
2021-11-01 14:55:22.360 INFO 18176 --- [nio-8080-exec-1] c.r.r.config.RetryRegistryEventListener : #### RetryRegistryEventListener message: 2021-11-01T14:55:22.360672900+05:30[Asia/Calcutta]: Retry 'sample-api', waiting PT10S until attempt '2'. Last attempt failed with exception 'org.springframework.web.client.HttpServerErrorException: 500 INTERNAL_SERVER_ERROR'.
2021-11-01 14:55:22.361 INFO 18176 --- [nio-8080-exec-1] c.r.retry.controller.RetryController : messsage: 2021-11-01T14:55:22.360672900+05:30[Asia/Calcutta]: Retry 'sample-api', waiting PT10S until attempt '2'. Last attempt failed with exception 'org.springframework.web.client.HttpServerErrorException: 500 INTERNAL_SERVER_ERROR'.
@FerdTurgusen: I believe there is some mistake in your code else it would be working fine. With the given information in the question i was unable to exactly find the issue. Hence, I have created a sample spring boot with resilience4j example, replicated your issue and it's working fine for me, hence uploaded to Github for your reference. I suggest you to add RetryRegistryEventListener class and find the issue in event logs, if still not resolved please share the event listener log.
Github Reference: sample spring-boot with resilience4j retry project