Circuit Breaker with Resilience4j — Production-Ready Java Implementation Guide
In this tutorial, you will learn about Circuit Breaker with Resilience4j. We cover key concepts, practical examples, and best practices to help you master this topic.
Resilience4j provides a production-ready circuit breaker library for Java with sliding window statistics, configurable failure rate thresholds, wait duration, event publishing, and seamless Spring Boot integration for microservices resilience.
flowchart TD
R[Request] --> CB[Resilience4j
CircuitBreaker]
CB -->|Closed| S[Call Service]
S -->|Success| Ok[Return]
S -->|Fail| Count[Count Failure]
Count -->|Rate > Threshold| Open[Open Circuit]
Open -->|Wait Duration| Half[Half-Open]
Half -->|Probe Success| Close[Close Circuit]
Half -->|Probe Fail| Reopen[Re-open]
CB -->|Events| Event[Event Publisher]
What You'll Learn
- Resilience4j circuit breaker configuration
- Sliding window statistics
- Event handling and notification
- Spring Boot integration
- Metrics and monitoring
Why It Matters
Resilience4j is the modern standard for Java circuit breakers, replacing Hystrix. It provides lightweight, modular resilience primitives with functional programming support and deep integration with Spring Boot, Micrometer, and Reactor.
Real-World Use
DodaTech's Java microservices use Resilience4j with Spring Boot. Each service gateway has circuit breakers configured with 100-request sliding Windows, 50% failure thresholds, and 30-second wait durations. The circuit breaker events feed a Grafana dashboard showing per-service health.
Basic Configuration
import io.github.resilience4j.circuitbreaker.*;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import java.time.Duration;
import java.util.function.Supplier;
public class CircuitBreakerExample {
public static void main(String[] args) {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.slidingWindowType(SlidingWindowType.COUNT_BASED)
.slidingWindowSize(10)
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30))
.permittedNumberOfCallsInHalfOpenState(3)
.recordExceptions(IOException.class, TimeoutException.class)
.build();
CircuitBreaker circuitBreaker = CircuitBreaker
.of("payment-service", config);
circuitBreaker.getEventPublisher()
.onStateTransition(event ->
System.out.println("State: " + event.getStateTransition()))
.onFailureRateExceeded(event ->
System.out.println("Failure rate exceeded: "
+ event.getFailureRate()));
Supplier<String> decorated = CircuitBreaker
.decorateSupplier(circuitBreaker,
() -> callPaymentService());
for (int i = 0; i < 15; i++) {
try {
String result = Try.ofSupplier(decorated)
.recover(throwable -> "Fallback response")
.get();
System.out.println("Result: " + result);
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
static String callPaymentService() {
if (Math.random() < 0.6) throw new RuntimeException("Service error");
return "Payment processed";
}
}
Expected output:
State: CLOSED_TO_OPEN
Result: Payment processed
Result: Fallback response
Failure rate exceeded: 60.0
State: OPEN_TO_HALF_OPEN
Result: Payment processed
State: HALF_OPEN_TO_CLOSED
Spring Boot Integration
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@CircuitBreaker(name = "catalog-service",
fallbackMethod = "getProductFallback")
public Product getProduct(String productId) {
return catalogClient.fetchProduct(productId);
}
public Product getProductFallback(String productId,
Throwable throwable) {
System.out.println("Fallback for product " + productId
+ ": " + throwable.getMessage());
return new Product(productId, "Unavailable", 0.0);
}
}
Application properties:
resilience4j.circuitbreaker.instances.catalog-service.sliding-window-size=100
resilience4j.circuitbreaker.instances.catalog-service.failure-rate-threshold=50
resilience4j.circuitbreaker.instances.catalog-service.wait-duration-in-open-state=30s
resilience4j.circuitbreaker.instances.catalog-service.permitted-number-of-calls-in-half-open-state=5
resilience4j.circuitbreaker.instances.catalog-service.slow-call-rate-threshold=50
resilience4j.circuitbreaker.instances.catalog-service.slow-call-duration-threshold=5s
Expected Spring Boot log output:
CircuitBreaker 'catalog-service' recorded a failure
CircuitBreaker 'catalog-service' state changed from CLOSED to OPEN
CircuitBreaker 'catalog-service' recorded a call in OPEN state
Metrics Integration
import io.github.resilience4j.micrometer.*;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
public class MetricsExample {
public static void main(String[] args) {
PrometheusMeterRegistry registry =
new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
CircuitBreaker circuitBreaker = CircuitBreaker
.ofDefaults("order-service");
TaggedCircuitBreakerMetrics
.ofCircuitBreakerRegistry(
circuitBreaker.getCircuitBreakerRegistry())
.bindTo(registry);
circuitBreaker.getEventPublisher()
.onCallNotPermitted(event ->
System.out.println("Call blocked: "
+ event.getCircuitBreakerName()));
String metrics = registry.scrape();
System.out.println("Metrics:");
System.out.println(metrics);
}
}
Expected output:
Metrics:
resilience4j_circuitbreaker_state{name="order-service",state="closed"} 1
resilience4j_circuitbreaker_calls{name="order-service",kind="successful"} 42
resilience4j_circuitbreaker_calls{name="order-service",kind="failed"} 5
resilience4j_circuitbreaker_calls{name="order-service",kind="not_permitted"} 3
Common Mistakes
- Sliding window too small -- a window of 5 samples gives high variance. Set sliding_window_size to 20-100 for stable failure rate estimates. Use time-based sliding windows for variable traffic patterns.
- Failure rate threshold too low -- 10% failure rate trips on 1 failure in a 10-sample window. Start at 50% and adjust based on service reliability. Payment services tolerate lower thresholds than recommendation services.
- No slow call threshold -- Resilience4j treats slow calls as failures by default. Set slow_call_duration_threshold to the service's p99 latency. Calls exceeding this count toward the failure rate.
- Ignoring half-open configuration -- default half-open permits 3 calls. For high-traffic services, increase to 10-20 for faster recovery. Monitor half-open probe success rate separately from normal traffic.
- No fallback method -- without a fallback method, circuit breaker calls throw CircuitBreakerOpenException. Define fallbacks for all @CircuitBreaker-annotated methods with appropriate degraded responses.
Practice Questions
- How does Resilience4j's sliding window differ from simple counters?
- What is the purpose of the half-open state in Resilience4j?
- How do you configure Resilience4j in Spring Boot?
- What metrics does Resilience4j expose for monitoring?
- How do slow calls affect circuit breaker behavior?
Challenge
Build a complete Resilience4j circuit breaker setup: (1) 3 circuit breakers for payment, inventory, and shipping services, (2) 100-count sliding window with 50% failure threshold, (3) slow call threshold of 5 seconds with 30% slow call rate threshold, (4) Spring Boot @CircuitBreaker annotations with fallback methods returning degraded responses, (5) Micrometer metrics exported to Prometheus, (6) event listeners that log state transitions and feed a monitoring dashboard, (7) custom exception recording that ignores specific exceptions (like validation errors).
FAQ
Mini Project
Build a Spring Boot microservice with full Resilience4j integration: (1) 4 circuit breakers for different external service dependencies, (2) combined with retry (max 3, exponential backoff) and bulkhead (5 concurrent calls) patterns, (3) custom fallback responses per circuit breaker with stale cached data, (4) Micrometer + Prometheus endpoint exposing circuit breaker metrics, (5) Actuator health endpoint showing circuit breaker states, (6) scheduled task that tests circuit breaker recovery and logs state transitions, (7) integration tests that verify circuit breaker opens and recovers correctly.
What's Next
Continue with Hystrix Migration to learn migrating from Hystrix to Resilience4j. Then explore Python Implementation for Python-based circuit breaker patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro