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

  1. Retry will be called only when ArithmeticException is thrown from the method block in the above example.
  2. Method will retry max 4 times [maxAttempts = 4] with a delay of 500ms [backoff = @Backoff(500))]
  3. @Retryable without any attribute will try 3 times with a delay of 1 sec if the method fails with an exception.
  4. @Recover is called when the @Retryable method fails ie after trying maxAttempts times.
  5. In @Recover the return type must match the @Retryable method.
  6. 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).
  7. 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.

Categories: Spring

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.

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *