Skip to content

Resilience4j Quick Start Guide

๐Ÿš€ Getting Started in 5 Minutes

1. Inject Circuit Breaker

@Service
public class MyService {

    @Autowired
    private CircuitBreaker defaultApiCircuitBreaker;

    @Autowired
    private TimeLimiter defaultApiTimeLimiter;
}

2. Wrap Your Service Calls

Simple Call:

public String callService() {
    return defaultApiCircuitBreaker.executeSupplier(() -> {
        return externalService.call();
    });
}

With Fallback:

public String callServiceWithFallback() {
    return defaultApiCircuitBreaker.executeSupplier(
        () -> externalService.call(),
        throwable -> "Fallback response"
    );
}

With Timeout:

public String callServiceWithTimeout() {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() ->
        defaultApiCircuitBreaker.executeSupplier(() -> externalService.call())
    );

    try {
        return future.get(200, TimeUnit.MILLISECONDS);
    } catch (TimeoutException e) {
        return "Timeout fallback";
    }
}

๐Ÿ“Š Circuit Breaker States

CLOSED  โ†’ Normal operation, all requests pass through
OPEN    โ†’ Circuit tripped, all requests fail immediately
HALF_OPEN โ†’ Testing if service recovered

๐Ÿ”ง Common Patterns

Pattern 1: Simple Protection

@Service
public class OrderService {
    @Autowired
    private CircuitBreaker defaultApiCircuitBreaker;

    public Order createOrder(OrderRequest request) {
        return defaultApiCircuitBreaker.executeSupplier(() -> {
            return orderApi.create(request);
        });
    }
}

Pattern 2: With Fallback

@Service
public class ProductService {
    @Autowired
    private CircuitBreaker defaultApiCircuitBreaker;

    public Product getProduct(String id) {
        return defaultApiCircuitBreaker.executeSupplier(
            () -> productApi.get(id),
            throwable -> getCachedProduct(id) // Fallback to cache
        );
    }

    private Product getCachedProduct(String id) {
        return cache.get(id);
    }
}

Pattern 3: Async with Timeout

@Service
public class NotificationService {
    @Autowired
    private CircuitBreaker defaultApiCircuitBreaker;

    @Async
    public CompletableFuture<Void> sendNotification(String message) {
        return CompletableFuture.runAsync(() ->
            defaultApiCircuitBreaker.executeRunnable(() -> {
                notificationApi.send(message);
            })
        ).orTimeout(500, TimeUnit.MILLISECONDS);
    }
}

Pattern 4: Multiple Resilience Patterns

@Service
public class PaymentService {
    @Autowired
    private CircuitBreaker circuitBreaker;

    @Autowired
    private Bulkhead bulkhead;

    @Autowired
    private Retry retry;

    public Payment processPayment(PaymentRequest request) {
        // Combine circuit breaker + bulkhead + retry
        return Decorators.ofSupplier(() -> paymentApi.process(request))
            .withCircuitBreaker(circuitBreaker)
            .withBulkhead(bulkhead)
            .withRetry(retry)
            .decorate()
            .get();
    }
}

๐ŸŽฏ Best Practices

โœ… DO

  • Use meaningful names for circuit breakers
  • Implement fallbacks for critical operations
  • Log state transitions for monitoring
  • Test circuit breaker behavior in integration tests
  • Configure appropriate timeouts based on SLAs
  • Use bulkhead to isolate thread pools

โŒ DON'T

  • Don't wrap database calls (use connection pooling instead)
  • Don't use for fast operations (< 10ms)
  • Don't ignore circuit breaker state in monitoring
  • Don't use same circuit breaker for different services
  • Don't set thresholds too low (causes flapping)

๐Ÿ“ˆ Monitoring

Check Circuit Breaker State

# Health endpoint
curl http://localhost:8080/actuator/health

# Metrics endpoint
curl http://localhost:8080/actuator/metrics/resilience4j.circuitbreaker.calls

Log Analysis

// State transitions are automatically logged
INFO: Circuit breaker transitioned to OPEN state: sbapi-defapi
INFO: Circuit breaker transitioned to HALF_OPEN state: sbapi-defapi
INFO: Circuit breaker transitioned to CLOSED state: sbapi-defapi

๐Ÿ” Debugging

Check Current State

CircuitBreaker.State state = defaultApiCircuitBreaker.getState();
System.out.println("Circuit Breaker State: " + state);

Get Metrics

CircuitBreaker.Metrics metrics = defaultApiCircuitBreaker.getMetrics();
System.out.println("Failure Rate: " + metrics.getFailureRate());
System.out.println("Number of Calls: " + metrics.getNumberOfSuccessfulCalls());

Force State Change (Testing Only)

// For testing purposes
defaultApiCircuitBreaker.transitionToOpenState();
defaultApiCircuitBreaker.transitionToClosedState();

๐Ÿงช Testing

Unit Test

@Test
public void testServiceWithCircuitBreaker() {
    // Given
    when(externalService.call()).thenThrow(new RuntimeException());

    // When
    String result = myService.callServiceWithFallback();

    // Then
    assertEquals("Fallback response", result);
}

Integration Test

@SpringBootTest
public class CircuitBreakerIntegrationTest {

    @Autowired
    private CircuitBreaker defaultApiCircuitBreaker;

    @Autowired
    private MyService myService;

    @Test
    public void testCircuitBreakerOpens() {
        // Simulate multiple failures
        for (int i = 0; i < 25; i++) {
            try {
                myService.callService();
            } catch (Exception e) {
                // Expected
            }
        }

        // Circuit should be OPEN
        assertEquals(CircuitBreaker.State.OPEN, 
                    defaultApiCircuitBreaker.getState());
    }
}

โš™๏ธ Configuration Quick Reference

Circuit Breaker

# Minimum calls before calculating failure rate
minimumNumberOfCalls=20

# Failure rate threshold (50%)
failureRateThreshold=50

# Wait time in OPEN state
waitDurationInOpenState=1000ms

# Test calls in HALF_OPEN state
permittedNumberOfCallsInHalfOpenState=10

Time Limiter

# Timeout duration
timeoutDuration=200ms

# Cancel running future on timeout
cancelRunningFuture=true

Bulkhead

# Max concurrent calls
maxConcurrentCalls=30

# Thread pool size
maxThreadPoolSize=40
coreThreadPoolSize=30

๐Ÿ†˜ Troubleshooting

Circuit Breaker Not Opening

Problem: Circuit stays CLOSED despite failures

Solution:

# Reduce minimum calls for testing
resilience4j.circuitbreaker.instances.test.minimumNumberOfCalls=5
resilience4j.circuitbreaker.instances.test.failureRateThreshold=50

Fallback Not Executing

Problem: Fallback method not called

Solution:

// Ensure fallback method signature matches
@CircuitBreaker(name = "cb", fallbackMethod = "fallback")
public String call() { ... }

// Fallback must accept Throwable
private String fallback(Throwable t) {
    return "Fallback";
}

Timeout Not Working

Problem: Calls not timing out

Solution:

// Use CompletableFuture with timeout
CompletableFuture<String> future = CompletableFuture.supplyAsync(() ->
    circuitBreaker.executeSupplier(() -> service.call())
);
return future.get(200, TimeUnit.MILLISECONDS);

๐Ÿ“š More Information

  • Full Documentation: RESILIENCE4J_MIGRATION.md
  • Official Docs: https://resilience4j.readme.io/
  • Examples: https://github.com/resilience4j/resilience4j-spring-boot3-demo

๐ŸŽ‰ You're Ready!

Start protecting your service calls with Resilience4j circuit breakers!

@Service
public class MyService {
    @Autowired
    private CircuitBreaker defaultApiCircuitBreaker;

    public String protectedCall() {
        return defaultApiCircuitBreaker.executeSupplier(
            () -> externalService.call(),
            throwable -> "Fallback"
        );
    }
}

Happy Coding! ๐Ÿš€