Overview
In this blog, we will configure and use Spring Retry logic using Spring Application. For the use case where you need to re-invoke a failed operation or call a method again on failure. Spring Retry provides declarative retry support for Spring applications.
Install Dependencies
Maven:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
Gradle:
compile group: 'org.springframework.retry',name:'spring-retry', version:'1.2.2.RELEASE'
Link for other versions of spring-retry.
Please Note: You will also need dependent jars for Spring Retry.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
Example
1. Enable Spring Retry in SpringBoot Application
@EnableRetry // Mandatory without this @Retryable logic will not work
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. Use retry logic on a method using annotation.
@Retryable: To add retry functionality to methods, @Retryable can be used.
@Recover: The @Recover annotation is used to define a recovery method when a @Retryable method fails with a specified exception.
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestRetry {
@Retryable(value={ ArithmeticException.class }, maxAttempts = 4, backoff=@Backoff(500))
@GetMapping("/testRetry")
public String loadData(@RequestParam("id") int id) {
System.out.println("loadData method called "+(new Date()).getTime());
if (id % 2 == 0) {
int a = id/0; // just to generate exception
}
return "Success";
}
@Recover
public String recover(ArithmeticException t, int d) {
System.out.println("recover method called");
return "Error";
}
}
Output:
loadData method called 1542818097643
loadData method called 1542818098148
loadData method called 1542818098648
loadData method called 1542818099153
recover method called
Points to Note
- Retry will be called only when ArithmeticException is thrown from the method block in the above example.
- Method will retry max 4 times [maxAttempts = 4] with a delay of 500ms [backoff = @Backoff(500))]
- @Retryable without any attribute will try 3 times with a delay of 1 sec if the method fails with an exception.
- @Recover is called when the @Retryable method fails ie after trying maxAttempts times.
- In @Recover the return type must match the @Retryable method.
- The arguments for the recovery method can optionally include the exception that was thrown, and also optionally the arguments passed to the original retryable method (or a partial list of them as long as none are omitted).
- For a random backoff between 100 and 500 milliseconds. @Retryable(maxAttempts=12, backoff=@Backoff(delay=100, maxDelay=500))
Few Pitfall
1. If the return type is not matched you get the below-mentioned error. For example, if you’re recover block code is
// If you try this block as Recover in above code
@Recover
public void recover() { // Notice return type
System.out.println("recover method called");
}
Exception:
nested exception is org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is
java.lang.ArithmeticException: / by zero] with root cause
java.lang.ArithmeticException: / by zero
at com.retry.demo.controller.TestRetry.loadData(TestRetry.java:20) ~[classes/:na]
at com.retry.demo.controller.TestRetry$$FastClassBySpringCGLIB$$a2b003c.invoke(<generated>) ~[classes/:na]
2. If the number of arguments exceeds in recover method you will get below-mentioned error. For example, if you’re recover block code is
@Recover
public String recover(int a, int b) { // parameter exceed
System.out.println("recover method called");
return "Error";
}
Exception:
threw exception [Request processing failed; nested exception is java.lang.ArrayIndexOutOfBoundsException] with root cause
java.lang.ArrayIndexOutOfBoundsException: null
at java.lang.System.arraycopy(Native Method) ~[na:1.8.0_40]
at org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler$SimpleMetadata.getArgs(RecoverAnnotationRecoveryHandler.java:180) ~[spring-retry-1.2.2.RELEASE.jar:na]
at org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler.recover(RecoverAnnotationRecoveryHandler.java:63) ~[spring-retry-1.2.2.RELEASE.jar:na]
at org.springframework.retry.interceptor.RetryOperationsInterceptor$ItemRecovererCallback.recover(RetryOperationsInterceptor.java:141) ~[spring-retry-1.2.2.RELEASE.jar:na]
at org.springframework.retry.support.RetryTemplate.handleRetryExhausted(RetryTemplate.java:512) ~[spring-retry-1.2.2.RELEASE.jar:na]
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:351) ~[spring-retry-1.2.2.RELEASE.jar:na]
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:180) ~[spring-retry-1.2.2.RELEASE.jar:na]
at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:115) ~[spring-retry-1.2.2.RELEASE.jar:na]
at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:153) ~[spring-retry-1.2.2.RELEASE.jar:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.1.2.RELEASE.jar:5.1.2.RELEAS
3. @Retryable and @Recover method must not be private.
Source Code
Download the Sample Project used for this Blog.
- To Run: Inside project root dir run: mvn spring-boot:run
- Hit http://localhost:8080/testRetry?id=2 in a browser.
3 Comments
Ramanamurty · September 20, 2019 at 9:22 pm
After API explanation Pitfalls helped me to recover my issues. Thanks for the Article.
Rishab · October 18, 2019 at 2:40 pm
Sample project just works fine.
Vinay · May 7, 2021 at 3:40 pm
Thanks. 2nd pitfall point was the issue for me.