Circuit Breaker Pattern in Spring Boot using Resilience4j

Introduction
The Circuit Breaker pattern is a design pattern used to detect failures and prevent cascading failures in distributed systems. It helps build resilient and fault-tolerant applications by temporarily disabling failing services to prevent system overload.
How Circuit Breaker Works
States
- CLOSED: Normal operation state
- Requests pass through to the service
- Failures are counted
- Transitions to OPEN when failure threshold is reached
- OPEN: Failure state
- Requests immediately fail without calling service
- After wait duration, transitions to HALF_OPEN
- HALF_OPEN: Recovery testing state
- Limited requests allowed through
- Success leads to CLOSED state
- Failure leads back to OPEN state
Flow Diagram
stateDiagram-v2
[*] --> CLOSED
CLOSED --> OPEN: Failure threshold reached
OPEN --> HALF_OPEN: Wait duration expired
HALF_OPEN --> CLOSED: Success threshold reached
HALF_OPEN --> OPEN: Failure threshold reached
Implementation in Spring Boot
1. Dependencies
Add these to your pom.xml
:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
2. Configuration
Create application.yml
:
resilience4j:
circuitbreaker:
instances:
orderService:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
recordExceptions:
- java.io.IOException
- java.util.concurrent.TimeoutException
3. Service Implementation
@Service
public class OrderService {
private final RestTemplate restTemplate;
@Autowired
public OrderService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@CircuitBreaker(name = "orderService", fallbackMethod = "fallbackForGetOrders")
public List<Order> getOrders() {
return restTemplate.getForObject("http://order-api/orders", List.class);
}
public List<Order> fallbackForGetOrders(Exception ex) {
// Return cached or default response
return Arrays.asList(new Order("Fallback-Order"));
}
}
4. Controller Implementation
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
@Autowired
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping
public ResponseEntity<List<Order>> getOrders() {
return ResponseEntity.ok(orderService.getOrders());
}
}
5. Circuit Breaker Configuration Class
@Configuration
public class CircuitBreakerConfig {
@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofMillis(1000))
.slidingWindowSize(2)
.build();
return CircuitBreakerRegistry.of(circuitBreakerConfig);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Advanced Features
1. Customizing Circuit Breaker Behavior
@CircuitBreaker(name = "orderService",
fallbackMethod = "fallbackForGetOrders",
ignore = {IllegalArgumentException.class})
2. Event Monitoring
@PostConstruct
public void init() {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("orderService");
circuitBreaker.getEventPublisher()
.onSuccess(event -> log.info("Call succeeded"))
.onError(event -> log.error("Call failed"))
.onStateTransition(event -> log.info("State changed: {}", event.getStateTransition()));
}
3. Metrics Integration
@Bean
MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
@PostConstruct
public void init() {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("orderService");
TaggedCircuitBreakerMetrics
.ofCircuitBreakerRegistry(circuitBreakerRegistry)
.bindTo(meterRegistry);
}
Use Cases
- External API Calls
@CircuitBreaker(name = "externalApiService")
public ApiResponse callExternalApi() {
return restTemplate.getForObject("http://external-api/data", ApiResponse.class);
}
- Database Operations
@CircuitBreaker(name = "databaseService")
public List<User> getAllUsers() {
return userRepository.findAll();
}
- File Operations
@CircuitBreaker(name = "fileService")
public String readFile(String path) {
return Files.readString(Path.of(path));
}
Best Practices
- Configuration Guidelines
- Set appropriate window size based on traffic
- Configure meaningful failure thresholds
- Use appropriate wait duration
- Include relevant exceptions only
resilience4j:
circuitbreaker:
instances:
orderService:
slidingWindowSize: 100
failureRateThreshold: 50
waitDurationInOpenState: 10s
- Error Handling
public List<Order> fallbackForGetOrders(Exception ex) {
log.error("Circuit breaker fallback triggered", ex);
if (ex instanceof TimeoutException) {
return getCachedOrders();
}
return getDefaultOrders();
}
- Monitoring Setup
@Bean
public ActuatorEndpoint circuitBreakerEndpoint(
CircuitBreakerRegistry circuitBreakerRegistry) {
return new CircuitBreakerEndpoint(circuitBreakerRegistry);
}
Common Scenarios and Solutions
- Handling Timeouts
@CircuitBreaker(name = "timeoutService")
@Timeout(value = 2, unit = ChronoUnit.SECONDS)
public Response slowOperation() {
return externalService.call();
}
- Bulk Operations
@CircuitBreaker(name = "bulkService")
public List<Response> bulkOperation(List<Request> requests) {
return requests.stream()
.map(this::processSingle)
.collect(Collectors.toList());
}
- Combining with Retry Pattern
@CircuitBreaker(name = "retryService")
@Retry(name = "retryService")
public Response operationWithRetry() {
return externalService.call();
}
This is a very insightful explanation of the Circuit Breaker pattern. It highlights the importance of handling failures in distributed systems effectively. The approach of temporarily disabling failing services seems practical and efficient. Itβs great to see how this pattern contributes to building resilient applications. How does this pattern handle the recovery of services after they have been disabled?
Resilience4j’s circuit-breaker pattern is designed to detect failures and prevent a system from performing actions that are likely to fail, thereby avoiding cascading failures. When a service is “disabled” due to repeated failures (i.e., the circuit opens), Resilience4j handles recovery through a state transition process:
π States of a Resilience4j Circuit Breaker
Closed β Normal operation; all requests go through. Failures are counted.
Open β The circuit is “tripped”; no calls go through. All requests are failed immediately.
Half-Open β A trial state; limited requests are allowed to test if the underlying service has recovered.
π§ How Recovery Works
When the circuit is in the Open state:
It stays open for a configurable waitDurationInOpenState (e.g., 60 seconds).
After this duration, it transitions to Half-Open, allowing a limited number of test requests (permittedNumberOfCallsInHalfOpenState).
In the Half-Open state:
If the test calls succeed, the circuit transitions to Closed, fully restoring traffic to the service.
If the test calls fail, the circuit transitions back to Open, and the recovery cycle restarts.