Circuit Breaker with Resilience4j in Java

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.

Leave a Reply

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


Macro Nepal Helper