Overview

In a project, there might be a 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.

In this blog, we will configure and use Spring Retry logic using Spring Application.

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 method block in above example.
  2. Method will retry max 4 times [maxAttempts = 4] with a deay 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 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 no of arguments exceed 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

Source Code

Download Sample Project used for this Blog.

 

Categories: JAVA

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.