How to retry service calls in Spring Boot ?

Let’s say you are building a spring boot app which calls other third party REST APIs.

And those APIs are not very reliable. They throw error at times but works fine most of the times.

So you want to retry hitting them when they error out so that you are chance of getting a response improves.

How do you do that in Spring Boot?

Wouldn’t it be nice if you can just add an annotation on a service method which will make Spring retry it again if it errors out.

Spring provides exactly that facility.

To retry a service just add the annotation @Retryable to it.

Here are the steps to follow :

STEP1 : Add two dependencies to your project (spring-retry and spring-aspects)

STEP2: Add the annotation @EnableRetry in your main class

STEP3: Add the annotation @Retryable on the service method you want to retry .

Let’s look at the steps in detail.

STEP1: Add required dependencies.

Spring Retry requires two dependencies spring-retry and spring-aspects. Add them in your pom.xml

		<dependency>
			<groupId>org.springframework.retry</groupId>
			<artifactId>spring-retry</artifactId>
			<version>1.3.0</version>
		</dependency>
	
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>4.2.0.RELEASE</version>
		</dependency>

STEP 2: Enable Retry by adding the annotation @EnableRetry

This can be done as shown below:

package com.example.retry;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;



@EnableRetry
@SpringBootApplication
public class RetryApplication {

	public static void main(String[] args) {
		SpringApplication.run(RetryApplication.class, args);
	}

}

STEP 3: Add @Retryable annotation to the service method that needs to be retried.

This can be done as shown in the below code:

package com.example.retry;

import java.util.Map;

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

@Service
public interface RetryService {

	@Retryable(value = { Exception.class }, maxAttempts = 2, backoff = @Backoff(delay = 1000))
	public void retry(Map<String, String> request) throws Exception;
}

As you see , the @Retryable annotation takes few parameters which you can use to configure the way retry happens.

The attribute value indicates the exception for which the retry should be triggered. Whenever this exception is thrown the method will be executed again. As a good practice it is better to throw a custom exception in your service method and configure it here instead of the generic Exception class.

The attribute maxAttempts indicates how many times you want to retry. The default value is 3.

The attribute backOff indicates how long you want to wait before trying back again. This value is in milliseconds. In the above example I configured this to be one second. If this attribute is left out , retry will happen immediately when the service fails.

You can configure maxAttempts and backOff values in a property file and then refer them here using maxAttemtpsExpression and delayExpression values like below:

package com.example.retry;

import java.util.Map;

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

@Service
public interface RetryService {

	@Retryable(value = { Exception.class }, maxAttemptsExpression = "${retry.maxAttempts}", backoff = @Backoff(delayExpression = "${retry.delay}"))
	public void retry(Map<String, String> request) throws Exception;
}

Application.properties:

retry.maxAttempts = 2

retry.delay = 1000

Notice that the annotation is used on an interface method than on the actual implementation. It works on the actual implementation method as well but annotating on the interface method looks more elegant and decouples the retry mechanism from the actual implementation.

Let’s test this.

Below is the implementation of the service method I created:

package com.example.retry;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class RetryServiceImpl implements RetryService{
	
	Logger logger = LoggerFactory.getLogger(RetryServiceImpl.class);

	@Override
	public void retry(Map<String, String> request) throws Exception {
		
		
		logger.info("Executing retry service");
		throw new Exception("Testing retry");
		
	}

}

I am throwing an exception inside the method body so the method should be invoked twice by Spring. In a real world scenario this method could be making a call to a REST service.

Let’s create a REST API and call the above service inside it:

package com.example.retry;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@org.springframework.web.bind.annotation.RestController
public class RestController {
	
	@Autowired
	private RetryService retryService;
	
	@PostMapping(value="/post")
	public void post(@RequestBody Map<String,String> request) throws Exception{
		
		retryService.retry(request);
	}

}

The REST API does nothing , just takes in a JSON request and calls the service method.

When I hit the above REST service through postman , the service method retry is called twice as seen in the logs:

Let’s make this method succeed in the second try and see if it works.

Here is the updated service implementation to verify this:

package com.example.retry;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class RetryServiceImpl implements RetryService{
	
	Logger logger = LoggerFactory.getLogger(RetryServiceImpl.class);
	
	static int counter = 0;

	@Override
	public void retry(Map<String, String> request) throws Exception {
		
		counter++;
		
		
		logger.info("Executing retry service");
		
		
		if(counter==1) {
		throw new Exception("Testing retry");
		}else {
			
			logger.info("Retry succeeded");
		}
		
	}

}

Now when I hit the REST API /post , the below logs get printed:

Notice the log “Executing retry service” getting printed twice and the log “Retry succeeded” getting printed in the second retry. It threw an exception earlier but now succeeded in the second try.

That’s it!

Here is the entire code:

https://github.com/vijaysrj/springretry


Posted

in

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading