In a distributed system, a slow dependency is more dangerous than a dead one — it ties up your threads while everything backs up behind it. Resilience4j gives you circuit breakers, retries, and timeouts as composable annotations.
Add a circuit breaker
When failures cross a threshold, the breaker opens and fails fast, giving the downstream time to recover instead of hammering it.
@Service
public class InventoryClient {
@CircuitBreaker(name = "inventory", fallbackMethod = "fallback")
public Stock check(Long productId) {
return client.get()
.uri("/stock/{id}", productId)
.retrieve()
.body(Stock.class);
}
private Stock fallback(Long productId, Throwable t) {
return Stock.unknown(productId);
}
}Configure thresholds
Behavior is data-driven through properties, so you can tune without redeploying code.
resilience4j:
circuitbreaker:
instances:
inventory:
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
sliding-window-size: 20Layer retry and timeout
Combine annotations for defense in depth. A @TimeLimiter caps how long you'll
wait; a @Retry handles transient blips.
@TimeLimiter(name = "inventory")
@Retry(name = "inventory")
@CircuitBreaker(name = "inventory", fallbackMethod = "fallback")
public CompletableFuture<Stock> check(Long productId) { ... }Takeaways
- A circuit breaker fails fast when a dependency is unhealthy.
- Provide a fallback so callers get a sensible degraded response.
- Combine timeouts and retries, but cap retries to avoid amplifying load.
Order matters: retry inside the circuit breaker, and always bound retries so you don't turn one failure into a storm.