Introduction
Circuit Breaker is a design pattern that prevents cascading failures in distributed systems. Resilience4j is a lightweight fault tolerance library inspired by Netflix Hystrix, providing circuit breaker, rate limiter, retry, bulkhead, and time limiter patterns.
Core Concepts
Circuit Breaker States
// Three states: CLOSED -> Normal operation, requests pass through OPEN -> Short-circuited, requests fail fast HALF_OPEN -> Testing if underlying service has recovered
Setup and Dependencies
Maven Dependencies
<dependencies> <!-- Resilience4j Core --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-circuitbreaker</artifactId> <version>2.1.0</version> </dependency> <!-- Spring Boot Integration --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot2</artifactId> <version>2.1.0</version> </dependency> <!-- Micrometer Metrics --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-micrometer</artifactId> <version>2.1.0</version> </dependency> <!-- Reactor Support --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-reactor</artifactId> <version>2.1.0</version> </dependency> <!-- Retrofit Support --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-retrofit</artifactId> <version>2.1.0</version> </dependency> </dependencies>
Basic Circuit Breaker Usage
Manual Circuit Breaker Configuration
@Component
public class BasicCircuitBreakerService {
private final CircuitBreakerRegistry circuitBreakerRegistry;
private final ExternalService externalService;
public BasicCircuitBreakerService(CircuitBreakerRegistry circuitBreakerRegistry,
ExternalService externalService) {
this.circuitBreakerRegistry = circuitBreakerRegistry;
this.externalService = externalService;
}
public void demonstrateBasicUsage() {
// Create circuit breaker configuration
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // Open circuit if 50% of calls fail
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
.slidingWindowSize(10) // Last 10 calls are considered
.minimumNumberOfCalls(5) // Minimum calls before calculating error rate
.waitDurationInOpenState(Duration.ofSeconds(10)) // Time in OPEN state before trying HALF_OPEN
.permittedNumberOfCallsInHalfOpenState(3) // Number of calls in HALF_OPEN state
.recordExceptions(IOException.class, TimeoutException.class) // Exceptions to record as failures
.ignoreExceptions(BusinessException.class) // Exceptions to ignore
.build();
// Create circuit breaker instance
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("externalService", config);
// Decorate the call
Supplier<String> decoratedSupplier = CircuitBreaker.decorateSupplier(
circuitBreaker,
externalService::callExternalService
);
try {
String result = decoratedSupplier.get();
System.out.println("Success: " + result);
} catch (CallNotPermittedException e) {
System.out.println("Circuit breaker is OPEN - call blocked");
} catch (Exception e) {
System.out.println("Call failed: " + e.getMessage());
}
}
// Using Try.of with circuit breaker
public String getDataWithFallback(String id) {
return Try.ofSupplier(
CircuitBreaker.decorateSupplier(
circuitBreakerRegistry.circuitBreaker("dataService"),
() -> externalService.fetchData(id)
)
).recover(throwable -> {
// Fallback logic
return getCachedData(id).orElse("default-value");
}).get();
}
}
// Example external service
@Service
class ExternalService {
public String callExternalService() {
// Simulate external service call
if (Math.random() > 0.7) {
throw new RuntimeException("Service unavailable");
}
return "Success response";
}
public String fetchData(String id) {
// Simulate data fetch
return "Data for " + id;
}
}
Advanced Circuit Breaker Configuration
Comprehensive Configuration
@Configuration
public class CircuitBreakerConfiguration {
@Bean
public CircuitBreakerConfig defaultCircuitBreakerConfig() {
return CircuitBreakerConfig.custom()
.failureRateThreshold(50) // Percentage of failures to trigger OPEN state
.slowCallRateThreshold(50) // Percentage of slow calls to trigger OPEN
.slowCallDurationThreshold(Duration.ofSeconds(2)) // What constitutes a slow call
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED)
.slidingWindowSize(10) // Number of calls for COUNT_BASED, seconds for TIME_BASED
.minimumNumberOfCalls(10) // Minimum calls before calculating error rate
.waitDurationInOpenState(Duration.ofSeconds(30))
.permittedNumberOfCallsInHalfOpenState(5)
.recordExceptions(IOException.class, TimeoutException.class, CallNotPermittedException.class)
.ignoreExceptions(BusinessException.class, ValidationException.class)
.automaticTransitionFromOpenToHalfOpenEnabled(true)
.writableStackTraceEnabled(false) // Disable stack traces for performance
.build();
}
@Bean
public CircuitBreakerConfig strictCircuitBreakerConfig() {
return CircuitBreakerConfig.custom()
.failureRateThreshold(20) // More sensitive - opens at 20% failure rate
.slidingWindowSize(20)
.minimumNumberOfCalls(5)
.waitDurationInOpenState(Duration.ofMinutes(1))
.permittedNumberOfCallsInHalfOpenState(2)
.recordExceptions(Exception.class) // Record all exceptions
.build();
}
@Bean
public CircuitBreakerConfig lenientCircuitBreakerConfig() {
return CircuitBreakerConfig.custom()
.failureRateThreshold(80) // Less sensitive - opens at 80% failure rate
.slidingWindowSize(50)
.minimumNumberOfCalls(20)
.waitDurationInOpenState(Duration.ofSeconds(10))
.permittedNumberOfCallsInHalfOpenState(10)
.recordExceptions(IOException.class, TimeoutException.class)
.ignoreExceptions(BusinessException.class)
.build();
}
@Bean
public CircuitBreakerRegistry circuitBreakerRegistry(CircuitBreakerConfig defaultConfig) {
return CircuitBreakerRegistry.of(defaultConfig);
}
@Bean
public CircuitBreakerEventPublisher circuitBreakerEventPublisher() {
return new CircuitBreakerEventPublisher();
}
}
Spring Boot Integration
Application Configuration
# application.yml resilience4j: circuitbreaker: configs: default: slidingWindowSize: 100 failureRateThreshold: 50 waitDurationInOpenState: 10s permittedNumberOfCallsInHalfOpenState: 10 minimumNumberOfCalls: 10 slidingWindowType: COUNT_BASED recordExceptions: - org.springframework.web.client.HttpServerErrorException - java.io.IOException - java.util.concurrent.TimeoutException ignoreExceptions: - com.example.BusinessException strict: failureRateThreshold: 20 waitDurationInOpenState: 60s slidingWindowSize: 50 lenient: failureRateThreshold: 80 waitDurationInOpenState: 5s instances: userService: baseConfig: default failureRateThreshold: 30 paymentService: baseConfig: strict waitDurationInOpenState: 30s externalApi: baseConfig: lenient slidingWindowSize: 200 management: endpoints: web: exposure: include: health,metrics,circuitbreakers endpoint: circuitbreakers: enabled: true metrics: export: prometheus: enabled: true
Annotation-Based Circuit Breaker
@Service
public class AnnotatedCircuitBreakerService {
private final ExternalApiClient externalApiClient;
private final CacheService cacheService;
public AnnotatedCircuitBreakerService(ExternalApiClient externalApiClient,
CacheService cacheService) {
this.externalApiClient = externalApiClient;
this.cacheService = cacheService;
}
@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
public User getUser(String userId) {
return externalApiClient.getUser(userId);
}
@CircuitBreaker(name = "paymentService", fallbackMethod = "processPaymentFallback")
public PaymentResult processPayment(PaymentRequest request) {
return externalApiClient.processPayment(request);
}
@CircuitBreaker(name = "externalApi", fallbackMethod = "searchFallback")
public SearchResult search(String query, int page) {
return externalApiClient.search(query, page);
}
@CircuitBreaker(name = "cacheService", fallbackMethod = "cacheFallback")
@RateLimiter(name = "cacheService")
@Retry(name = "cacheService", fallbackMethod = "cacheFallback")
@Bulkhead(name = "cacheService")
public String getFromCache(String key) {
return cacheService.get(key);
}
// Fallback methods
private User getUserFallback(String userId, Exception e) {
log.warn("Circuit breaker fallback for getUser: {}", e.getMessage());
return User.builder()
.id(userId)
.name("Fallback User")
.email("[email protected]")
.build();
}
private PaymentResult processPaymentFallback(PaymentRequest request, Exception e) {
log.error("Payment processing failed, queuing for retry", e);
return PaymentResult.builder()
.status("PENDING")
.message("Payment queued for processing")
.build();
}
private SearchResult searchFallback(String query, int page, Exception e) {
log.warn("Search fallback for query: {}", query);
return SearchResult.builder()
.query(query)
.results(Collections.emptyList())
.total(0)
.fallback(true)
.build();
}
private String cacheFallback(String key, Exception e) {
log.warn("Cache fallback for key: {}", key);
return "default-value";
}
}
// Supporting classes
@Data
@Builder
class User {
private String id;
private String name;
private String email;
}
@Data
@Builder
class PaymentResult {
private String status;
private String message;
}
@Data
class PaymentRequest {
private String amount;
private String currency;
private String cardToken;
}
@Data
@Builder
class SearchResult {
private String query;
private List<String> results;
private int total;
private boolean fallback;
}
Reactive Circuit Breaker
Reactor Integration
@Service
public class ReactiveCircuitBreakerService {
private final CircuitBreakerRegistry circuitBreakerRegistry;
private final ReactiveExternalService reactiveExternalService;
public ReactiveCircuitBreakerService(CircuitBreakerRegistry circuitBreakerRegistry,
ReactiveExternalService reactiveExternalService) {
this.circuitBreakerRegistry = circuitBreakerRegistry;
this.reactiveExternalService = reactiveExternalService;
}
public Mono<String> getDataReactive(String id) {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("reactiveService");
return CircuitBreakerOperator.of(circuitBreaker)
.apply(reactiveExternalService.fetchData(id))
.onErrorResume(throwable -> {
if (throwable instanceof CallNotPermittedException) {
return Mono.just("Circuit breaker is OPEN - fallback response");
}
return Mono.just("Error fallback: " + throwable.getMessage());
});
}
public Flux<String> streamDataReactive(String query) {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("streamService");
return Flux.fromStream(IntStream.range(0, 10).boxed())
.flatMap(i -> CircuitBreakerOperator.of(circuitBreaker)
.apply(reactiveExternalService.getChunk(query, i))
.onErrorResume(throwable ->
Mono.just("Fallback chunk " + i))
);
}
// Using transformDeferred for more control
public Mono<User> getUserReactive(String userId) {
return reactiveExternalService.getUser(userId)
.transformDeferred(CircuitBreakerOperator.of(
circuitBreakerRegistry.circuitBreaker("userServiceReactive")
))
.onErrorResume(this::handleUserServiceError);
}
private Mono<User> handleUserServiceError(Throwable throwable) {
if (throwable instanceof CallNotPermittedException) {
return Mono.just(createFallbackUser());
}
log.error("Error fetching user", throwable);
return Mono.error(throwable);
}
private User createFallbackUser() {
return User.builder()
.id("fallback")
.name("Fallback User")
.email("[email protected]")
.build();
}
}
@Service
class ReactiveExternalService {
public Mono<String> fetchData(String id) {
return Mono.fromCallable(() -> {
// Simulate external call
if (Math.random() > 0.8) {
throw new RuntimeException("Service unavailable");
}
return "Data for " + id;
}).subscribeOn(Schedulers.boundedElastic());
}
public Mono<String> getChunk(String query, int index) {
return Mono.just("Chunk " + index + " for " + query)
.delayElement(Duration.ofMillis(100));
}
public Mono<User> getUser(String userId) {
return Mono.fromCallable(() -> {
// Simulate user service call
return User.builder()
.id(userId)
.name("Real User")
.email("[email protected]")
.build();
}).subscribeOn(Schedulers.boundedElastic());
}
}
Event Handling and Monitoring
Circuit Breaker Events
@Component
public class CircuitBreakerEventListener {
private static final Logger logger = LoggerFactory.getLogger(CircuitBreakerEventListener.class);
private final MeterRegistry meterRegistry;
public CircuitBreakerEventListener(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@EventListener
public void onCircuitBreakerEvent(CircuitBreakerOnStateTransitionEvent event) {
logger.info("Circuit Breaker {} transitioned from {} to {}",
event.getCircuitBreakerName(),
event.getStateTransition().getFromState(),
event.getStateTransition().getToState());
// Record metrics for state transitions
Counter.builder("circuitbreaker.state.transitions")
.tag("name", event.getCircuitBreakerName())
.tag("from", event.getStateTransition().getFromState().name())
.tag("to", event.getStateTransition().getToState().name())
.register(meterRegistry)
.increment();
}
@EventListener
public void onCircuitBreakerCallEvent(CircuitBreakerOnCallNotPermittedEvent event) {
logger.warn("Circuit Breaker {} is OPEN - call not permitted for {}",
event.getCircuitBreakerName(), event.getEventType());
Counter.builder("circuitbreaker.calls.blocked")
.tag("name", event.getCircuitBreakerName())
.register(meterRegistry)
.increment();
}
@EventListener
public void onCircuitBreakerErrorEvent(CircuitBreakerOnErrorEvent event) {
logger.error("Circuit Breaker {} recorded error: {}",
event.getCircuitBreakerName(), event.getThrowable().getMessage());
Counter.builder("circuitbreaker.calls.failed")
.tag("name", event.getCircuitBreakerName())
.tag("error", event.getThrowable().getClass().getSimpleName())
.register(meterRegistry)
.increment();
}
@EventListener
public void onCircuitBreakerSuccessEvent(CircuitBreakerOnSuccessEvent event) {
logger.debug("Circuit Breaker {} recorded successful call",
event.getCircuitBreakerName());
Counter.builder("circuitbreaker.calls.successful")
.tag("name", event.getCircuitBreakerName())
.register(meterRegistry)
.increment();
}
@EventListener
public void onCircuitBreakerSlowCallEvent(CircuitBreakerOnSlowCallRateEvent event) {
logger.warn("Circuit Breaker {} detected slow call rate",
event.getCircuitBreakerName());
Counter.builder("circuitbreaker.slow.calls")
.tag("name", event.getCircuitBreakerName())
.register(meterRegistry)
.increment();
}
}
// Custom event publisher
@Component
public class CircuitBreakerEventPublisher {
private final CircuitBreakerRegistry circuitBreakerRegistry;
public CircuitBreakerEventPublisher(CircuitBreakerRegistry circuitBreakerRegistry) {
this.circuitBreakerRegistry = circuitBreakerRegistry;
registerEventConsumers();
}
private void registerEventConsumers() {
circuitBreakerRegistry.getAllCircuitBreakers().forEach((name, circuitBreaker) -> {
circuitBreaker.getEventPublisher()
.onStateTransition(this::onStateTransition)
.onCallNotPermitted(this::onCallNotPermitted)
.onError(this::onError)
.onSuccess(this::onSuccess)
.onSlowCallRate(this::onSlowCallRate)
.onFailureRate(this::onFailureRate);
});
}
private void onStateTransition(CircuitBreakerOnStateTransitionEvent event) {
// Publish to message queue or external monitoring
System.out.printf("State Transition: %s from %s to %s%n",
event.getCircuitBreakerName(),
event.getStateTransition().getFromState(),
event.getStateTransition().getToState());
}
private void onCallNotPermitted(CircuitBreakerOnCallNotPermittedEvent event) {
System.out.printf("Call Not Permitted: %s%n", event.getCircuitBreakerName());
}
private void onError(CircuitBreakerOnErrorEvent event) {
System.out.printf("Error in %s: %s%n",
event.getCircuitBreakerName(), event.getThrowable().getMessage());
}
private void onSuccess(CircuitBreakerOnSuccessEvent event) {
System.out.printf("Success in %s%n", event.getCircuitBreakerName());
}
private void onSlowCallRate(CircuitBreakerOnSlowCallRateEvent event) {
System.out.printf("Slow call rate in %s%n", event.getCircuitBreakerName());
}
private void onFailureRate(CircuitBreakerOnFailureRateEvent event) {
System.out.printf("Failure rate exceeded in %s%n", event.getCircuitBreakerName());
}
}
Advanced Patterns
Composite Circuit Breaker
@Service
public class CompositeCircuitBreakerService {
private final CircuitBreakerRegistry circuitBreakerRegistry;
private final Map<String, ExternalService> services;
public CompositeCircuitBreakerService(CircuitBreakerRegistry circuitBreakerRegistry) {
this.circuitBreakerRegistry = circuitBreakerRegistry;
this.services = initializeServices();
}
public String callWithCompositeCircuitBreaker(String serviceName, String operation) {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(serviceName);
if (!circuitBreaker.tryAcquirePermission()) {
throw new CallNotPermittedException("Circuit breaker is OPEN for " + serviceName);
}
long start = System.nanoTime();
try {
String result = services.get(serviceName).execute(operation);
circuitBreaker.onSuccess(System.nanoTime() - start, TimeUnit.NANOSECONDS);
return result;
} catch (Exception e) {
circuitBreaker.onError(System.nanoTime() - start, TimeUnit.NANOSECONDS, e);
throw e;
}
}
public <T> T executeWithMetrics(String serviceName, Supplier<T> operation) {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(serviceName);
return circuitBreaker.executeSupplier(() -> {
Timer.Sample sample = Timer.start();
try {
T result = operation.get();
sample.stop(Timer.builder("circuitbreaker.operation.duration")
.tag("service", serviceName)
.tag("status", "success")
.register(meterRegistry));
return result;
} catch (Exception e) {
sample.stop(Timer.builder("circuitbreaker.operation.duration")
.tag("service", serviceName)
.tag("status", "error")
.register(meterRegistry));
throw e;
}
});
}
private Map<String, ExternalService> initializeServices() {
Map<String, ExternalService> services = new HashMap<>();
services.put("userService", new UserService());
services.put("paymentService", new PaymentService());
services.put("inventoryService", new InventoryService());
return services;
}
}
// Circuit Breaker with custom predicate
@Service
class CustomCircuitBreakerService {
public String callWithCustomFailurePredicate(String input) {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.slidingWindowSize(10)
.recordException(throwable ->
throwable instanceof IOException ||
throwable instanceof TimeoutException ||
(throwable instanceof RuntimeException &&
throwable.getMessage().contains("unavailable")))
.build();
CircuitBreaker circuitBreaker = CircuitBreaker.of("customService", config);
return circuitBreaker.executeSupplier(() -> {
if (input.equals("fail")) {
throw new RuntimeException("Service unavailable");
}
return "Success: " + input;
});
}
}
Integration with HTTP Clients
RestTemplate Integration
@Configuration
public class RestTemplateCircuitBreakerConfig {
@Bean
public RestTemplate restTemplate(CircuitBreakerRegistry circuitBreakerRegistry) {
RestTemplate restTemplate = new RestTemplate();
// Add circuit breaker interceptor
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(circuitBreakerInterceptor(circuitBreakerRegistry));
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
@Bean
public ClientHttpRequestInterceptor circuitBreakerInterceptor(CircuitBreakerRegistry registry) {
return (request, body, execution) -> {
String serviceName = extractServiceName(request.getURI());
CircuitBreaker circuitBreaker = registry.circuitBreaker(serviceName);
return circuitBreaker.executeSupplier(() -> {
try {
ClientHttpResponse response = execution.execute(request, body);
if (response.getStatusCode().is5xxServerError()) {
throw new HttpClientErrorException(response.getStatusCode(),
"Server error: " + response.getStatusText());
}
return response;
} catch (Exception e) {
throw new RuntimeException("HTTP call failed", e);
}
});
};
}
private String extractServiceName(URI uri) {
// Extract service name from URI
return uri.getHost(); // or use service discovery
}
}
// Feign Client Integration
@FeignClient(name = "user-service", configuration = FeignConfig.class)
public interface UserServiceClient {
@CircuitBreaker(name = "userService")
@GetMapping("/users/{id}")
User getUser(@PathVariable String id);
@CircuitBreaker(name = "userService")
@PostMapping("/users")
User createUser(@RequestBody User user);
}
@Configuration
public class FeignConfig {
@Bean
public CircuitBreaker feignCircuitBreaker(CircuitBreakerRegistry registry) {
return registry.circuitBreaker("feignClient");
}
}
Testing Circuit Breakers
Comprehensive Testing
@SpringBootTest
@ExtendWith(SpringExtension.class)
class CircuitBreakerTest {
@Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;
@Autowired
private AnnotatedCircuitBreakerService circuitBreakerService;
@Mock
private ExternalApiClient externalApiClient;
@Test
void testCircuitBreakerStateTransitions() {
// Given
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testService");
// When - simulate failures to trigger OPEN state
for (int i = 0; i < 10; i++) {
try {
circuitBreaker.executeRunnable(() -> {
throw new RuntimeException("Simulated failure");
});
} catch (Exception e) {
// Expected
}
}
// Then
assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN);
}
@Test
void testFallbackMethod() {
// Given
when(externalApiClient.getUser("123"))
.thenThrow(new RuntimeException("Service unavailable"));
// When
User result = circuitBreakerService.getUser("123");
// Then
assertThat(result.getName()).isEqualTo("Fallback User");
assertThat(result.getEmail()).isEqualTo("[email protected]");
}
@Test
void testCallNotPermittedException() {
// Given
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testService");
// Force circuit breaker to OPEN state
circuitBreaker.transitionToOpenState();
// When/Then
assertThatThrownBy(() ->
circuitBreaker.executeRunnable(() -> {}))
.isInstanceOf(CallNotPermittedException.class);
}
@Test
void testCircuitBreakerMetrics() {
// Given
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("metricsTest");
// When - execute some calls
for (int i = 0; i < 5; i++) {
try {
circuitBreaker.executeSupplier(() -> "Success");
} catch (Exception e) {
// Ignore
}
}
// Then
CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
assertThat(metrics.getNumberOfSuccessfulCalls()).isGreaterThan(0);
}
}
// Test configuration
@TestConfiguration
public class TestCircuitBreakerConfig {
@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.slidingWindowSize(5)
.minimumNumberOfCalls(2)
.waitDurationInOpenState(Duration.ofSeconds(1))
.build();
return CircuitBreakerRegistry.of(config);
}
}
Best Practices
Production Configuration
@Component
public class CircuitBreakerBestPractices {
private final CircuitBreakerRegistry circuitBreakerRegistry;
public CircuitBreakerBestPractices(CircuitBreakerRegistry circuitBreakerRegistry) {
this.circuitBreakerRegistry = circuitBreakerRegistry;
}
// 1. Use meaningful names for circuit breakers
public void useMeaningfulNames() {
// Good
CircuitBreaker userServiceCircuitBreaker = circuitBreakerRegistry.circuitBreaker("userService");
CircuitBreaker paymentServiceCircuitBreaker = circuitBreakerRegistry.circuitBreaker("paymentService");
// Avoid
CircuitBreaker cb1 = circuitBreakerRegistry.circuitBreaker("cb1");
}
// 2. Configure appropriate thresholds
public void configureAppropriateThresholds() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // Adjust based on service criticality
.slowCallRateThreshold(80) // For latency-sensitive services
.waitDurationInOpenState(Duration.ofSeconds(30)) // Balance between recovery and protection
.minimumNumberOfCalls(10) // Ensure statistical significance
.build();
}
// 3. Use proper exception handling
public void handleExceptionsProperly() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.recordExceptions(IOException.class, TimeoutException.class) // Network-related exceptions
.ignoreExceptions(BusinessException.class, ValidationException.class) // Business logic exceptions
.build();
}
// 4. Implement meaningful fallbacks
public String implementMeaningfulFallback(String input, Exception e) {
if (e instanceof CallNotPermittedException) {
return "Service temporarily unavailable - please try again later";
}
if (e instanceof TimeoutException) {
return "Request timed out - please try again";
}
// Return cached data, default values, or queue for retry
return getCachedValue(input).orElse("Default value");
}
// 5. Monitor and adjust configurations
public void monitorAndAdjust() {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("monitoredService");
// Regularly check metrics and adjust configuration if needed
CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
double failureRate = metrics.getFailureRate();
if (failureRate > 70) {
// Consider tightening the circuit breaker
log.warn("High failure rate detected for service: {}", failureRate);
}
}
private Optional<String> getCachedValue(String key) {
// Implementation for cache retrieval
return Optional.empty();
}
}
Conclusion
Resilience4j Circuit Breaker provides robust fault tolerance for Java applications:
- Lightweight - No external dependencies required
- Flexible configuration - Customizable for different services
- Spring Boot integration - Seamless integration with Spring ecosystem
- Reactive support - Works with Reactor and other reactive libraries
- Comprehensive monitoring - Events and metrics for observability
Key benefits:
- Prevents cascading failures by isolating faulty services
- Improves system resilience with automatic recovery
- Provides meaningful fallbacks for graceful degradation
- Enables monitoring with detailed metrics and events
By implementing circuit breakers with Resilience4j, you can build more resilient and fault-tolerant microservices architectures that gracefully handle failures and maintain system stability.