Spring Retry: method annotated with @Recover not being called

Tadele Azanaw picture Tadele Azanaw · May 14, 2018 · Viewed 11.2k times · Source

I am testing a spring retry, but it seems the recover is not being called. Tried to get it work but it seems exhaustive. I passed to @Recover no argument, Throwable, Exception. Changed retry dependency version, and it seems it is included with aop for spring boot and removed it. Kept getting recover is not being call with the following exception messege.

Request processing failed; nested exception is org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is java.lang.ArithmeticException: / by zero] with root cause

Any help would be much appreciated

The code i have looks like below.

Configuration class

package hello;

import java.util.Arrays;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Retryable;

@SpringBootApplication
@EnableRetry
public class Application {
public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
}

@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
    return args -> {

        System.out.println("Let's inspect the beans provided by `Spring Boot:");`

        String[] beanNames = ctx.getBeanDefinitionNames();
        Arrays.sort(beanNames);
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }

    };
}

}

Rest Controller class;

package hello;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloController {

@Autowired
private SomeService service;

@RequestMapping("/")
public String hello() {
    String result = service.getInfo();
    return result;
}

}

Service class is ;

ackage hello;

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

@Service
public class SomeService {

@Retryable(value = ArithmeticException.class, maxAttempts = 3, `backoff = @Backoff(delay = 3000))`
public String getInfo() {
    System.out.println("How many time will this be printed?");
    return "Hello" + 4/0;
}

@Recover
public void helpHere(ArithmeticException cause) {
        System.out.println(cause);
        System.out.println("Recovery place!");
}

THis is my dependencies list

 <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- tag::actuator[] -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- end::actuator[] -->
    <!-- tag::tests[] -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- end::tests[] -->
    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
        </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

With try-catch and variety of arguments

 @Service
public class SomeService {

    @Retryable(value = {ArithmeticException.class}, maxAttempts = 3, `backoff = @Backoff(delay = 3000))`
public String getInfo() {
        try {
        System.out.println("How many time will this be printed?");
        return "Hello" + 4/0;
    } catch(ArithmeticException ex) {
            System.out.println("In the arthemetic Exception");
            throw new ArithmeticException();
    }   
}

@Recover
public void helpHere(ArithmeticException cause) {
        System.out.println(cause);
        System.out.println("Recovery place! ArithmeticException");
}
@Recover
public void helpHere(Exception cause ) {
        System.out.println(cause);
        System.out.println("Recovery place! Exception");
}

@Recover
public void helpHere(Throwable cause) {
        System.out.println(cause);
        System.out.println("Recovery place! Exception");
}

@Recover
public void helpHere() {
        System.out.println("Recovery place! Exception");
}
}

Screen shot of the console

enter image description here

Answer

Tadele Azanaw picture Tadele Azanaw · May 14, 2018

I finally got the answer.

For a method annotated with @Recover to be invoked, it has to have the same method argument(plus the exception) and the same return type.

I tested it with different type of exception argument and methods are called if they have more specific exception type. If I have a method like this will be called than one with Exception argument. However, if I have multiple recover methods, only one with the more specific exception argument will be called.

@Recover
public String helpHere(ArithmeticException cause) {

Final code Example

package hello;

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

@Service
public class SomeService {

@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 3000))
public String getInfo() {
        try {
        System.out.println("How many time will this be printed?");
        return "Hello" + 4/0;
    } catch(Exception ex) {
            System.out.println("In the arthemetic Exception");
            throw new ArithmeticException();
    }   
}

@Recover
public String helpHere(ArithmeticException cause) {

        System.out.println("Recovery place! ArithmeticException");
        return "Hello";
}
@Recover
public String helpHere(Exception cause ) {

        System.out.println("Recovery place! Exception");
        return "Hello";
}

@Recover
public String helpHere() {
        System.out.println("Recovery place! Exception");
        return "Hello";
}

@Recover
public String helpHere(Throwable cause) {

        System.out.println("Recovery place! Throwable");
        return "Hello";
}

enter image description here