Spring Retryable – Retry to consume Restful Services:

Generally in our application, We might face an issue with the REST API’s, some times the services may be down or server issue then the REST end point is not available. This exception is thrown by the application server or web-server.

But to make processing more robust and less prone to failure, It is sometimes best to make retry attempts to the same end point as it might get succeed on further attempts.

This is where Spring Retry comes to our rescue!

what is Spring Retry?
Spring Retry helps us to automatically retry the operation- most commonly consuming the Rest endpoint, again after the operation is failed.

Spring helps us with an interface – Retryable to achieve the above operation.

Most commonly used methods from Retryable interface,

PropertiesUsage
BackoffSpecify backoff properties for retrying the operation
excludeExclude properties that do not have to be retried
IncludeInclude properties that have to be retried
maxAttemptsNumber of times the retry attempt to be made
valueExceptions that are retryable
Retryable Interface Methods

@EnableRetry

We need to annotate @EnableRetry in our spring boot main class to indicate that this springboot application has implemented retryable interface.

Our Project Structure:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.3</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.javainfinite</groupId>
	<artifactId>Retry</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>Retry</name>
	<description>RestAPI Retry example</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.retry</groupId>
			<artifactId>spring-retry</artifactId>
			<version>1.3.1</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>5.3.4</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

First let us write a simple Rest End point to perform retry operation.

Let us create a custom exception,

package com.javainfinite.retry.exception;

public class RetryException extends Exception {

    private String message;

    public RetryException(String message) {
        this.message = message;
    }
}

We are going to write a code that will throw RetryException (custom exception) exception, once exception is thrown – We are going to make 4 attempts of retries to reach the endpoint with a time gap of 5 seconds.

	@RequestMapping(value="/retry")
	@Retryable(value = { RetryException.class }, maxAttempts = 4, label="retry API", backoff = @Backoff(delay = 5000))
	public void RetryTest() throws RetryException {
		System.out.println("Retrying Attempt...");
		int i=0;
		if(i==0) {
			throw new RetryException("Failed retry Again....");
		}
	}

From the above code,

We have used the annotation @Retryable to indicate that this method has enabled Retry operation.

value = Exception.class –> Whenever a exception is thrown, it will perform the retry operation for maxAttempts of 4 times and with time delay of 5 seconds. i.e. Retries made every 5 seconds for 4 times.

Output:

Now let us write an endpoint to exclude when our custom exception,

	@RequestMapping(value="/retryExclude")
	@Retryable(value = { RetryException.class }, maxAttempts = 4, exclude=RetryException.class, label="retry API", backoff = @Backoff(delay = 5000))
	public void RetryTestExclude() throws Exception {
		System.out.println("Retrying Attempt...");
		int i=0;
		if(i==0) {
			throw new RetryException("Failed retry Again....");
		}

	}

In the above code we have mentioned exclude – RetryException.class, so whenever the RetryException occurs, the retry will not be executed.

Output:

From the output retry did not happen for our exception.

So, Can we handle the exception after all retries are exhausted?

YES!

How?

@Recover

We have an annotation – @Recover, which we can make it to get executed once all the retries are made and still not able to execute the endpoint.

Now let us see an example with Recover,



	@RequestMapping(value="/retry")
	@Retryable(value = { RetryException.class }, maxAttempts = 4, label="retry API", backoff = @Backoff(delay = 5000))
	public void RetryTest() throws RetryException {
		System.out.println("Retrying Attempt...");
		int i=0;
		if(i==0) {
			throw new RetryException("Failed retry Again....");
		}
	}


@Recover
    public void connectionException(RetryException e) {
        System.out.println("Retry failure");
    }

So what will this recover do?

Once all the attempts are made for the /retry endpoint, then this Recover block of code will get executed.

IMPORTANT: The return type of /retry endpoint and @Recover should be same in order for recover to get executed!

Output:

Complete Code:

package com.javainfinite.retry;

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

@SpringBootApplication
@EnableRetry
public class RetryApplication {

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

}

RestRetryController.java

package com.javainfinite.retry.controller;

import com.javainfinite.retry.exception.RetryException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/RestRetry")
public class RestRetryController {

    @RequestMapping(value = "/retry")
    @Retryable(value = {RetryException.class}, maxAttempts = 4, label = "retry API", backoff = @Backoff(delay = 5000))
    public void RetryTest() throws RetryException {
        System.out.println("Retrying Attempt...");
        int i = 0;
        if (i == 0) {
            throw new RetryException("Failed retry Again....");
        }
    }

    @RequestMapping(value = "/retryExclude")
    @Retryable(value = {RetryException.class}, maxAttempts = 4, exclude = RetryException.class, label = "retry API", backoff = @Backoff(delay = 5000))
    public void RetryTestExclude() throws Exception {
        System.out.println("Retrying Attempt...");
        int i = 0;
        if (i == 0) {
            throw new RetryException("Failed retry Again....");
        }

    }

    @Recover
    public void connectionException(RetryException e) {
        System.out.println("Retry failure");
    }

}

Code can be downloaded at : Spring-Retryable

By Sri

Leave a Reply

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