A circuit breaker doesn’t just prevent cascading failures; it actively helps your system recover faster by giving failing services a chance to breathe.

Let’s see this in action. Imagine you have a microservice, OrderService, that calls another service, InventoryService, to check stock. If InventoryService starts failing, OrderService should stop bombarding it with requests.

Here’s a simplified Java example using Resilience4j, a popular Java library for fault tolerance:

// Configure the circuit breaker
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
    .failureRateThreshold(50) // Open the circuit after 50% failures
    .waitDurationInOpenState(Duration.ofSeconds(10)) // Stay open for 10 seconds
    .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
    .slidingWindowSize(10) // Consider the last 10 calls
    .recordExceptions(IOException.class, TimeoutException.class) // Exceptions that count as failures
    .build();

CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(circuitBreakerConfig);
CircuitBreaker circuitBreaker = registry.circuitBreaker("inventoryService");

// Decorate the call to InventoryService
Supplier<Boolean> stockCheck = () -> {
    try {
        // Actual call to InventoryService
        return inventoryServiceClient.checkStock("some-item");
    } catch (Exception e) {
        // Exceptions are automatically handled by the circuit breaker if recorded
        throw new RuntimeException(e);
    }
};

// Wrap the call with the circuit breaker
Supplier<Boolean> protectedStockCheck = CircuitBreaker.decorateSupplier(circuitBreaker, stockCheck);

try {
    boolean isInStock = protectedStockCheck.get();
    if (isInStock) {
        // Proceed with order
    }
} catch (CallNotPermittedException e) {
    // Circuit is open, handle gracefully (e.g., return cached data, inform user)
    System.out.println("Inventory service is currently unavailable. Circuit breaker is open.");
} catch (RuntimeException e) {
    // Other exceptions during the call (e.g., network issues before breaker tripped)
    System.err.println("Error checking stock: " + e.getMessage());
}

In this snippet, OrderService is the caller, and InventoryService is the dependency. The CircuitBreaker acts as a proxy.

The CircuitBreakerConfig defines the rules:

  • failureRateThreshold(50): If 50% of the last 10 calls to InventoryService fail, the circuit breaker trips.
  • waitDurationInOpenState(Duration.ofSeconds(10)): Once tripped, it stays open for 10 seconds, preventing any further calls.
  • slidingWindowSize(10): The failure rate is calculated based on the last 10 calls.
  • recordExceptions(...): Specifies which exceptions indicate a failure.

When the circuit is closed, requests flow through to InventoryService. If failures accumulate and cross the failureRateThreshold, the circuit transitions to the open state. In this state, subsequent calls to protectedStockCheck.get() will immediately throw a CallNotPermittedException without even attempting to contact InventoryService. This is crucial: it stops OrderService from wasting resources and allows InventoryService to potentially recover.

After the waitDurationInOpenState elapses, the circuit breaker enters a half-open state. It allows a limited number of test calls (typically one) to go through. If these test calls succeed, the circuit breaker resets to closed. If they fail, it immediately returns to the open state for another waitDurationInOpenState.

The mental model is that of an electrical circuit breaker. When too much current (failures) flows, it trips, breaking the connection (stopping calls) to prevent damage (cascading failures). After a cool-down period, it allows a small test to see if the fault has cleared.

The primary problem circuit breakers solve is cascading failure. If InventoryService is slow or down, and OrderService keeps calling it, OrderService’s threads or resources get tied up. If other services depend on OrderService, they too will start failing, and the problem spreads like a virus. By tripping the circuit, OrderService immediately stops making bad calls, freeing up its resources and preventing the issue from propagating. It also gives the failing InventoryService a break, potentially allowing it to recover faster.

What most people don’t realize is that the waitDurationInOpenState is not just a timeout; it’s a recovery window. During this time, the failing service is not being hammered. This is essential. If a service is overloaded, the worst thing you can do is keep sending it more requests. The circuit breaker’s open state is a deliberate pause, a cooling-off period that can be more effective than any retry strategy.

The next concept to explore is how to implement different fallback strategies when a circuit breaker is open, such as returning cached data or a default response.

Want structured learning?

Take the full Circuit-breaker course →