Spring Boot Retry Example

In this Spring boot tutorial, learn to use Spring retry module to invoke remote APIs and retry the request if it fails for some reason such as a network outage, server down, network glitch, deadlock etc. In such cases, we usually try to retry the operation a few times before sending an error to the client to make processing more robust and less prone to failure.

The spring-retry module provides a declarative way to configure the retries using annotations. We can also define the fallback method if all retries fail.

1. Maven

Import the latest version of  spring-retry dependency from the maven repository. Spring retry is AOP based so include the latest version of spring-aspects as well.

<dependencies>
    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>
</dependencies>

2. Enabling Spring Retry with @EnableRetry

The @EneableRetry annotation enables the spring retry feature in the application. We can apply it to any @Confguration class. The @EnableRetry scan for all @Retryable and @Recover annotated methods and proxies them using AOP. It also configures RetryListener interfaces used for intercepting methods used during retries.

@EnableRetry
@Configuration
public class RetryConfig { ... }

3. Spring Retry using Annotations

In spring-retry, we can retry the operations using the following annotations for the declarative approach.

3.3. @Retryable

It indicates a method to be a candidate for retry. We specify the exception type for which the retry should be done, the maximum number of retries and the delay between two retries using the backoff policy.

Please note that maxAttampts includes the first failure also. In other words, the first invocation of this method is always the first retry attempt. The default retry count is 3.

public interface BackendAdapter {

  @Retryable(retryFor = {RemoteServiceNotAvailableException.class},
      maxAttempts = 3,
      backoff = @Backoff(delay = 1000))
  public String getBackendResponse(String param1, String param2);

   ...
}

3.2. @Recover

It specifies the fallback method if all retries fail for a @Retryable method. The method accepts the exception that occurred in the Retryable method and its arguments in the same order.

public interface BackendAdapter {

  ...

  @Recover
  public String getBackendResponseFallback(RemoteServiceNotAvailableException e, String param1, String param2);
}

4. Using RetryTemplate for Pragammatic Approach

We can use the programmatic way for retry functionality creating RetryTemplate bean as follows:

We can create the template and use it as follows:

RetryTemplate template = RetryTemplate.builder()
				.maxAttempts(3)
				.fixedBackoff(1000)
				.retryOn(RemoteServiceNotAvailableException.class)
				.build();

template.execute(ctx -> {
    return backendAdapter.getBackendResponse(...);
});

We can also configure the RetryTemplate bean with custom default values:

@EnableRetry
@Configuration
public class RetryConfig {

  @Bean
  public RetryTemplate retryTemplate() {
    RetryTemplate retryTemplate = new RetryTemplate();

    //Fixed delay of 1 second between retries
    FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
    fixedBackOffPolicy.setBackOffPeriod(1000l);
    retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

    //Retry only 3 times
    SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
    retryPolicy.setMaxAttempts(3);
    retryTemplate.setRetryPolicy(retryPolicy);

    return retryTemplate;
  }
}

And now we use this bean for retry operations:

@Autowired
RetryTemplate retryTemplate;

template.execute(ctx -> {
    return backendAdapter.getBackendResponse(...);
});

5. Spring Retry Example

In this example, we created a Spring boot project to expose one sample Rest API which will call one backend operation. The backed operation is prone to failure, and we will fail it randomly to simulate the service failures.

The BackendAdapterImpl class implements both @Retryable and @Recover methods as declared in BackendAdapter interface. We should always use the retry annotations on interfaces, so the implementations are automatically retry enabled.

public interface BackendAdapter {

  @Retryable(retryFor = {RemoteServiceNotAvailableException.class},
      maxAttempts = 3,
      backoff = @Backoff(delay = 1000))
  public String getBackendResponse(String param1, String param2);

  @Recover
  public String getBackendResponseFallback(RemoteServiceNotAvailableException e, String param1, String param2);
}
@Slf4j
@Service
public class BackendAdapterImpl implements BackendAdapter {

  @Override
  public String getBackendResponse(String param1, String param2) {

    int random = new Random().nextInt(4);
    if (random % 2 == 0) {
      throw new RemoteServiceNotAvailableException("Remote API failed");
    }

    return "Hello from remote backend!!!";
  }

  @Override
  public String getBackendResponseFallback(RemoteServiceNotAvailableException e, String param1, String param2) {
    return "Hello from fallback method!!!";
  }
}

In the above code, the backend service will be retried 3 times. In those 3 attempts, if we get success response then that successful response will be returned else the fallback method will be called.

Create one simple RestController that will call the BackendAdapter.getBackendResponse() method where we have simulated the exception.

@RestController
public class ResourceController {

  @Autowired
  BackendAdapter backendAdapter;

  @GetMapping("/retryable-operation")
  public ResponseEntity<String> validateSpringRetryCapability(@RequestParam String param1, @RequestParam String param2) {

    String apiResponse = backendAdapter.getBackendResponse(param1, param2);
    return ResponseEntity.ok().body(apiResponse);
  }
}

Invoke the REST API and watch the logs for retry attempts:

HTTP GET http://localhost:8080/retryable-operation?param1=param1&param2=param2

The following were the logs when all 3 retries happened, and finally, the fallback method was invoked.

TRACE 3824 --- [nio-8080-exec-1] o.s.retry.support.RetryTemplate: RetryContext retrieved: [RetryContext: count=0, lastException=null, exhausted=false]
2022-12-09T13:24:04.348+05:30 DEBUG 3824 --- [nio-8080-exec-1] o.s.retry.support.RetryTemplate: Retry: count=0
2022-12-09T13:24:05.363+05:30 DEBUG 3824 --- [nio-8080-exec-1] o.s.retry.support.RetryTemplate: Checking for rethrow: count=1

2022-12-09T13:24:05.363+05:30 DEBUG 3824 --- [nio-8080-exec-1] o.s.retry.support.RetryTemplate: Retry: count=1
2022-12-09T13:24:06.375+05:30 DEBUG 3824 --- [nio-8080-exec-1] o.s.retry.support.RetryTemplate: Checking for rethrow: count=2

2022-12-09T13:24:06.375+05:30 DEBUG 3824 --- [nio-8080-exec-1] o.s.retry.support.RetryTemplate: Retry: count=2
2022-12-09T13:24:06.375+05:30 DEBUG 3824 --- [nio-8080-exec-1] o.s.retry.support.RetryTemplate: Checking for rethrow: count=3

2022-12-09T13:24:06.376+05:30 DEBUG 3824 --- [nio-8080-exec-1] o.s.retry.support.RetryTemplate: Retry failed last attempt: count=3
2022-12-09T13:24:06.392+05:30 TRACE 3824 --- [nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing ["Hello from fallback method!!!"]

The following are the logs when no failure occurred and API returned the response in the first attempt.

2022-12-09T13:24:31.411+05:30 TRACE 3824 --- [nio-8080-exec-2] o.s.retry.support.RetryTemplate          : RetryContext retrieved: [RetryContext: count=0, lastException=null, exhausted=false]

2022-12-09T13:24:31.411+05:30 DEBUG 3824 --- [nio-8080-exec-2] o.s.retry.support.RetryTemplate          : Retry: count=0

2022-12-09T13:24:31.413+05:30 TRACE 3824 --- [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing ["Hello from remote backend!!!"]

7. Conclusion

This tutorial taught us to use the spring retry module for implementing retry-based remote API invocations that can handle infrequent runtime exceptions or network failures.

Happy Learning !!

Sourcecode on Github

Leave a Reply

0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial