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();
}