Circuit Breaker Pattern in Spring Boot using Resilience4j

2

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

  1. CLOSED: Normal operation state
  • Requests pass through to the service
  • Failures are counted
  • Transitions to OPEN when failure threshold is reached
  1. OPEN: Failure state
  • Requests immediately fail without calling service
  • After wait duration, transitions to HALF_OPEN
  1. 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

  1. External API Calls
@CircuitBreaker(name = "externalApiService")
public ApiResponse callExternalApi() {
    return restTemplate.getForObject("http://external-api/data", ApiResponse.class);
}
  1. Database Operations
@CircuitBreaker(name = "databaseService")
public List<User> getAllUsers() {
    return userRepository.findAll();
}
  1. File Operations
@CircuitBreaker(name = "fileService")
public String readFile(String path) {
    return Files.readString(Path.of(path));
}

Best Practices

  1. 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
  1. Error Handling
public List<Order> fallbackForGetOrders(Exception ex) {
    log.error("Circuit breaker fallback triggered", ex);
    if (ex instanceof TimeoutException) {
        return getCachedOrders();
    }
    return getDefaultOrders();
}
  1. Monitoring Setup
@Bean
public ActuatorEndpoint circuitBreakerEndpoint(
        CircuitBreakerRegistry circuitBreakerRegistry) {
    return new CircuitBreakerEndpoint(circuitBreakerRegistry);
}

Common Scenarios and Solutions

  1. Handling Timeouts
@CircuitBreaker(name = "timeoutService")
@Timeout(value = 2, unit = ChronoUnit.SECONDS)
public Response slowOperation() {
    return externalService.call();
}
  1. Bulk Operations
@CircuitBreaker(name = "bulkService")
public List<Response> bulkOperation(List<Request> requests) {
    return requests.stream()
        .map(this::processSingle)
        .collect(Collectors.toList());
}
  1. Combining with Retry Pattern
@CircuitBreaker(name = "retryService")
@Retry(name = "retryService")
public Response operationWithRetry() {
    return externalService.call();
}

2 thoughts on “Circuit Breaker Pattern in Spring Boot using Resilience4j

  1. 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?

    1. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *