Resilience4j is a lightweight fault tolerance library inspired by Netflix Hystrix but designed for Java 8 and functional programming. It provides several resilience patterns like Circuit Breaker, Rate Limiter, and Retry, which are particularly useful for building resilient microservices. The integration with Spring Boot allows developers to use these patterns with minimal configuration, leveraging Spring’s auto-configuration capabilities.
Setting Up Your Project
To get started, you’ll need to set up a Spring Boot 3 project:
1. Create a New Project
Use Spring Initializr to generate a new Spring Boot project. Select dependencies like Spring Web, Spring Boot Actuator, and Resilience4j.
2. Add Dependencies
Ensure your pom.xml includes the necessary Resilience4j dependencies. Here’s how it might look in Maven:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot3</artifactId> </dependency> </dependencies>
Configuring Resilience4j Retry
Once your project is set up, you’ll configure the retry mechanism:
1. Application Configuration
Open your application.yml or application.properties file to add retry configurations. Here’s an example: yaml
resilience4j: retry: instances: myService: maxAttempts: 3 waitDuration: 1s retryExceptions: - org.springframework.web.client.HttpServerErrorException ignoreExceptions: - org.springframework.web.client.HttpClientErrorException
This configuration sets up a retry instance named myService that will retry operations up to three times, waiting one second between attempts for server errors but ignoring client errors.
2. Implementing Retry in the Service Layer
In your service class, use the @Retry annotation:
@Service @Slf4j public class Resilience4jService { private final Random random = new Random(); @Retry(name = "myService") public String callExternalService(String param) { log.info("Attempt #{}", LocalDateTime.now()); // Simulate an external call that might fail if (random.nextBoolean()) { throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR, "Service Unavailable"); } return "Service response"; } }
This method will be retried according to the configuration if it throws an HttpServerErrorException.
Handling Fallbacks
Sometimes, you might want to provide a fallback method if all retries fail:
@Service @Slf4j public class Resilience4jService { private final Random random = new Random(); @Retry(name = "myService", fallbackMethod = "fallback") public String callExternalService(String param) { log.info("Attempt #{}", LocalDateTime.now()); // Simulate an external call that might fail if (random.nextBoolean()) { throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR, "Service Unavailable"); } return "Service response"; } public String fallback(String param, Throwable throwable) { return "Fallback response due to: " + throwable.getMessage(); } }
Monitoring and Metrics
Resilience4j integrates with Spring Boot Actuator to provide metrics:
Enable Actuator: Ensure your application.yml exposes the relevant endpoints: yaml
management: endpoints: web: exposure: include: "*" # Exposes all actuator endpoints endpoint: health: show-details: always # Show detailed health information
Access Metrics: You can now access retry metrics via endpoints.
http://localhost:8080/actuator/retryevents
{ "retryEvents": [ { "retryName": "myService", "type": "RETRY", "creationTime": "2024-11-27T15:33:45.129577800+07:00[Asia/Bangkok]", "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 Service Unavailable", "numberOfAttempts": 2 }, { "retryName": "myService", "type": "ERROR", "creationTime": "2024-11-27T15:33:46.140762900+07:00[Asia/Bangkok]", "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 Service Unavailable", "numberOfAttempts": 3 } ] }
Practical Example
Let’s consider a practical scenario where you’re making an API call to an external service that might be temporarily down or slow:
@RestController @RequestMapping("/api/v1") @RequiredArgsConstructor public class MyController { private final Resilience4jService resilience4jService; @GetMapping("/resilience4j-retry") public ResponseEntity<ApiResponse> resilience4jRetry() { try { String result = resilience4jService.callExternalService("Hello world"); 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())); } } }
Create a response bean.
import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class ApiResponse { private boolean success; private String data; private String error; }
Test Resilience4j retry by Postman.
Spring Boot application using Postman involves setting up a retry mechanism in your service and invoking it through a REST API endpoint.
Test Resilience4j Retry
1. The client sends a request to the REST API endpoint.
http://localhost:8080/api/v1/resilience4j-retry

In the console log. Retry attempt 3 times.
Attempt #2024-11-27T15:55:21.906816100 Attempt #2024-11-27T15:55:22.916523200 Attempt #2024-11-27T15:55:23.927661800

Test result: The server response error with HTTP status 500. because Retry attempts more than 3 times, but gets an exception every time.
2. The client sends a request to the REST API endpoint.
http://localhost:8080/api/v1/resilience4j-retry

In console log. Retry attempt 2 times.
Attempt #2024-11-27T16:00:30.840048 Attempt #2024-11-27T16:00:31.849122300

Test result: The server response error with HTTP status 200. because Retry attempts less than 3 times.
Test Resilience4j Retry and fallback
1. The client sends a request to the REST API endpoint.
http://localhost:8080/api/v1/resilience4j-retry

In the console log. Retry attempt 3 times.
Attempt #2024-11-27T16:04:23.088632800 Attempt #2024-11-27T16:04:24.099379300 Attempt #2024-11-27T16:04:25.110237700

Test result: The server response error with HTTP status 200. because Retry attempts more than 3 times and gets an error message from a fallback method instead.
Best Practices
- Idempotency: Ensure that retry operations are idempotent to avoid unintended side effects.
- Backoff Strategies: Use exponential backoff or randomized wait times to prevent overwhelming the downstream service (enableRandomizedWait or enableExponentialBackoff in configurations).
- Conditional Retries: Use conditions to retry only on specific outcomes or exceptions for more precise control (retryOnResult or retryOnException in configurations).
Conclusion
Integrating Resilience4j’s retry mechanism into a Spring Boot 3 application significantly enhances the resilience of your services. It helps manage transient failures, thereby improving the reliability and availability of your application. Following this tutorial, you’ve learned how to set up, configure, and utilize retry patterns effectively, ensuring your application can withstand temporary disruptions without compromising user experience.
Explore other Resilience4j patterns like Circuit Breaker or Time Limiter for further learning to build even more robust applications. Remember, resilience isn’t just about handling failures but also about making your system more predictable and manageable in the face of adversity.