Java’s circuit breaker implementations, particularly with Resilience4j, are less about "preventing failures" and more about "gracefully degrading service when upstream dependencies inevitably fail."
Let’s watch a simple RestTemplate call protected by a Resilience4j CircuitBreaker fail, then recover.
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
public class CircuitBreakerExample {
public static void main(String[] args) {
// 1. Configure the Circuit Breaker
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 50% of calls must fail to trip the breaker
.waitDurationInOpenState(Duration.ofSeconds(5)) // Stay open for 5 seconds
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
.slidingWindowSize(10) // Consider the last 10 calls
.build();
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("myService");
// 2. Configure a Retry mechanism (optional but good practice)
RetryConfig retryConfig = RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.ofMillis(500))
.build();
Retry retry = Retry.of("myService", retryConfig);
// 3. Create a decorated callable
RestTemplate restTemplate = new RestTemplate();
String targetUrl = "http://localhost:8081/api/data"; // This service will be down
// Simulate a call that will fail
Runnable decoratedCall = CircuitBreaker.decorateRunnable(circuitBreaker, () -> {
try {
System.out.println("Attempting to call: " + targetUrl);
restTemplate.getForObject(targetUrl, String.class);
} catch (Exception e) {
System.err.println("Call failed: " + e.getMessage());
throw e; // Re-throw to let CircuitBreaker know it's a failure
}
});
// Decorate with retry as well
decoratedCall = Retry.decorateRunnable(retry, decoratedCall);
// Simulate multiple calls to trip the breaker
System.out.println("--- Initializing failures ---");
for (int i = 0; i < 15; i++) {
try {
decoratedCall.run();
} catch (Exception e) {
// Ignore exceptions here as we expect them to fail initially
}
try {
Thread.sleep(200); // Small delay between attempts
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("\n--- Breaker state after failures ---");
System.out.println("Breaker state: " + circuitBreaker.getState());
// After 5 seconds, the breaker will transition to HALF-OPEN
System.out.println("\n--- Waiting for breaker to transition to HALF-OPEN ---");
try {
Thread.sleep(6000); // Wait longer than waitDurationInOpenState
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// Simulate a successful call when the breaker is HALF-OPEN
// (You'd need to start the target service here for this to succeed)
System.out.println("\n--- Attempting call in HALF-OPEN state ---");
try {
decoratedCall.run(); // This will likely fail if the service is still down
System.out.println("Call succeeded in HALF-OPEN state!");
} catch (Exception e) {
System.err.println("Call failed in HALF-OPEN state: " + e.getMessage());
}
System.out.println("\n--- Breaker state after HALF-OPEN attempt ---");
System.out.println("Breaker state: " + circuitBreaker.getState());
}
}
When you run this, you’ll see the initial calls fail, the breaker trip to OPEN, and then, after the waitDurationInOpenState, it will move to HALF-OPEN for a single test call. If that call succeeds, it closes; if it fails, it re-opens.
The core problem circuit breakers solve isn’t preventing errors, but managing the cascade of errors when a dependency is overloaded or unavailable. Imagine a service that calls an external API. If that API slows down, your service starts holding open threads waiting for responses. If enough threads are held, your service itself becomes unresponsive, impacting its callers, and so on. A circuit breaker acts as a safety valve: when it detects too many failures from a dependency, it "opens" and immediately fails calls to that dependency, rather than letting them hang or consume resources. This protects your service from becoming a victim of its own dependency’s problems.
The mental model is a traffic light:
- CLOSED: Normal operation. Requests are allowed through. Failures are counted. If the failure rate exceeds a threshold, the light turns yellow.
- OPEN: The circuit is tripped. Requests are immediately rejected (fail-fast) without even attempting to reach the dependency. This is a timeout period.
- HALF-OPEN: After the
waitDurationInOpenState, the light turns yellow. A single request is allowed through. If it succeeds, the light turns green (CLOSED). If it fails, it immediately turns red again (OPEN).
The levers you control are primarily within CircuitBreakerConfig:
failureRateThreshold(50): The percentage of calls within theslidingWindowSizethat must fail for the breaker to trip.waitDurationInOpenState(Duration.ofSeconds(5)): How long the breaker stays in theOPENstate before transitioning toHALF-OPEN.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED): Defines how the failures are counted.COUNT_BASEDuses the lastNcalls, whileTIME_BASEDuses calls within a specific time window.slidingWindowSize(10): The number of calls (forCOUNT_BASED) or the time window (forTIME_BASED) to consider for calculating the failure rate.
The one thing most people don’t know is that the HALF-OPEN state isn’t just a random test; it’s specifically designed to allow your service to automatically recover as soon as the dependency starts working again, without manual intervention. The configuration of waitDurationInOpenState and slidingWindowSize in HALF-OPEN dictates how quickly you can detect and rejoin a recovering service.
The next concept to explore is how to integrate this with reactive programming models like Spring WebFlux and Project Reactor.