In today’s fast-paced software development world, resilience is a critical application feature. Network issues, database downtime, or third-party service failures are common scenarios that can lead to temporary disruptions. Instead of letting such failures crash your application, implementing a retry mechanism allows your system to recover gracefully.
Spring Boot provides a robust way to implement retries using the spring-retry
module. This article explores how to use Spring Retry in Spring Boot, covering its configuration, implementation, and best practices for building resilient applications.
What is Spring Retry?
Spring Retry is a library designed to support retry operations in Spring applications. It enables developers to handle transient issues by repeatedly retrying failed operations before giving up. This mechanism can be used in scenarios such as:
- Retrying failed API calls to a third-party service.
- Retrying database transactions in case of temporary issues.
- Implementing exponential backoff to prevent resource overload.
The library integrates seamlessly with Spring Boot, making it easy to add retry logic to your applications with minimal configuration.
Key Features of Spring Retry
- Retry Policies: Customize retry behavior, including the number of attempts, exception types to retry, and back-off strategies.
- Backoff Policies: Implement fixed or exponential delays between retries to avoid overwhelming external systems.
- Integration with Annotations: Use annotations like
@Retryable
and@Recover
for declarative retry handling. - Stateful and Stateless Retries: Choose between stateful retries that track invocation state and stateless retries that treat every attempt independently.
Why Use Retry Mechanisms?
Retry mechanisms enhance the resilience and reliability of your application. They allow you to:
- Handle Transient Failures: Retry operations that fail due to temporary issues, such as network latency or service unavailability.
- Improve User Experience: Ensure smoother interactions for end-users by mitigating failures automatically.
- Reduce Manual Intervention: Automate recovery processes to minimize downtime and reduce the need for human intervention.
- Implement Circuit Breaker Patterns: Combine retries with circuit breakers to prevent overloading failing systems.
Setting Up Spring Retry in Spring Boot
Step 1: Add Spring Retry Dependency
To get started with Spring Retry, include the following dependency in your pom.xml
file:
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>2.0.10</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
The spring-retry
dependency enables retry mechanisms, while the spring-boot-starter-aop
dependency allows you to use annotations for declarative retry.
Step 2: Enable Spring Retry
In your Spring Boot application, enable retry by adding the @EnableRetry
annotation to your configuration class:
import org.springframework.context.annotation.Configuration; import org.springframework.retry.annotation.EnableRetry; @Configuration @EnableRetry public class AppConfig { }
Using @Retryable and @Recover Annotations
@Retryable
The @Retryable
annotation marks methods for retry operations. You can configure parameters like:
maxAttempts
: Maximum number of retry attempts.retryFor
: Specify exceptions to retry.exclude
: Specify exceptions to avoid retrying.backoff
: Define delay or backoff strategy.
import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; @Service @Slf4j public class ApiService { @Retryable( retryFor = {RuntimeException.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 2) ) public String fetchData() { log.info("Attempting to fetch data..."); if (Math.random() > 0.5) { throw new RuntimeException("Temporary API failure"); } return "Data retrieved successfully"; } }
In this example:
- The method
fetchData
will retry up to 3 times if aRuntimeException
occurs. - There’s an initial delay of 2 seconds between retries, doubling on each subsequent attempt (exponential backoff).
@Service @Slf4j public class RetryService { private int attemptCount = 0; @Retryable( retryFor = {RuntimeException.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 2) ) public String callExternalService(){ attemptCount++; log.info("Attempting to call external service. Attempt #" + attemptCount); // Simulate a service that fails the first two times if (attemptCount < 3) { log.info("Service temporarily unavailable"); throw new RuntimeException("Service temporarily unavailable"); } return "Service call successful!"; } }
In this example:
- The method
callExternalService
will retry up to 3 times if aRuntimeException
occurs. - There’s an initial delay of 2 seconds between retries, doubling on each subsequent attempt (exponential backoff).
@Recover
The @Recover
annotation provides a fallback method if all retry attempts fail. The fallback method must have the same return type and accept the exception as the first parameter.
Example:
import org.springframework.retry.annotation.Recover; import org.springframework.stereotype.Service; @Service @Slf4j public class RetryService { @Retryable(retryFor = {RuntimeException.class}, maxAttempts = 3) public String fetchData() { log.info("Attempting to fetch data..."); throw new RuntimeException("API failure"); } @Recover public String recover(RuntimeException e) { log.info("Recovering from failure: " + e.getMessage()); return "Default data"; } }
Here:
- If all retry attempts fail, the
recover
method is called to provide a fallback response.
REST API example
Demonstrates calling the retry service with different scenarios.
@RestController @RequestMapping("/api/v1") @RequiredArgsConstructor public class RetryController { private final RetryService retryService; @GetMapping("/simple-retry") public ResponseEntity<ApiResponse> simpleRetry() { try { String result = retryService.callExternalService(); return ResponseEntity.ok(new ApiResponse(true, result, null)); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ApiResponse(false, null, e.getMessage())); } } @GetMapping("/simple-recovery") public ResponseEntity<ApiResponse> simpleRecovery() { try { String result = retryService.fetchData(); return ResponseEntity.ok(new ApiResponse(true, result, null)); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ApiResponse(false, null, e.getMessage())); } } }
1. Simple Retry (GET /api/v1/simple-retry
):
- Basic retry functionality
- Fixed number of attempts
- Exponential backoff
2. Retry with recovery (GET /api/v1/simple-recovery
):
- Basic recovery functionality
- Can force errors for testing
- Custom recovery method for response
Test Spring Boot Retry annotation.
To test the @Retryable
annotation in a Spring Boot application using Postman.

1. Test: send a request to GET /api/v1/simple-retry

Attempting to call external service. Attempt #1 Service temporarily unavailable Attempting to call external service. Attempt #2 Service temporarily unavailable Attempting to call external service. Attempt #3

Test result conclusion when the client sends a request to the server. The server causes an error RuntimeException 2 times, then sends a response successfully. This test result followed expectations.
2. Test: send a request to GET /api/v1/simple-retry

//log in application Attempting to fetch data... Attempting to fetch data... Attempting to fetch data... Recovering from failure: API failure

Test result conclusion when the client sends a request to the server. The server causes a RuntimeException 3 times, then goes to the recovery method and sends a response Default data
message. This test result followed expectations.
Stateful vs. Stateless Retries
Spring Retry supports two types of retries:
- Stateless Retries: Default behavior where each retry is treated independently. Useful for idempotent operations.
- Stateful Retries: Retaining state across retries is beneficial for non-idempotent operations, such as database transactions.
Enable stateful retries by setting the stateful
attribute in @Retryable
to true
.
Example:
@Retryable(stateful = true) public void processTransaction() { // Non-idempotent operation }
Customizing Retry Policies
RetryTemplate
For advanced use cases, you can define a RetryTemplate
bean for programmatic retry handling.
Example:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.RetryTemplate; @Configuration public class RetryConfig { @Bean public RetryTemplate retryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); // Configure retry policy SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(5); retryTemplate.setRetryPolicy(retryPolicy); return retryTemplate; } }
Best Practices for Spring Retry
- Use Exponential Backoff: Prevent overwhelming external systems by gradually increasing the delay between retries.
- Handle Specific Exceptions: Avoid broad exception handling; retry only on transient errors like
IOException
orTimeoutException
. - Limit Retry Attempts: Set a reasonable number of retries to avoid unnecessary resource consumption.
- Monitor and Log Retries: Log retry attempts for better visibility and troubleshooting.
- Combine with Circuit Breakers: Use libraries like Resilience4j alongside Spring Retry for enhanced fault tolerance.
Common Use Cases
- External API Calls: Retry API calls in case of temporary service outages or network errors.
- Database Operations: Retry database queries that fail due to transient connectivity issues.
- Messaging Systems: Handle message delivery failures in systems like RabbitMQ or Kafka.
- File Uploads: Retry file uploads in case of intermittent failures.
Common Mistakes
- Misusing the
@Retryable
Annotation: Placing@Retryable
on a private method or a method that is not called through a Spring-managed proxy. - Ignoring Unsupported Exceptions: Not specifying the exception types to retry in the
retryFor
parameter of@Retryable
. - Forgetting
@Recover
for Recovery Logic: No fallback mechanism when all retry attempts fail. - Not Configuring
maxAttempts
andbackoff
Properly: Too many retries or short/long delays lead to performance degradation or long delays. - Ignoring Thread-Safety: Using shared resources (e.g., mutable objects) in
retryable
methods without ensuring thread safety.
Conclusion
Spring Retry provides a powerful mechanism for building resilient Spring Boot applications. By leveraging @Retryable
and @Recover
annotations or customizing retry policies with RetryTemplate
, developers can gracefully handle transient failures, enhance user experience, and maintain application stability.