Retry (Spring Boot)
Introduction
Using spring retry to gracefully implement the retry code.
See also 'Async (Spring Boot)' post:
https://sites.google.com/site/pawneecity/sprint-boot/async-spring-boot
Reference
Use Spring Retry to Implement Automatic Retry
https://medium.com/javarevisited/use-spring-retry-to-implement-automatic-retry-742f4532620c
Como usar Spring Boot Retry
https://gustavopeiretti.com/spring-boot-retry/
Using Spring boot Async with Retry
https://www.openprogrammer.info/2019/09/03/using-spring-boot-async-with-retry/
@Retryable usage
pom.xml:
<!-- spring-retry depends on AOP -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- spring-retry depends on AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Enable retry:
@EnableRetry
@SpringBootApplication
public class RetryDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RetryDemoApplication.class, args);
}
}
Add the @Retryable annotation on the business method that needs to be retried:
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
@Retryable(retryFor = { ServiceUnavailableAppException.class }, maxAttempts = 3, backoff = @Backoff(delay = 3000, multiplier = 2.0))
public void sendMessage(String message) throws InterruptedException {
mailSender.doSend(message);
}
The @Retryable annotation has several important attributes, which are described below.
value : What exception do you want to throw before retrying. This is the condition for triggering retries.
maxAttempts : The maximum number of retries.
backoff: Retry waiting policy, the default policy is to wait for 1 second to retry. If the code multiplier above is set to 2, it means that the first retry waits for 3 seconds, the second retry waits for 6 seconds, the third retry waits for 12 seconds, and so on.
@Recover usage
The annotation @org.springframework.retry.annotation.Recover has the following javadoc:
Annotation for a method invocation that is a recovery handler.
A suitable recovery handler has a first parameter of type Throwable (or a subtype of Throwable) and a return value of the same type as the <code>@Retryable</code> method to recover from.
The Throwable first argument is optional (but a method without it will only be called
if no others match). Subsequent arguments are populated from the argument list of the
failed method in order.
Because spring retry is implemented based on spring AOP, the implementation principle is the same as that of spring transactions. Both use dynamic proxy, so if used improperly, retry will become invalid. For example, the exception is caught in the method.
Logging exhausted retries (@Recover)
If the number of retries is exhausted and the method call still fails, what can be done to record the log? It can be done through the @Recover annotation.
@Recover
public void retryExampleRecovery(ServiceUnavailableAppException th, Application a,
LearningObjectDeliveryDto lodDto, String recipientEmail) {
// DOC Just logging @Retryable exhausted retries
if (log.isErrorEnabled()) {
val errMsg = String.format(
"Exhausted retries for notifyNewDeliveryToLibraryAsync. Application:%n%s%nLearning object delivery:%n%s%nRecipient email:%n%s%n",
a, lodDto, recipientEmail);
log.error(errMsg, th);
}
}
Providing a fallback response (@Recover)
Another usage of @Recover is to provide a fallback response if all retries fails, for an example see the reference article 'Como usar Spring Boot Retry'.