Skip to content

resilience4j/resilience4j

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fault tolerance library designed for functional programming

1. Introduction

Resilience4j is a lightweight fault tolerance library designed for functional programming. Resilience4j provides higher-order functions (decorators) to enhance any functional interface, lambda expression or method reference with a Circuit Breaker, Rate Limiter, Retry or Bulkhead. You can stack more than one decorator on any functional interface, lambda expression or method reference. The advantage is that you have the choice to select the decorators you need and nothing else.

Resilience4j 3 requires Java 21.

// Create a CircuitBreaker with default configuration
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendService");

// Create a Retry with default configuration
// 3 retry attempts and a fixed time interval between retries of 500ms
Retry retry = Retry.ofDefaults("backendService");

// Create a Bulkhead with default configuration
Bulkhead bulkhead = Bulkhead.ofDefaults("backendService");

Supplier<String> supplier = () -> backendService
  .doSomething(param1, param2);

// Decorate your call to backendService.doSomething()
// with a Bulkhead, CircuitBreaker and Retry
// **note: you will need the resilience4j-all dependency for this
Supplier<String> decoratedSupplier = Decorators.ofSupplier(supplier)
  .withCircuitBreaker(circuitBreaker)
  .withBulkhead(bulkhead)
  .withRetry(retry)
  .decorate();

// Execute the decorated supplier and recover from any exception
String result = Try.ofSupplier(decoratedSupplier)
  .recover(throwable -> "Hello from Recovery").get();

// When you don't want to decorate your lambda expression,
// but just execute it and protect the call by a CircuitBreaker.
String result = circuitBreaker
  .executeSupplier(backendService::doSomething);

// You can also run the supplier asynchronously in a ThreadPoolBulkhead
 ThreadPoolBulkhead threadPoolBulkhead = ThreadPoolBulkhead
  .ofDefaults("backendService");

// The Scheduler is needed to schedule a timeout on a non-blocking CompletableFuture
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3);
TimeLimiter timeLimiter = TimeLimiter.of(Duration.ofSeconds(1));

CompletableFuture<String> future = Decorators.ofSupplier(supplier)
    .withThreadPoolBulkhead(threadPoolBulkhead)
    .withTimeLimiter(timeLimiter, scheduler)
    .withCircuitBreaker(circuitBreaker)
    .withFallback(asList(TimeoutException.class, CallNotPermittedException.class, BulkheadFullException.class),
      throwable -> "Hello from Recovery")
    .get().toCompletableFuture();
ℹ️
With Resilience4j you don’t have to go all-in, you can pick what you need.

3. Overview

Resilience4j provides several core modules:

  • resilience4j-circuitbreaker: Circuit breaking

  • resilience4j-ratelimiter: Rate limiting

  • resilience4j-bulkhead: Bulkheading

  • resilience4j-retry: Automatic retrying (sync and async)

  • resilience4j-timelimiter: Timeout handling

  • resilience4j-cache: Result caching

There are also add-on modules for metrics, Feign, Kotlin, Spring, Ratpack, Vertx, RxJava2 and more.

ℹ️
Find out full list of modules in our User Guide.
💡
For core modules package or Decorators builder see resilience4j-all.

4. Best Practices

4.1. Instance Management: When to Share vs. Not Share Instances

One of the most important concepts for newcomers to understand is when to create separate instances versus when to share instances across different remote services or backends.

The Golden Rule: Create a unique instance (with a unique ID) for each protected remote service or backend you communicate with.

4.1.1. Why Unique Instances Matter

Creating separate instances for each backend service is critical for:

  1. Proper metrics collection - Each instance tracks its own metrics, allowing you to monitor the health of individual services

  2. Isolation of failures - If Service A fails, it won’t affect the resilience patterns protecting Service B

  3. Independent configuration - Different backends may require different circuit breaker thresholds, concurrency limits, rate limits, or retry policies

4.1.2. Instance-Aware vs. Non-Instance-Aware Patterns

Different resilience patterns have different requirements for instance separation:

Instance-Aware Patterns (MUST NOT Share)

These patterns maintain state that is specific to a particular service and MUST have separate instances:

  • CircuitBreaker - Tracks failure rates and state (OPEN/CLOSED/HALF_OPEN) per service

    // CORRECT: Separate CircuitBreaker for each service
    CircuitBreaker paymentServiceCB = CircuitBreaker.ofDefaults("paymentService");
    CircuitBreaker inventoryServiceCB = CircuitBreaker.ofDefaults("inventoryService");
    CircuitBreaker notificationServiceCB = CircuitBreaker.ofDefaults("notificationService");
    
    // WRONG: Sharing one CircuitBreaker across multiple services
    CircuitBreaker sharedCB = CircuitBreaker.ofDefaults("shared"); // DON'T DO THIS!
    // If one service fails, the circuit opens for ALL services!
  • Bulkhead (both SemaphoreBulkhead and ThreadPoolBulkhead) - Manages concurrent call limits per service

    // CORRECT: Separate Bulkhead for each service
    Bulkhead paymentBulkhead = Bulkhead.ofDefaults("paymentService");
    Bulkhead inventoryBulkhead = Bulkhead.ofDefaults("inventoryService");
    
    // WRONG: Sharing limits resource isolation benefits
    Bulkhead sharedBulkhead = Bulkhead.ofDefaults("shared"); // DON'T DO THIS!
  • RateLimiter - Controls request rate per service

    // CORRECT: Different rate limits for different services
    RateLimiter apiRateLimiter = RateLimiter.ofDefaults("externalAPI");
    RateLimiter dbRateLimiter = RateLimiter.ofDefaults("database");
Non-Instance-Aware Patterns (CAN Share, But Better Not To)

These patterns create fresh context for each execution and don’t maintain service-specific state:

  • Retry - Configuration can be shared, but unique instances are still recommended for proper metrics

  • TimeLimiter - Timeout settings can be shared if identical across services

// TECHNICALLY OK: Retry doesn't maintain state between calls
Retry sharedRetry = Retry.ofDefaults("shared");

// BETTER: Unique instances provide better metrics and monitoring
Retry paymentRetry = Retry.ofDefaults("paymentService");
Retry inventoryRetry = Retry.ofDefaults("inventoryService");

// Now you can track:
// - How many retry attempts failed for paymentService specifically
// - How many retry attempts failed for inventoryService specifically

Even for patterns that can be shared, creating unique instances is the recommended approach because:

  • Better observability - Metrics are tagged by instance name, letting you see which backend is struggling

  • Easier troubleshooting - Clear separation makes debugging production issues simpler

  • Future flexibility - If you later need different configurations per service, instances are already separated

Complete Example: Multiple Services
// You have 3 backend services to protect
public class ServiceOrchestrator {

    // Payment Service protection
    private final CircuitBreaker paymentCB = CircuitBreaker.ofDefaults("paymentService");
    private final Retry paymentRetry = Retry.ofDefaults("paymentService");
    private final Bulkhead paymentBulkhead = Bulkhead.ofDefaults("paymentService");

    // Inventory Service protection
    private final CircuitBreaker inventoryCB = CircuitBreaker.ofDefaults("inventoryService");
    private final Retry inventoryRetry = Retry.ofDefaults("inventoryService");
    private final Bulkhead inventoryBulkhead = Bulkhead.ofDefaults("inventoryService");

    // Notification Service protection
    private final CircuitBreaker notificationCB = CircuitBreaker.ofDefaults("notificationService");
    private final Retry notificationRetry = Retry.ofDefaults("notificationService");

    public Order processOrder(OrderRequest request) {
        // Each service call is protected by its own set of resilience instances

        // Call payment service
        Supplier<PaymentResult> paymentCall = () -> paymentService.charge(request);
        PaymentResult payment = Decorators.ofSupplier(paymentCall)
            .withCircuitBreaker(paymentCB)
            .withRetry(paymentRetry)
            .withBulkhead(paymentBulkhead)
            .decorate()
            .get();

        // Call inventory service
        Supplier<InventoryResult> inventoryCall = () -> inventoryService.reserve(request);
        InventoryResult inventory = Decorators.ofSupplier(inventoryCall)
            .withCircuitBreaker(inventoryCB)
            .withRetry(inventoryRetry)
            .withBulkhead(inventoryBulkhead)
            .decorate()
            .get();

        // Call notification service (demonstrates circuit isolation: the notification circuit breaker remains independent of the payment circuit breaker, even if payment opens its circuit in other calls)
        Runnable notificationCall = () -> notificationService.send(request);
        Decorators.ofRunnable(notificationCall)
            .withCircuitBreaker(notificationCB)
            .withRetry(notificationRetry)
            .decorate()
            .run();

        return new Order(payment, inventory);
    }
}

4.1.4. Using Registries for Instance Management

Resilience4j provides Registry classes to manage multiple instances efficiently:

// Create a registry with custom default configuration
CircuitBreakerConfig defaultConfig = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)
    .waitDurationInOpenState(Duration.ofSeconds(30))
    .build();

CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(defaultConfig);

// Get or create instances by name
CircuitBreaker paymentCB = registry.circuitBreaker("paymentService");
CircuitBreaker inventoryCB = registry.circuitBreaker("inventoryService");

// You can also override config for specific instances
CircuitBreakerConfig customConfig = CircuitBreakerConfig.custom()
    .failureRateThreshold(80)  // More lenient for this service
    .build();
CircuitBreaker legacyCB = registry.circuitBreaker("legacyService", customConfig);
ℹ️
Registries are particularly useful in Spring Boot applications where they’re auto-configured and instances are created on-demand based on configuration properties.

4.1.5. Summary

Pattern Can Share? Should Share? Why?

CircuitBreaker

No

No

State is service-specific. Sharing causes one service’s failures to affect all others.

Bulkhead

No

No

Concurrent call limits must be isolated per service for proper resource management.

RateLimiter

No

No

Rate limits are typically different per service and sharing defeats the purpose.

Retry

Yes

No

While technically safe to share, unique instances provide better metrics and observability.

TimeLimiter

Yes

No

While technically safe to share, unique instances provide better metrics and observability.

Cache

Depends

Depends

Only share if the cached data is truly identical across use cases.

💡

When in doubt, create separate instances. The overhead is minimal, and the benefits for metrics, debugging, and flexibility are significant.

5. Resilience patterns

name how does it work? description links

Retry

repeats failed executions

Many faults are transient and may self-correct after a short delay.

overview, documentation, Spring

Circuit Breaker

temporary blocks possible failures

When a system is seriously struggling, failing fast is better than making clients wait.

overview, documentation, Feign, Spring

Rate Limiter

limits executions/period

Limit the rate of incoming requests.

overview, documentation, Feign, Spring

Time Limiter

limits duration of execution

Beyond a certain wait interval, a successful result is unlikely.

documentation, Spring

Bulkhead

limits concurrent executions

Resources are isolated into pools so that if one fails, the others will continue working.

overview, documentation, Spring

Cache

memorizes a successful result

Some proportion of requests may be similar.

documentation

Fallback

provides an alternative result for failures

Things will still fail - plan what you will do when that happens.

Try::recover, Spring, Feign

Above table is based on Polly: resilience policies.

ℹ️
To find more information about resilience patterns check Talks section. Find out more about components in our User Guide.

6. Spring Boot

Setup and usage in Spring Boot 2 is demonstrated here.

7. Usage examples

7.1. CircuitBreaker, Retry and Fallback

The following example shows how to decorate a lambda expression (Supplier) with a CircuitBreaker and how to retry the call at most 3 times when an exception occurs. You can configure the wait interval between retries and also configure a custom backoff algorithm.

The example uses Vavr’s Try Monad to recover from an exception and invoke another lambda expression as a fallback, when even all retries have failed.

// Simulates a Backend Service
public interface BackendService {
    String doSomething();
}

// Create a CircuitBreaker (use default configuration)
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName");
// Create a Retry with at most 3 retries and a fixed time interval between retries of 500ms
Retry retry = Retry.ofDefaults("backendName");

// Decorate your call to BackendService.doSomething() with a CircuitBreaker
Supplier<String> decoratedSupplier = CircuitBreaker
    .decorateSupplier(circuitBreaker, backendService::doSomething);

// Decorate your call with automatic retry
decoratedSupplier = Retry
    .decorateSupplier(retry, decoratedSupplier);

// Use of Vavr's Try to
// execute the decorated supplier and recover from any exception
String result = Try.ofSupplier(decoratedSupplier)
    .recover(throwable -> "Hello from Recovery").get();

// When you don't want to decorate your lambda expression,
// but just execute it and protect the call by a CircuitBreaker.
String result = circuitBreaker.executeSupplier(backendService::doSomething);

7.1.1. CircuitBreaker and RxJava2

The following example shows how to decorate an Observable by using the custom RxJava operator.

CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");
Observable.fromCallable(backendService::doSomething)
    .compose(CircuitBreakerOperator.of(circuitBreaker))
ℹ️
Resilience4j also provides RxJava operators for RateLimiter, Bulkhead, TimeLimiter and Retry. Find out more in our User Guide.

7.1.2. CircuitBreaker and Spring Reactor

The following example shows how to decorate a Mono by using the custom Reactor operator.

CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");
Mono.fromCallable(backendService::doSomething)
    .transformDeferred(CircuitBreakerOperator.of(circuitBreaker))
ℹ️
Resilience4j also provides Reactor operators for RateLimiter, Bulkhead, TimeLimiter and Retry. Find out more in our User Guide.

7.2. RateLimiter

The following example shows how to restrict the calling rate of some method to be not higher than 1 request/second.

// Create a custom RateLimiter configuration
RateLimiterConfig config = RateLimiterConfig.custom()
    .timeoutDuration(Duration.ofMillis(100))
    .limitRefreshPeriod(Duration.ofSeconds(1))
    .limitForPeriod(1)
    .build();
// Create a RateLimiter
RateLimiter rateLimiter = RateLimiter.of("backendName", config);

// Decorate your call to BackendService.doSomething()
Supplier<String> restrictedSupplier = RateLimiter
    .decorateSupplier(rateLimiter, backendService::doSomething);

// First call is successful
Try<String> firstTry = Try.ofSupplier(restrictedSupplier);
assertThat(firstTry.isSuccess()).isTrue();

// Second call fails, because the call was not permitted
Try<String> secondTry = Try.of(restrictedSupplier);
assertThat(secondTry.isFailure()).isTrue();
assertThat(secondTry.getCause()).isInstanceOf(RequestNotPermitted.class);

7.3. Bulkhead

There are two isolation strategies and bulkhead implementations.

7.3.1. SemaphoreBulkhead

The following example shows how to decorate a lambda expression with a Bulkhead. A Bulkhead can be used to limit the amount of parallel executions. This bulkhead abstraction should work well across a variety of threading and io models. It is based on a semaphore, and unlike Hystrix, does not provide "shadow" thread pool option.

// Create a custom Bulkhead configuration
BulkheadConfig config = BulkheadConfig.custom()
    .maxConcurrentCalls(150)
    .maxWaitDuration(100)
    .build();

Bulkhead bulkhead = Bulkhead.of("backendName", config);

Supplier<String> supplier = Bulkhead
    .decorateSupplier(bulkhead, backendService::doSomething);

7.3.2. ThreadPoolBulkhead

The following example shows how to use a lambda expression with a ThreadPoolBulkhead which uses a bounded queue and a fixed thread pool.

// Create a custom ThreadPoolBulkhead configuration
ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom()
    .maxThreadPoolSize(10)
    .coreThreadPoolSize(2)
    .queueCapacity(20)
    .build();

ThreadPoolBulkhead bulkhead = ThreadPoolBulkhead.of("backendName", config);

// Decorate or execute immediately a lambda expression with a ThreadPoolBulkhead.
Supplier<CompletionStage<String>> supplier = ThreadPoolBulkhead
    .decorateSupplier(bulkhead, backendService::doSomething);

CompletionStage<String> execution = bulkhead
    .executeSupplier(backendService::doSomething);

8. Consume emitted events

CircuitBreaker, RateLimiter, Cache, Bulkhead, TimeLimiter and Retry components emit a stream of events. It can be consumed for logging, assertions and any other purpose.

8.1. Examples

A CircuitBreakerEvent can be a state transition, a circuit breaker reset, a successful call, a recorded error or an ignored error. All events contains additional information like event creation time and processing duration of the call. If you want to consume events, you have to register an event consumer.

circuitBreaker.getEventPublisher()
    .onSuccess(event -> logger.info(...))
    .onError(event -> logger.info(...))
    .onIgnoredError(event -> logger.info(...))
    .onReset(event -> logger.info(...))
    .onStateTransition(event -> logger.info(...));
// Or if you want to register a consumer listening to all events, you can do:
circuitBreaker.getEventPublisher()
    .onEvent(event -> logger.info(...));

You can use RxJava or Spring Reactor Adapters to convert the EventPublisher into a Reactive Stream. The advantage of a Reactive Stream is that you can use RxJava’s observeOn operator to specify a different Scheduler that the CircuitBreaker will use to send notifications to its observers/consumers.

RxJava2Adapter.toFlowable(circuitBreaker.getEventPublisher())
    .filter(event -> event.getEventType() == Type.ERROR)
    .cast(CircuitBreakerOnErrorEvent.class)
    .subscribe(event -> logger.info(...))
ℹ️
You can also consume events from other components. Find out more in our User Guide.

9. Talks

0:34

Battle of the Circuit Breakers: Resilience4J vs Istio

Nicolas Frankel

GOTO Berlin

0:33

Battle of the Circuit Breakers: Istio vs. Hystrix/Resilience4J

Nicolas Frankel

JFuture

0:42

Resilience patterns in the post-Hystrix world

Tomasz Skowroński

Cloud Native Warsaw

0:52

Building Robust and Resilient Apps Using Spring Boot and Resilience4j

David Caron

SpringOne

0:22

Hystrix is dead, now what?

Tomasz Skowroński

DevoxxPL

10. Companies that use Resilience4j

  • Deutsche Telekom (In an application with over 400 million requests per day)

  • AOL (In an application with low latency requirements)

  • Netpulse (In a system with 40+ integrations)

  • wescale.de (In a B2B integration platform)

  • Topia (In an HR application built with microservices architecture)

  • Auto Trader Group plc (The largest Britain digital automotive marketplace)

  • PlayStation Network (A platform backend)

  • TUI InfoTec GmbH (Backend applications inside of reservation booking workflow streams for accommodations)

11. License

Copyright 2020 Robert Winkler, Bohdan Storozhuk, Mahmoud Romeh, Dan Maas and others

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.