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


@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'.