1. Introduction

Resilience4j is a lightweight, easy-to-use fault tolerance library inspired by Netflix Hystrix, but designed for Java 8 and functional programming. Lightweight, because the library only uses Vavr (formerly Javaslang), which does not have any other external library dependencies. Netflix Hystrix, in contrast, has a compile dependency to Archaius which has many more external library dependencies such as Guava and Apache Commons Configuration.
With Resilience4j you don’t have to go all-in, you can pick what you need.

Resilience provides several core modules and add-on modules:

Core modules:

  • resilience4j-circuitbreaker: Circuit breaking

  • resilience4j-ratelimiter: Rate limiting

  • resilience4j-bulkhead: Bulkheading

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

  • resilience4j-cache: Response caching

  • resilience4j-timelimiter: Timeout handling

Add-on modules

  • resilience4j-reactor: Spring Reactor adapter

  • resilience4j-rxjava2: RxJava2 adapter

  • resilience4j-micrometer: Micrometer Metrics exporter

  • resilience4j-metrics: Dropwizard Metrics exporter

  • resilience4j-prometheus: Prometheus Metrics exporter

  • resilience4j-spring-boot: Spring Boot Starter

  • resilience4j-ratpack: Ratpack Starter

  • resilience4j-retrofit: Retrofit Call Adapter Factories

  • resilience4j-vertx: Vertx Future decorator

  • resilience4j-consumer: Circular Buffer Event consumer

To highlight a few differences to Netflix Hystrix:

  • In Hystrix calls to external systems have to be wrapped in a HystrixCommand. This library, in contrast, provides higher-order functions (decorators) to enhance any functional interface, lambda expression or method reference with a Circuit Breaker, Rate Limiter or Bulkhead. Furthermore, the library provides decorators to retry failed calls or cache call results. You can stack more than one decorator on any functional interface, lambda expression or method reference. That means, you can combine a Bulkhead, RateLimiter and Retry decorator with a CircuitBreaker decorator. The advantage is that you have the choice to select the decorator you need and nothing else. Any decorated function can be executed synchronously or asynchronously by using a CompletableFuture or RxJava.

  • Hystrix, by default, stores execution results in 10 1-second window buckets. If a 1-second window bucket is passed, a new bucket is created and the oldest is dropped. This library stores execution results in Ring Bit Buffer without a statistical rolling time window. A successful call is stored as a 0 bit and a failed call is stored as a 1 bit. The Ring Bit Buffer has a configurable fixed-size and stores the bits in a long[] array which is saving memory compared to a boolean array. That means the Ring Bit Buffer only needs an array of 16 long (64-bit) values to store the status of 1024 calls. The advantage is that this CircuitBreaker works out-of-the-box for low and high frequency backend systems, because execution results are not dropped when a time window is passed.

  • Hystrix only performs a single execution when in half-open state to determine whether to close a CircuitBreaker. This library allows to perform a configurable number of executions and compares the result against a configurable threshold to determine whether to close a CircuitBreaker.

  • This library provides custom RxJava operators to decorate any Observable or Flowable with a Circuit Breaker, Bulkhead or Ratelimiter.

  • Hystrix and this library emit a stream of events which are useful to system operators to monitor metrics about execution outcomes and latency.

2. Getting started

The projects requires JDK 8. The project is published in JCenter and Maven Central.
If you use Gradle or Maven, you can include Resilience4j modules as follows.

The groupId has been changed from io.github.robwin to io.github.resilience4j

Resilience provides several core modules and add-on modules:

Core modules:

  • resilience4j-circuitbreaker: Circuit breaking

  • resilience4j-ratelimiter: Rate limiting

  • resilience4j-bulkhead: Bulkheading

  • resilience4j-retry: Automatic retrying

  • resilience4j-cache: Response caching

  • resilience4j-timelimiter: Timeout handling

Add-on modules

  • resilience4j-metrics: Dropwizard Metrics exporter

  • resilience4j-prometheus: Prometheus Metrics exporter

  • resilience4j-spring-boot: Spring Boot Starter

  • resilience4j-ratpack: Ratpack Starter

  • resilience4j-retrofit: Retrofit Call Adapter Factories

  • resilience4j-vertx: Vertx Future decorator

  • resilience4j-consumer: Circular Buffer Event consumer

2.1. Gradle

2.1.1. Release

repositories {
    jCenter()
}

compile "io.github.resilience4j:resilience4j-circuitbreaker:0.13.1"
compile "io.github.resilience4j:resilience4j-ratelimiter:0.13.1"
compile "io.github.resilience4j:resilience4j-retry:0.13.1"
compile "io.github.resilience4j:resilience4j-bulkhead:0.13.1"
compile "io.github.resilience4j:resilience4j-cache:0.13.1"
compile "io.github.resilience4j:resilience4j-timelimiter:0.13.1"

2.1.2. Snapshot

repositories {
   maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }
}

2.2. Maven

2.2.1. Release

<repositories>
    <repository>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <id>jcenter-releases</id>
        <name>jcenter</name>
        <url>http://jcenter.bintray.com</url>
    </repository>
</repositories>

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-circuitbreaker</artifactId>
    <version>0.13.1</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-ratelimiter</artifactId>
    <version>0.13.1</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-retry</artifactId>
    <version>0.13.1</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-bulkhead</artifactId>
    <version>0.13.1</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-cache</artifactId>
    <version>0.13.1</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-timelimiter</artifactId>
    <version>0.13.1</version>
</dependency>

2.2.2. Snapshot

<repositories>
    <repository>
      <id>jcenter-snapshots</id>
      <name>jcenter</name>
      <url>http://oss.jfrog.org/artifactory/oss-snapshot-local/</url>
    </repository>
</repositories>

3. Usage Guide

Resilience provides several core modules and add-on modules:

Core modules:

  • resilience4j-circuitbreaker: Circuit breaking

  • resilience4j-ratelimiter: Rate limiting

  • resilience4j-bulkhead: Bulkheading

  • resilience4j-retry: Automatic retrying

  • resilience4j-cache: Response caching

  • resilience4j-timelimiter: Timeout handling

Add-on modules

  • resilience4j-metrics: Dropwizard Metrics exporter

  • resilience4j-prometheus: Prometheus Metrics exporter

  • resilience4j-spring-boot: Spring Boot Starter

  • resilience4j-ratpack: Ratpack Starter

  • resilience4j-retrofit: Retrofit Call Adapter Factories

  • resilience4j-vertx: Vertx Future decorator

  • resilience4j-consumer: Circuit Buffer Event consumer

3.1. CircuitBreaker

3.1.1. Introduction

This library comes with an in-memory CircuitBreakerRegistry based on a ConcurrentHashMap which provides thread safety and atomicity guarantees. You can use the CircuitBreakerRegistry to manage (create and retrieve) CircuitBreaker instances. You can create a CircuitBreakerRegistry with a default global CircuitBreakerConfig for all of your CircuitBreaker instances as follows.

3.1.2. Set-Up

CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();

As an alternative you can provide your own custom global CircuitBreakerConfig. In order to create a custom global CircuitBreakerConfig or a CircuitBreakerConfig for a specific CircuitBreaker, you can use the CircuitBreakerConfig builder. You can configure:

  • the failure rate threshold in percentage above which the CircuitBreaker should trip open and start short-circuiting calls

  • the wait duration which specifies how long the CircuitBreaker should stay open, before it switches to half open

  • the size of the ring buffer when the CircuitBreaker is half open

  • the size of the ring buffer when the CircuitBreaker is closed

  • a custom CircuitBreakerEventListener which handles CircuitBreaker events

  • a custom Predicate which evaluates if an exception should be recorded as a failure and thus increase the failure rate

// Create a custom configuration for a CircuitBreaker
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)
    .waitDurationInOpenState(Duration.ofMillis(1000))
    .ringBufferSizeInHalfOpenState(2)
    .ringBufferSizeInClosedState(2)
    .build();

// Create a CircuitBreakerRegistry with a custom global configuration
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);

// Get a CircuitBreaker from the CircuitBreakerRegistry with the global default configuration
CircuitBreaker circuitBreaker2 = circuitBreakerRegistry.circuitBreaker("otherName");

// Get a CircuitBreaker from the CircuitBreakerRegistry with a custom configuration
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("uniqueName", circuitBreakerConfig);

If you don’t want to use the CircuitBreakerRegistry to manage CircuitBreaker instances, you can also create instances directly:

CircuitBreaker defaultCircuitBreaker = CircuitBreaker.ofDefaults("testName");

CircuitBreaker customCircuitBreaker = CircuitBreaker.of("testName", circuitBreakerConfig);

3.1.3. Examples

You can decorate any Supplier / Runnable / Function or CheckedRunnable / CheckedFunction function with CircuitBreaker.decorateCheckedSupplier(), CircuitBreaker.decorateCheckedRunnable() or CircuitBreaker.decorateCheckedFunction().
You can invoke the decorated function with Try.of(…​) or Try.run(…​) from Vavr. This allows to chain further functions with map, flatMap, filter, recover or andThen. The chained functions are only invoked, if the CircuitBreaker is CLOSED or HALF_OPEN.
In the following example, Try.of(…​) returns a Success<String> Monad, if the invocation of the function is successful. If the function throws an exception, a Failure<Throwable> Monad is returned and map is not invoked.

// Given
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");

// When I decorate my function
CheckedFunction0<String> decoratedSupplier = CircuitBreaker
        .decorateCheckedSupplier(circuitBreaker, () -> "This can be any method which returns: 'Hello");

// and chain an other function with map
Try<String> result = Try.of(decoratedSupplier)
                .map(value -> value + " world'");

// Then the Try Monad returns a Success<String>, if all functions ran successfully.
assertThat(result.isSuccess()).isTrue();
assertThat(result.get()).isEqualTo("This can be any method which returns: 'Hello world'");

You can also chain up functions which are decorated by different CircuitBreakers.

// Given
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");
CircuitBreaker anotherCircuitBreaker = CircuitBreaker.ofDefaults("anotherTestName");

// When I create a Supplier and a Function which are decorated by different CircuitBreakers
CheckedFunction0<String> decoratedSupplier = CircuitBreaker
        .decorateCheckedSupplier(circuitBreaker, () -> "Hello");

CheckedFunction1<String, String> decoratedFunction = CircuitBreaker
        .decorateCheckedFunction(anotherCircuitBreaker, (input) -> input + " world");

// and I chain a function with map
Try<String> result = Try.of(decoratedSupplier)
        .mapTry(decoratedFunction::apply);

// Then
assertThat(result.isSuccess()).isTrue();
assertThat(result.get()).isEqualTo("Hello world");
CircuitBreaker and RxJava

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

CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName");
Observable.fromCallable(backendService::doSomething)
    .lift(CircuitBreakerOperator.of(circuitBreaker))

Other reactive types (Flowable, Single, Maybe and Completable) are also supported.

CircuitBreaker and Reactor

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

CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName");
Mono.fromCallable(backendService::doSomething)
    .transform(CircuitBreakerOperator.of(circuitBreaker))

Flux is also supported.

OPEN CircuitBreaker example

In this example map is not invoked, because the CircuitBreaker is OPEN. The call to Try.of returns a Failure<Throwable> Monad so that the chained function is not invoked.

// Given
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
        .ringBufferSizeInClosedState(2)
        .waitDurationInOpenState(Duration.ofMillis(1000))
        .build();
CircuitBreaker circuitBreaker = CircuitBreaker.of("testName", circuitBreakerConfig);

// Simulate a failure attempt
circuitBreaker.onError(0, new RuntimeException());
// CircuitBreaker is still CLOSED, because 1 failure is allowed
assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);
// Simulate a failure attempt
circuitBreaker.onError(0, new RuntimeException());
// CircuitBreaker is OPEN, because the failure rate is above 50%
assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN);

// When I decorate my function and invoke the decorated function
Try<String> result = Try.of(CircuitBreaker.decorateCheckedSupplier(circuitBreaker, () -> "Hello"))
        .map(value -> value + " world");

// Then the call fails, because CircuitBreaker is OPEN
assertThat(result.isFailure()).isTrue();
// Exception is CircuitBreakerOpenException
assertThat(result.failed().get()).isInstanceOf(CircuitBreakerOpenException.class);

3.1.4. Reset

The CircuitBreaker supports resetting, returning to its original state while losing all the current metrics.

circuitBreaker.reset();
Recover from an exception

If you want to recover from any exception, you can chain the method Try.recover(). The recovery method is only invoked, if Try.of() returns a Failure<Throwable> Monad.

// Given
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");

// When I decorate my function and invoke the decorated function
CheckedFunction0<String> checkedSupplier = CircuitBreaker.decorateCheckedSupplier(circuitBreaker, () -> {
    throw new RuntimeException("BAM!");
});
Try<String> result = Try.of(checkedSupplier)
        .recover(throwable -> "Hello Recovery");

// Then the function should be a success, because the exception could be recovered
assertThat(result.isSuccess()).isTrue();
// and the result must match the result of the recovery function.
assertThat(result.get()).isEqualTo("Hello Recovery");
Customize the exception handler

The default exception handler counts all type of exceptions as failures and triggers the CircuitBreaker. If you want to use a custom exception handler, you have to implement the functional interface Predicate which has a method test. The Predicate must return true if the exception should count as a failure, otherwise it must return false.
The following example shows how to ignore an IOException, but all other exception types still count as failures.

// Given
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
        .ringBufferSizeInClosedState(2)
        .ringBufferSizeInHalfOpenState(2)
        .waitDurationInOpenState(Duration.ofMillis(1000))
        .recordFailure(throwable -> Match(throwable).of(
                Case($(instanceOf(WebServiceException.class)), true),
                Case($(), false)))
        .build();
CircuitBreaker circuitBreaker = CircuitBreaker.of("testName", circuitBreakerConfig);

// Simulate a failure attempt
circuitBreaker.onError(0, new WebServiceException());
// CircuitBreaker is still CLOSED, because 1 failure is allowed
assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);

//When
CheckedRunnable checkedRunnable = CircuitBreaker.decorateCheckedRunnable(circuitBreaker, () -> {
    throw new SocketTimeoutException("BAM!");
});
Try result = Try.run(checkedRunnable);

//Then
assertThat(result.isFailure()).isTrue();
// CircuitBreaker is still CLOSED, because SocketTimeoutException has not been recorded as a failure
assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);
assertThat(result.failed().get()).isInstanceOf(IOException.class);
Consume emitted CircuitBreakerEvents

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 could use the CircularEventConsumer to store events in a circular buffer with a fixed capacity.

CircularEventConsumer<CircuitBreakerEvent> ringBuffer = new CircularEventConsumer<>(10);
circuitBreaker.getEventPublisher().onEvent(ringBuffer);
List<CircuitBreakerEvent> bufferedEvents = ringBuffer.getBufferedEvents()

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(...))

3.1.5. Monitoring

The CircuitBreaker provides an interface to monitor the current metrics.

CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
// Returns the failure rate in percentage.
float failureRate = metrics.getFailureRate();
// Returns the current number of buffered calls.
int bufferedCalls = metrics.getNumberOfBufferedCalls();
// Returns the current number of failed calls.
int failedCalls = metrics.getNumberOfFailedCalls();

3.2. RateLimiter

3.2.1. Introduction

The RateLimiter API is very similar to CircuitBreaker.
So it also have in-memory RateLimiterRegistry and RateLimiterConfig where you can configure:

  • the period of limit refresh, after each period rate limiter sets its permissions count to limitForPeriod value.

  • the permissions limit for refresh period.

  • the default wait for permission duration.

3.2.2. Examples

// For example you want to restrict the calling rate of some method to be not higher than 10 req/ms.
RateLimiterConfig config = RateLimiterConfig.builder()
    .limitRefreshPeriod(Duration.ofMillis(1))
    .limitForPeriod(10)
    .timeoutDuration(Duration.ofMillis(25))
    .build();

// Create registry
RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.of(config);

// Use registry
RateLimiter rateLimiterWithDefaultConfig = rateLimiterRegistry.rateLimiter("backend");
RateLimiter rateLimiterWithCustomConfig = rateLimiterRegistry.rateLimiter("backend#2", config);

// Or create RateLimiter directly
RateLimiter rateLimiter = RateLimiter.of("NASDAQ :-)", config);
Use a RateLimiter

As you can guess RateLimiter has all sort of higher order decorator functions just like CircuitBreaker.

// Decorate your call to BackendService.doSomething()
CheckedRunnable restrictedCall = RateLimiter
    .decorateCheckedRunnable(rateLimiter, backendService::doSomething);

Try.run(restrictedCall)
    .andThenTry(restrictedCall)
    .onFailure((RequestNotPermitted throwable) -> LOG.info("Wait before call it again :)"));
Dynamic rate limiter reconfiguration

You can use changeTimeoutDuration and changeLimitForPeriod methods to change rate limiter params in runtime.

// Decorate your call to BackendService.doSomething()
CheckedRunnable restrictedCall = RateLimiter
    .decorateCheckedRunnable(rateLimiter, backendService::doSomething);

Try.run(restrictedCall)
    .andThenTry(restrictedCall)
    .onFailure((RequestNotPermitted throwable) -> LOG.info("Wait before call it again :)"));

// durring second refresh cycle limiter will get 100 permissions
rateLimiter.changeLimitForPeriod(100);

New timeout duration won’t affect threads that are currently waiting for permission.
New limit won’t affect current period permissions and will apply only from next one.

RateLimiter and RxJava

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

RateLimiter rateLimiter = RateLimiter.ofDefaults("backendName");
Observable.fromCallable(backendService::doSomething)
    .lift(RateLimiterOperator.of(rateLimiter))

Other reactive types (Flowable, Single, Maybe and Completable) are also supported.

RateLimiter and Reactor

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

RateLimiter rateLimiter = RateLimiter.ofDefaults("backendName");
Mono.fromCallable(backendService::doSomething)
    .transform(RateLimiterOperator.of(rateLimiter))

Flux is also supported.

Consume emitted RateLimiterEvents

The RateLimiter emits a stream of RateLimiterEvents. An event can be a successful permission acquire or acquire failure.
All events contains additional information like event creation time and rate limiter name.
If you want to consume events, you have to register an event consumer.

rateLimiter.getEventPublisher()
    .onSuccess(event -> logger.info(...))
    .onFailure(event -> logger.info(...));

You can use RxJava or Spring Reactor Adapters to convert the EventPublisher into a Reactive Stream.

RxJava2Adapter.toFlowable(rateLimiter.getEventPublisher())
    .filter(event -> event.getEventType() == FAILED_ACQUIRE)
    .subscribe(event -> logger.info(...))

3.2.3. Monitoring

The RateLimiter provides simple an interface to monitor the current limiter.
Also AtomicRateLimiter has some enhanced Metrics with some implementation specific details.

RateLimiter limit;
RateLimiter.Metrics metrics = limit.getMetrics();
int numberOfThreadsWaitingForPermission = metrics.getNumberOfWaitingThreads();
// Estimates count of available permissions. Can be negative if some permissions where reserved.
int availablePermissions = metrics.getAvailablePermissions();

AtomicRateLimiter atomicLimiter;
// Estimated time duration in nanos to wait for the next permission
long nanosToWaitForPermission = atomicLimiter.getNanosToWait();
Low level API

If you’re interested in making your own thing on top of Resilience4j RateLimiter you’ll possibly be interested in
our lower level APIs like getPermission and reservePermission methods.
=== Bulkhead

3.2.4. Introduction

Provides an implementation of a bulkhead pattern that can be used to limit the amount of parallel executions - in case of backend calls to downstream dependencies, the bulkhead provides dependency isolation and load shedding. For cpu-bound work, the bulkhead provides load shedding only.

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. It is up to the client to ensure correct thread pool sizing that will be consistent with bulkhead configuration.

3.2.5. Set-Up

Just like the CircuitBreaker, this module provides in-memory 'BulkheadRegistry'. You can use the registry to manage (create and retrieve) Bulkhead instances.

BulkheadRegistry bulkheadRegistry = BulkheadRegistry.ofDefaults();

As an alternative you can provide custom global BulkheadConfig. In order to create a custom global BulkheadConfig or a BulkheadConfig for a specific Bulkhead, you can use the BulkheadConfig builder. You can use the builder to configure:

  • max amount of parallel executions allowed by the bulkhead

  • max amount of time a thread can be blocked for when attempting to enter a saturated bulkhead

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

// Create a BulkheadRegistry with a custom global configuration
BulkheadRegistry registry = BulkheadRegistry.of(config);

// Get-Or-Create a Bulkhead from the registry - bulkhead will be backed by the default config
Bulkhead bulkhead1 = registry.bulkhead("foo");

// Get-Or-Create a Bulkhead from the registry, use a custom configuration when creating the bulkhead
BulkheadConfig custom = BulkheadConfig.custom()
                                      .maxWaitTime(0)
                                      .build();

Bulkhead bulkhead2 = registry.bulkhead("bar", custom);

If you don’t want to use the BulkheadRegistry to manage Bulkhead instances, you can also create instances directly:

Bulkhead bulkhead1 = Bulkhead.ofDefaults("foo");

Bulkhead bulkhead2 = Bulkhead.of(
                         "bar",
                         BulkheadConfig.custom()
                                       .maxConcurrentCalls(50)
                                       .build()
                     );

3.2.6. Examples

You can decorate any Supplier / Runnable / Function or CheckedSupplier / CheckedRunnable / CheckedFunction function with Bulkhead.decorateCheckedSupplier(), Bulkhead.decorateCheckedRunnable() or Bulkhead.decorateCheckedFunction().
You can invoke the decorated function with Try.of(…​) or Try.run(…​) from Javaslang. This allows to chain further functions with map, flatMap, filter, recover or andThen. The chained functions are only invoked, if the Bulkhead is not saturated. In the following example, Try.of(…​) returns a Success<String> Monad, if the invocation of the function is successful. If the function throws an exception, a Failure<Throwable> Monad is returned and map is not invoked.

// Given
Bulkhead bulkhead = Bulkhead.of("testName", config);

// When I decorate my function
CheckedFunction0<String> decoratedSupplier = Bulkhead.decorateCheckedSupplier(bulkhead, () -> "This can be any method which returns: 'Hello");

// and chain an other function with map
Try<String> result = Try.of(decoratedSupplier)
                        .map(value -> value + " world'");

// Then the Try Monad returns a Success<String>, if all functions ran successfully.
assertThat(result.isSuccess()).isTrue();
assertThat(result.get()).isEqualTo("This can be any method which returns: 'Hello world'");
assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1);

You can also chain up functions which are decorated by different Bulkheads and/or CircuitBreakers.

// Given
Bulkhead bulkhead = Bulkhead.of("test", config);
Bulkhead anotherBulkhead = Bulkhead.of("testAnother", config);

// When I create a Supplier and a Function which are decorated by different Bulkheads
CheckedFunction0<String> decoratedSupplier
    = Bulkhead.decorateCheckedSupplier(bulkhead, () -> "Hello");

CheckedFunction1<String, String> decoratedFunction
    = Bulkhead.decorateCheckedFunction(anotherBulkhead, (input) -> input + " world");

// and I chain a function with map
Try<String> result = Try.of(decoratedSupplier)
                        .mapTry(decoratedFunction::apply);

// Then
assertThat(result.isSuccess()).isTrue();
assertThat(result.get()).isEqualTo("Hello world");
assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1);
assertThat(anotherBulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(1);

3.2.7. Dynamic bulkhead reconfiguration

You can use changeConfig method to modify bulkhead params in runtime.
NOTE! New maxWaitTime duration won’t affect threads that are currently waiting for permission.

Bulkhead and RxJava

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

Bulkhead bulkhead = Bulkhead.ofDefaults("backendName");
Observable.fromCallable(backendService::doSomething)
          .lift(BulkheadOperator.of(bulkhead));

Other reactive types (Flowable, Single, Maybe and Completable) are also supported.

Bulkhead and Reactor

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

Bulkhead bulkhead = Bulkhead.ofDefaults("backendName");
Mono.fromCallable(backendService::doSomething)
          .transform(BulkheadOperator.of(bulkhead));

Flux is also supported.

Saturated Bulkhead example

In this example the decorated runnable is not executed because the Bulkhead is saturated and will not allow any more parallel executions. The call to Try.run returns a Failure<Throwable> Monad so that the chained function is not invoked.

// Given
BulkheadConfig config = BulkheadConfig.custom().maxConcurrentCalls(2).build();
Bulkhead bulkhead = Bulkhead.of("test", config);
bulkhead.isCallPermitted();
bulkhead.isCallPermitted();

// When
CheckedRunnable checkedRunnable = Bulkhead.decorateCheckedRunnable(bulkhead, () -> {throw new RuntimeException("BAM!");});
Try result = Try.run(checkedRunnable);

//Then
assertThat(result.isFailure()).isTrue();
assertThat(result.failed().get()).isInstanceOf(BulkheadFullException.class);
Consume emitted BulkheadEvents

The BulkHead emits a stream of BulkHeadEvents. There are two types of events emitted: permitted execution, rejected execution & finished execution. If you want to consume these events, you have to register an event consumer.

bulkhead.getEventPublisher()
    .onCallPermitted(event -> logger.info(...))
    .onCallRejected(event -> logger.info(...))
    .onCallFinished(event -> logger.info(...));

3.2.8. Monitoring

The Bulkhead provides an interface to monitor the current metrics.

Bulkhead.Metrics metrics = bulkhead.getMetrics();
// Returns the number of parallel executions this bulkhead can support at this point in time.
in remainingBulkheadDepth = metrics.getAvailableConcurrentCalls()

3.3. Retry

3.3.1. Set-Up

You can also retry a failed function and recover from the exception, if the maximum retry count was reached. You can create a Retry context using a default configuration as follows.

// Create a Retry context with a default global configuration
// (maxAttempts = 3, waitDurationInOpenState = 500[ms])
RetryConfig config = RetryConfig.custom()
    .maxAttempts(3)
    .waitDuration(Duration.ofMillis(500))
    .build();
Retry retry = Retry.of("id", config);

In order to create a custom-configured Retry, you can use the RetryConfig builder. You can configure the maximum number of retry attempts and the wait duration between successive attempts. Furthermore, you can configure a custom Predicate which evaluates if an exception should trigger a retry.

RetryConfig config = RetryConfig.custom()
    .maxAttempts(2)
    .waitDuration(Duration.ofMillis(100))
    .retryOnException(throwable -> API.Match(throwable).of(
            API.Case($(Predicates.instanceOf(WebServiceException.class)), true),
            API.Case($(), false)))
    .build();

3.3.2. Examples

You can decorate any Supplier / Runnable / Function or CheckedSupplier / CheckedRunnable / CheckedFunction function with Retry.decorateCheckedSupplier(), Retry.decorateCheckedRunnable() or Retry.decorateCheckedFunction().

// Given I have a HelloWorldService which throws an exception
HelloWorldService  helloWorldService = mock(HelloWorldService.class);
given(helloWorldService.sayHelloWorld()).willThrow(new WebServiceException("BAM!"));

// Create a Retry with default configuration
Retry retry = Retry.ofDefaults("id");
// Decorate the invocation of the HelloWorldService
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(retry, helloWorldService::sayHelloWorld);

// When I invoke the function
Try<String> result = Try.of(retryableSupplier).recover((throwable) -> "Hello world from recovery function");

// Then the helloWorldService should be invoked 3 times
BDDMockito.then(helloWorldService).should(times(3)).sayHelloWorld();
// and the exception should be handled by the recovery function
assertThat(result.get()).isEqualTo("Hello world from recovery function");
Retry and RxJava

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

Retry retry = Retry.ofDefaults("backendName");
Observable.fromCallable(backendService::doSomething)
    .compose(RetryTransformer.of(retry))
Consume emitted RetryEvents

The Retry emits a stream of RetryEvents. An event can be a failure which signals that even all retries have failed or success if a retry was successful.

retry.getEventPublisher()
    .onSuccess(event -> logger.info(...))
    .onError(event -> logger.info(...));

3.4. Cache

3.4.1. Set-Up

The following example shows how to decorate a lambda expression with a Cache abstraction. The cache abstraction puts the result of the lambda expression in a cache instance (JCache) and
tries to retrieve a previous cached result from the cache before it invokes the lambda expression.
If the cache retrieval from a distributed cache fails, the exception is taken care of and the lambda expression is called.

// Create a CacheContext by wrapping a JCache instance.
javax.cache.Cache<String, String> cacheInstance = Caching.getCache("cacheName", String.class, String.class);
Cache<String, String> cacheContext = Cache.of(cacheInstance);

// Decorate your call to BackendService.doSomething()
CheckedFunction1<String, String> cachedFunction = Decorators.ofCheckedSupplier(() -> backendService.doSomething())
    .withCache(cacheContext)
    .decorate();
String value = Try.of(() -> cachedFunction.apply("cacheKey")).get();

3.4.2. Example

The Cache emits a stream of CacheEvents. An event can be a cache hit, a cache miss or an error.

cacheContext.getEventPublisher()
    .onCacheHit(event -> logger.info(...))
    .onCacheMiss(event -> logger.info(...))
    .onError(event -> logger.info(...));

3.5. TimeLimiter

3.5.1. Introduction

The TimeLimiter API sets a time limit on the execution of a supplied future. The decorated supplied
future can be chained with a CircuitBreaker to trip the circuit breaker if the supplied
future’s timeout has exceeded.

The TimeLimiter provides the flexibility to handle custom future implementations such as ones that
can cancel its execution gracefully. The TimeLimiter by default is configured to cancel the future
if an exception occurs. This is configurable within the configuration.

3.5.2. Examples

// For example, you want to restrict the execution of a long running task to 60 seconds.
TimeLimiterConfig config = TimeLimiterConfig.custom()
    .timeoutDuration(Duration.ofSeconds(60))
    .cancelRunningFuture(true)
    .build();

// Create TimeLimiter
TimeLimiter timeLimiter = TimeLimiter.of(config);
Using a TimeLimiter

TimeLimiter takes a Future Supplier and returns a Callable that will unwrap the Future and attempt
to retrieve the future’s value within the configured timeout period. If the timeout is reached, then
an exception is thrown upstream and TimeLimiter, if configured, will attempt to cancel the future.

ExecutorService executorService = Executors.newSingleThreadExecutor();

// Wrap your call to BackendService.doSomething() in a future provided by your executor
Supplier<Future<Integer>> futureSupplier = () -> executorService.submit(backendService::doSomething)

// Decorate your supplier so that the future can be retrieved and executed upon
Callable restrictedCall = TimeLimiter
    .decorateFutureSupplier(timeLimiter, futureSupplier);

Try.of(restrictedCall.call)
    .onFailure(throwable -> LOG.info("A timeout possibly occurred."));
TimeLimiter and CircuitBreaker

The following example shows how to apply a timelimit to a circuit breaker callable.

Callable restrictedCall = TimeLimiter
    .decorateFutureSupplier(timeLimiter, futureSupplier);

// Decorate the restricted callable with a CircuitBreaker
Callable chainedCallable = CircuitBreaker.decorateCallable(circuitBreaker, restrictedCall);

Try.of(chainedCallable::call)
    .onFailure(throwable -> LOG.info("We might have timed out or the circuit breaker has opened."));

4. Add-on modules Usage Guide

4.1. Spring Boot Starter

4.1.1. Gradle

Add the Spring Boot Starter of Resilience4j to your compile dependency:

repositories {
	maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }
	mavenCentral()
}


dependencies {
	compile('io.github.resilience4j:resilience4j-spring-boot:{release-version}')
}

4.1.2. Monitoring

Spring Boot Actuator health information can be used to check the status of your running application.
It is often used by monitoring software to alert someone if a production system has serious issues.

4.1.3. CircuitBreaker

This demo publishes the status and metrics of all CircuitBreakers via a custom CircuitBreakerHealthIndicator.
A closed CircuitBreaker state is mapped to UP, an open state to DOWN and a half-open state to UNKNOWN.

For example:

{
  "status": "UP",
  "backendACircuitBreaker": {
    "status": "DOWN",
    "failureRate": "60.0%",
    "failureRateThreshold": "50.0%",
    "maxBufferedCalls": 5,
    "bufferedCalls": 5,
    "failedCalls": 3,
    "notPermittedCalls": 0
  },
  "backendBCircuitBreaker": {
    "status": "UP",
    "failureRate": "0.0%",
    "failureRateThreshold": "50.0%",
    "maxBufferedCalls": 10,
    "bufferedCalls": 10,
    "failedCalls": 0,
    "notPermittedCalls": 0
  }
}

When you want to publish CircuitBreaker metrics on the Metrics endpoint, you can add resilience4j-metrics to register metrics in a Dropwizard Metrics Registry.
For example:

{
    "resilience4j.circuitbreaker.backendA.successful": 2,
    "resilience4j.circuitbreaker.backendA.failed": 3,
    "resilience4j.circuitbreaker.backendA.buffered": 5,
    "resilience4j.circuitbreaker.backendA.buffered_max": 5,
    "resilience4j.circuitbreaker.backendA.not_permitted": 7,
    "resilience4j.circuitbreaker.backendB.successful": 0,
    "resilience4j.circuitbreaker.backendB.failed": 0,
    "resilience4j.circuitbreaker.backendB.buffered": 0,
    "resilience4j.circuitbreaker.backendB.buffered_max": 10,
    "resilience4j.circuitbreaker.backendB.not_permitted": 0
}

When you want to publish CircuitBreaker endpoints on the Prometheus endpoint, you have to add the optional module resilience4j-prometheus.
For example:

# HELP resilience4j_circuitbreaker_calls Circuit Breaker Call Stats
# TYPE resilience4j_circuitbreaker_calls gauge
resilience4j_circuitbreaker_calls{name="backendB",call_result="successful",} 0.0
resilience4j_circuitbreaker_calls{name="backendB",call_result="failed",} 0.0
resilience4j_circuitbreaker_calls{name="backendB",call_result="not_permitted",} 0.0
resilience4j_circuitbreaker_calls{name="backendB",call_result="buffered",} 0.0
resilience4j_circuitbreaker_calls{name="backendB",call_result="buffered_max",} 10.0
resilience4j_circuitbreaker_calls{name="backendA",call_result="successful",} 0.0
resilience4j_circuitbreaker_calls{name="backendA",call_result="failed",} 0.0
resilience4j_circuitbreaker_calls{name="backendA",call_result="not_permitted",} 0.0
resilience4j_circuitbreaker_calls{name="backendA",call_result="buffered",} 0.0
resilience4j_circuitbreaker_calls{name="backendA",call_result="buffered_max",} 5.0
# HELP resilience4j_circuitbreaker_states Circuit Breaker States
# TYPE resilience4j_circuitbreaker_states gauge
resilience4j_circuitbreaker_states{name="backendB",state="closed",} 1.0
resilience4j_circuitbreaker_states{name="backendB",state="open",} 0.0
resilience4j_circuitbreaker_states{name="backendB",state="half_open",} 0.0
resilience4j_circuitbreaker_states{name="backendA",state="closed",} 1.0
resilience4j_circuitbreaker_states{name="backendA",state="open",} 0.0
resilience4j_circuitbreaker_states{name="backendA",state="half_open",} 0.0

4.1.4. RateLimiter

This demo publishes the status and metrics of all RateLimiter via a custom RateLimiterHealthIndicator.
RateLimiterHealthIndicator changes its state DOWN only if there is some permission waiting threads
and they won’t be able to unblock until timeout.

For example:

{
  "status": "UP",
  "backendARateLimiter": {
    "status": "UP",
    "availablePermissions": 10,
    "numberOfWaitingThreads": 0
  }
}

You can publish RateLimiter metrics on the Metrics endpoint,
you can add resilience4j-metrics to register metrics in a Dropwizard Metrics Registry.
For example:

{
    "resilience4j.ratelimiter.backendA.available_permissions": 10,
    "resilience4j.ratelimiter.backendA.number_of_waiting_threads": 0,
    "resilience4j.ratelimiter.backendB.available_permissions": 6,
    "resilience4j.ratelimiter.backendB.number_of_waiting_threads": 0
}

4.1.5. Configuration

CircuitBreaker

You can configure your CircuitBreakers in Spring Boot’s application.yml config file.
For example

resilience4j.circuitbreaker:
    backends:
        backendA:
            ringBufferSizeInClosedState: 5
            ringBufferSizeInHalfOpenState: 3
            waitInterval: 5000
            failureRateThreshold: 50
            eventConsumerBufferSize: 10
            registerHealthIndicator: true
            recordFailurePredicate: com.foo.FooRecordFailurePredicate
            recordExceptions:
                - org.springframework.web.client.HttpServerErrorException
            ignoreExceptions:
                - org.springframework.web.client.HttpClientErrorException
        backendB:
            ringBufferSizeInClosedState: 10
            ringBufferSizeInHalfOpenState: 5
            waitInterval: 5000
            failureRateThreshold: 50
            eventConsumerBufferSize: 10
            registerHealthIndicator: true
            recordFailurePredicate: com.foo.FooRecordFailurePredicate
            recordExceptions:
                - org.springframework.web.client.HttpServerErrorException
            ignoreExceptions:
                - org.springframework.web.client.HttpClientErrorException
RateLimiter

You can configure your CircuitBreakers in Spring Boot’s application.yml config file.
For example

resilience4j.ratelimiter:
    limiters:
        backendA:
            limitForPeriod: 10
            limitRefreshPeriodInMillis: 1000
            timeoutInMillis: 0
            subscribeForEvents: true
            registerHealthIndicator: true
            eventConsumerBufferSize: 100
        backendB:
            limitForPeriod: 6
            limitRefreshPeriodInMillis: 500
            timeoutInMillis: 3000
Explicit ordering for CircuitBreaker and RateLimiter aspects

You can adjust RateLimiterProperties.rateLimiterAspectOrder and CircuitBreakerProperties.circuitBreakerAspectOrder
and explicitly define CircuitBreaker and RateLimiter execution sequence.
By default CircuitBreaker will be executed BEFORE RateLimiter.

Please be careful changing of CircuitBreaker/RateLimiter ordering can drastically change application behavior.

4.1.6. Event Monitoring

CircuitBreaker

The emitted CircuitBreaker events are stored in a separate circular event consumer buffers. The size of a event consumer buffer can be configured per CircuitBreaker in the application.yml file (eventConsumerBufferSize).
The demo adds a custom Spring Boot Actuator endpoint which can be used to monitor the emitted events of your CircuitBreakers.
The endpoint /management/circuitbreaker lists the names of all CircuitBreaker instances.
For example:

{
    "circuitBreakers": [
      "backendA",
      "backendB"
    ]
}

The endpoint /management/circuitbreaker/events lists the latest 100 emitted events of all CircuitBreaker instances.
The endpoint /management/circuitbreaker/stream/events streams emitted events of all CircuitBreaker instances using Server-Sent Events.

{
"circuitBreakerEvents":[
  {
    "circuitBreakerName": "backendA",
    "type": "ERROR",
    "creationTime": "2017-01-10T15:39:17.117+01:00[Europe/Berlin]",
    "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendA",
    "type": "SUCCESS",
    "creationTime": "2017-01-10T15:39:20.518+01:00[Europe/Berlin]",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendB",
    "type": "ERROR",
    "creationTime": "2017-01-10T15:41:31.159+01:00[Europe/Berlin]",
    "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendB",
    "type": "SUCCESS",
    "creationTime": "2017-01-10T15:41:33.526+01:00[Europe/Berlin]",
    "durationInMs": 0
  }
]
}

The endpoint /management/circuitbreaker/events/{circuitBreakerName} lists the latest emitted events of a specific CircuitBreaker.
The endpoint /management/circuitbreaker/stream/events/{circuitBreakerName} streams emitted events using Server-Sent Events.
For example /management/circuitbreaker/events/backendA:

{
"circuitBreakerEvents":[
  {
    "circuitBreakerName": "backendA",
    "type": "ERROR",
    "creationTime": "2017-01-10T15:39:17.117+01:00[Europe/Berlin]",
    "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendA",
    "type": "SUCCESS",
    "creationTime": "2017-01-10T15:39:20.518+01:00[Europe/Berlin]",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendA",
    "type": "STATE_TRANSITION",
    "creationTime": "2017-01-10T15:39:22.341+01:00[Europe/Berlin]",
    "stateTransition": "CLOSED_TO_OPEN"
  },
  {
    "circuitBreakerName": "backendA",
    "type": "NOT_PERMITTED",
    "creationTime": "2017-01-10T15:39:22.780+01:00[Europe/Berlin]"
  }
]
}

You can even filter the list of events.
The endpoint /management/circuitbreaker/events/{circuitBreakerName}/{eventType} lists the filtered events.
The endpoint /management/circuitbreaker/stream/events/{circuitBreakerName}/{eventType} streams emitted events using Server-Sent Events.
Event types can be:

  • ERROR: A CircuitBreakerEvent which informs that an error has been recorded.

  • IGNORED_ERROR: A CircuitBreakerEvent which informs that an error has been ignored.

  • SUCCESS: A CircuitBreakerEvent which informs that a success has been recorded.

  • NOT_PERMITTED: A CircuitBreakerEvent which informs that a call was not permitted because the CircuitBreaker state is OPEN.

  • STATE_TRANSITION: A CircuitBreakerEvent which informs the state of the CircuitBreaker has been changed.

For example /management/circuitbreaker/events/backendA/ERROR:

{
"circuitBreakerEvents":[
  {
    "circuitBreakerName": "backendA",
    "type": "ERROR",
    "creationTime": "2017-01-10T15:42:59.324+01:00[Europe/Berlin]",
    "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendA",
    "type": "ERROR",
    "creationTime": "2017-01-10T15:43:22.802+01:00[Europe/Berlin]",
    "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
    "durationInMs": 0
  }
]
}
RateLimiter
Unlike the CircuitBreaker events, RateLimiter events require explicit subscription.
Use property resilience4j.ratelimiter.limiters.{yourBackendName}.registerHealthIndicator=true

There are literally the same endpoints implemented for RateLimiter,
so for detailed documentation please refer to previous section:

List of available endpoints:

  • /ratelimiter/events

  • /ratelimiter/stream/events

  • /ratelimiter/events/{rateLimiterName}

  • /ratelimiter/stream/events/{rateLimiterName}

  • /ratelimiter/events/{rateLimiterName}/{eventType}

  • /ratelimiter/stream/events/{rateLimiterName}/{eventType}

Example of response:

{
  "eventsList": [
    {
      "rateLimiterName": "backendA",
      "rateLimiterEventType": "SUCCESSFUL_ACQUIRE",
      "rateLimiterCreationTime": "2017-05-05T21:29:40.463+03:00[Europe/Uzhgorod]"
    },
    {
      "rateLimiterName": "backendA",
      "rateLimiterEventType": "SUCCESSFUL_ACQUIRE",
      "rateLimiterCreationTime": "2017-05-05T21:29:40.469+03:00[Europe/Uzhgorod]"
    },
    {
      "rateLimiterName": "backendA",
      "rateLimiterEventType": "FAILED_ACQUIRE",
      "rateLimiterCreationTime": "2017-05-05T21:29:41.268+03:00[Europe/Uzhgorod]"
    }
  ]
}

4.2. Ratpack Starter

This add-on provides integration with Ratpack libraries.

4.2.1. Gradle

Add the Ratpack Starter of Resilience4j to your compile dependency:

repositories {
	maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }
	mavenCentral()
}


dependencies {
	compile('io.github.resilience4j:resilience4j-ratpack:{release-version}')
}

4.2.2. Basic Usage

Installing the Resilience4jModule module provides a BulkheadRegistry CircuitBreakerRegistry, RateLimiterRegistry,
and RetryRegistry with the default configurations. It also install the Guice method interceptors
for Bulkheads, CircuitBreakers, RateLimiters, and Retries. Finally, it allows configuration of metrics
and even the building of Bulkheads, CircuitBreakers, RateLimiters, and Retries. See below for configuration details.

Note: If you don’t register a CircuitBreakerRegistry or RateLimiterRegistry or RetryRegistry, the defaults
will be used.

For example

public class MyModule extends AbstractModule {

    @Override
    protected void configure() {
        CircuitBreakerConfig config = CircuitBreakerConfig.custom().build();
        bind(CircuitBreakerRegistry.class).toInstance(CircuitBreakerRegistry.of(config));
        Resilience4jModule module = new Resilience4jModule();
        module.configure(c -> {
            c.circuitBreaker("test", circuitBreakerConfig ->
                circuitBreakerConfig
                        .defaults(false)
                        .automaticTransitionFromOpenToHalfOpen(false)
                        .failureRateThreshold(25)
            );
        });
        install(new Resilience4jModule());
    }
}

4.2.3. Handlers

You can rate limit an endpoint by defining a RateLimiterHandler for the endpoint.

This will only rate limit the / endpoint.

ratpack {
    bindings {
        bindInstance(RateLimiterRegistry, RateLimiterRegistry.ofDefaults())
        module(Resilience4jModule)
    }
    handlers {
        get(new RateLimiterHandler(registry, 'test'))
        get {
            render 'success'
        }
        get('a') {
            render 'success'
        }
    }
}

This will rate limit all endpoints against the same RateLimiter.

ratpack {
    bindings {
        bindInstance(RateLimiterRegistry, RateLimiterRegistry.ofDefaults())
        module(Resilience4jModule)
    }
    handlers {
        all(new RateLimiterHandler(registry, 'test'))
        get {
            render 'success'
        }
        get('a') {
            render 'success'
        }
    }
}

4.2.4. Promises

Ratpack promises provide the means by which an application can become fully non-blocking and asynchronous.
Resilience4j provides transformers that can be applied to Promises. This is ideal when promising a value
that is coming from some sort of I/O source.

Bulkhead

You can easily apply a Bulkhead to any Ratpack Promise, given an existing Bulkhead instance called bulkhead.

public Promise<String> methodWhichReturnsAPromise() {
    return backendBConnector.methodWhichReturnsAPromise()
            .transform(BulkheadTransformer.of(bulkhead).recover(t -> "recovered"));
}
CircuitBreaker

You can easily apply a CircuitBreaker to any Ratpack Promise, given an existing CircuitBreaker instance called circuitBreaker.

public Promise<String> methodWhichReturnsAPromise() {
    return backendBConnector.methodWhichReturnsAPromise()
            .transform(CircuitBreakerTransformer.of(circuitBreaker).recover(t -> "recovered"));
}

You can also specify in-line which exception conditions should be recorded as a failure. In this example when
MyException is thrown, it will be recorded as a circuitbreaker failure. Other exceptions will be ignored by
the circuitBreaker.

public Promise<String> methodWhichReturnsAPromise() {
    return backendBConnector.methodWhichReturnsAPromise()
            .transform(
                CircuitBreakerTransformer
                    .of(circuitBreaker)
                    .recover(t -> "recovered")
                    .recordFailurePredicate(e -> e instanceof MyException)
            );
}
Retry

You can easily apply a Retry to any Ratpack Promise, given an existing Retry instance called retry.

public Promise<String> methodWhichReturnsAPromise() {
    return backendBConnector.methodWhichReturnsAPromise()
            .transform(RetryTransformer.of(retry).recover(t -> "recovered"));
}
RateLimiter

You can easily apply a RateLimiter to any Ratpack Promise, given an existing RateLimiter instance called rateLimiter.

public Promise<String> methodWhichReturnsAPromise() {
    return backendBConnector.methodWhichReturnsAPromise()
            .transform(RateLimiterTransformer.of(rateLimiter).recover(t -> "recovered"));
}

4.2.5. Guice AOP

Guice provides method interception capabilities. Here are provided some annotations which support
methods returning types:

  • Promise

  • CompletionStage

  • object values

  • Observable

  • Flowable

  • Single

Bulkhead

The demo shows how to use the Bulkhead annotation to have your Ratpack application limiting number of method calls.
You can either annotate a class in order to protect all public methods or just some specific methods.
For example:

@Bulkhead(name = "backendA", recovery = MyRecoveryFunction.class)
@Singleton
public class BackendAConnector implements Connector {
    ...
}

Where MyRecoveryFunction is implements io.github.resilience4j.ratpack.RecoveryFunction and provides
a fallback value that is returned when the bulkhead identified by name is full or call ends in exception.

CircuitBreaker

The demo shows how to use the CircuitBreaker annotation to make your Ratpack application more fault tolerant.
You can either annotate a class in order to protect all public methods or just some specific methods.
For example:

@CircuitBreaker(name = "backendA", recovery = MyRecoveryFunction.class)
@Singleton
public class BackendAConnector implements Connector {
    ...
}

Where MyRecoveryFunction is implements io.github.resilience4j.ratpack.RecoveryFunction and provides
a fallback value that is returned when the circuit breaker identified by name is open.

Retry

The demo shows how to use the Retry annotation to make your Ratpack application more fault tolerant.
You can either annotate a class in order to protect all public methods or just some specific methods.
For example:
`

@Retry(name = "backendA", recovery = MyRecoveryFunction.class)
@Singleton
public class BackendAConnector implements Connector {
    ...
}

Where MyRecoveryFunction is implements io.github.resilience4j.ratpack.RecoveryFunction and provides
a fallback value that is returned when the retry identified by name is has exceeded it’s max calls.

RateLimiter

The demo shows how to use the RateLimiter annotation to make your Ratpack application more fault tolerant.
You can either annotate a class in order to protect all public methods or just some specific methods.
For example:

@RateLimiter(name = "backendA", recovery = MyRecoveryFunction.class)
@Singleton
public class BackendAConnector implements Connector {
    ...
}

Where MyRecoveryFunction is implements io.github.resilience4j.ratpack.RecoveryFunction and provides
a fallback value that is returned when the rate limiter rate limit identified by name is exceeded.

4.2.6. Functional style

You can still use a functional programming style for Bulkhead, CircuitBreaker, Retry, and RateLimiter. For example:

@Singleton
public class BusinessBService implements BusinessService  {

    public Try<String> methodWithRecovery() {
        CheckedFunction0<String> backendFunction = CircuitBreaker.decorateCheckedSupplier(circuitBreaker, () -> backendBConnector.failure());
        return Try.of(backendFunction)
                .recover((throwable) -> recovery(throwable));
    }

    private String recovery(Throwable throwable) {
        // Handle exception and invoke fallback
        return "Hello world from recovery";
    }

}

4.2.7. Adding Bulkheads, CircuitBreakers, RateLimiters, and Retries

These can be defined in the module configuration or in an external configuration.
Note that the module only provide default registries, which you can replace by
binding your own.

Module configuration example:

public class MyModule extends AbstractModule {

    @Override
    protected void configure() {
        Resilience4jModule module = new Resilience4jModule();
        module.configure(c -> c
            .bulkhead("test1", b -> b
                .defaults(true)
            ).bulkhead("test2", b -> b
                .maxConcurrentCalls(100)
                .maxWaitTime(1000)
            ).circuitBreaker("test1", cb -> cb
                .defaults(true)
            ).circuitBreaker("test2", cb -> cb
                .failureRateThreshold(50)
                .waitIntervalInMillis(5000)
                .ringBufferSizeInClosedState(200)
                .ringBufferSizeInHalfOpenState(20)
            ).rateLimiter("test1", cb -> cb
                .defaults(true)
            ).rateLimiter("test2", cb -> cb
                .limitForPeriod(100)
                .limitRefreshPeriodInNanos(500)
                .timeoutInMillis(10)
            ).retry("test1", cb -> cb
                .defaults(true)
            ).retry("test2", cb -> cb
                .maxAttempts(3)
                .waitDurationInMillis(1000)
            )
        );
        install(module);
    }
}

External configuration example:

ratpack {
    serverConfig {
        yaml(getClass().classLoader.getResource('application.yml'))
        require("/resilience4j", Resilience4jConfig)
    }
    bindings {
        module(Resilience4jModule)
    }
    handlers {
        get {
            render 'ok'
        }
    }
}

Note that recordFailurePredicate cannot be specified via yaml configuration. Instead specify recordExceptions and ignoreExceptions.
The below example will record everything exception java.io.IOException.

resilience4j:
    bulkheads:
        test1:
            defaults: true
        test2:
            maxConcurrentCalls: 100
            maxWaitTime: 1000
    circuitBreakers:
        test1:
            defaults: true
        test2:
            ringBufferSizeInClosedState: 200
            ringBufferSizeInHalfOpenState: 20
            waitInterval: 5000
            failureRateThreshold: 50
            recordExceptions: java.lang.Exception
            ignoreExceptions: java.io.IOException
    rateLimiters:
        test1:
            defaults: true
        test2:
            limitForPeriod: 100
            limitRefreshPeriodInNanos: 500
            timeoutInMillis: 10
    retries:
        test1:
            defaults: true
        test2:
            maxAttempts: 3
            waitDurationInMillis: 1000

4.2.8. Metrics

Both dropwizard and prometheus metrics can be auto configured and enabled for all registered
bulkhead instances, circuitbreaker instances, ratelimiter instances, and retry instances.

For dropwizard metrics to work, add a compile dependency on resilience4j-metrics and
bind a MetricRegistry instance.

For prometheus metrics to work, add a compile dependency on resilience4j-prometheus and
bind a CollectorRegistry instance.

Enabling Dropwizard Metrics:

public class MyModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(MetricRegistry.class);
        Resilience4jModule module = new Resilience4jModule();
        module.configure(c -> c.metrics(true));
        install(module);
    }
}

Enabling Prometheus Metrics:

public class MyModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(CollectorRegistry.class);
        Resilience4jModule module = new Resilience4jModule();
        module.configure(c -> c.prometheus(true));
        install(module);
    }
}

4.2.9. Event Monitoring

Bulkhead

These are the same endpoints as implemented for Bulkhead,
so for detailed documentation please refer to previous sections.

List of available endpoints:

  • /bulkhead/events

  • /bulkhead/stream/events

  • /bulkhead/events/{bulkheadName}

  • /bulkhead/stream/events/{bulkheadName}

  • /bulkhead/events/{bulkheadName}/{eventType}

  • /bulkhead/stream/events/{bulkheadName}/{eventType}

Example of response:

{
  "bulkheadEvents": [
    {
      "bulkheadName": "backendA",
      "type": "CALL_PERMITTED",
      "creationTime": "2017-05-05T21:29:40.463+03:00[Europe/Uzhgorod]"
    },
    {
      "bulkheadName": "backendA",
      "type": "CALL_REJECTED",
      "creationTime": "2017-05-05T21:29:40.469+03:00[Europe/Uzhgorod]"
    },
    {
      "bulkheadName": "backendA",
      "type": "CALL_FINISHED",
      "creationTime": "2017-05-05T21:29:41.268+03:00[Europe/Uzhgorod]"
    }
  ]
}
CircuitBreaker

The emitted CircuitBreaker events are stored in a separate circular event consumer buffers. The size of a event consumer buffer can be configured per CircuitBreaker in the application.yml file (eventConsumerBufferSize).
The demo adds a custom Ratpack actuator endpoint which can be used to monitor the emitted events of your CircuitBreakers.
The endpoint /circuitbreaker lists the names of all CircuitBreaker instances.
For example:

{
    "circuitBreakers": [
      "backendA",
      "backendB"
    ]
}

The endpoint /circuitbreaker/events lists the latest 100 emitted events of all CircuitBreaker instances.
The endpoint /circuitbreaker/stream/events streams emitted events of all CircuitBreaker instances using Server-Sent Events.

{
"circuitBreakerEvents":[
  {
    "circuitBreakerName": "backendA",
    "type": "ERROR",
    "creationTime": "2017-01-10T15:39:17.117+01:00[Europe/Berlin]",
    "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendA",
    "type": "SUCCESS",
    "creationTime": "2017-01-10T15:39:20.518+01:00[Europe/Berlin]",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendB",
    "type": "ERROR",
    "creationTime": "2017-01-10T15:41:31.159+01:00[Europe/Berlin]",
    "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendB",
    "type": "SUCCESS",
    "creationTime": "2017-01-10T15:41:33.526+01:00[Europe/Berlin]",
    "durationInMs": 0
  }
]
}

The endpoint /circuitbreaker/events/{circuitBreakerName} lists the latest emitted events of a specific CircuitBreaker.
The endpoint /circuitbreaker/stream/events/{circuitBreakerName} streams emitted events using Server-Sent Events.
For example /circuitbreaker/events/backendA:

{
"circuitBreakerEvents":[
  {
    "circuitBreakerName": "backendA",
    "type": "ERROR",
    "creationTime": "2017-01-10T15:39:17.117+01:00[Europe/Berlin]",
    "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendA",
    "type": "SUCCESS",
    "creationTime": "2017-01-10T15:39:20.518+01:00[Europe/Berlin]",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendA",
    "type": "STATE_TRANSITION",
    "creationTime": "2017-01-10T15:39:22.341+01:00[Europe/Berlin]",
    "stateTransition": "CLOSED_TO_OPEN"
  },
  {
    "circuitBreakerName": "backendA",
    "type": "NOT_PERMITTED",
    "creationTime": "2017-01-10T15:39:22.780+01:00[Europe/Berlin]"
  }
]
}

You can even filter the list of events.
The endpoint /circuitbreaker/events/{circuitBreakerName}/{eventType} lists the filtered events.
The endpoint /circuitbreaker/stream/events/{circuitBreakerName}/{eventType} streams emitted events using Server-Sent Events.
Event types can be:

  • ERROR: A CircuitBreakerEvent which informs that an error has been recorded.

  • IGNORED_ERROR: A CircuitBreakerEvent which informs that an error has been ignored.

  • SUCCESS: A CircuitBreakerEvent which informs that a success has been recorded.

  • NOT_PERMITTED: A CircuitBreakerEvent which informs that a call was not permitted because the CircuitBreaker state is OPEN.

  • STATE_TRANSITION: A CircuitBreakerEvent which informs the state of the CircuitBreaker has been changed.

For example /circuitbreaker/events/backendA/ERROR`:

{
"circuitBreakerEvents":[
  {
    "circuitBreakerName": "backendA",
    "type": "ERROR",
    "creationTime": "2017-01-10T15:42:59.324+01:00[Europe/Berlin]",
    "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
    "durationInMs": 0
  },
  {
    "circuitBreakerName": "backendA",
    "type": "ERROR",
    "creationTime": "2017-01-10T15:43:22.802+01:00[Europe/Berlin]",
    "errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
    "durationInMs": 0
  }
]
}
RateLimiter

These are the same endpoints as implemented for CircuitBreaker,
so for detailed documentation please refer to previous section.

List of available endpoints:

  • /ratelimiter/events

  • /ratelimiter/stream/events

  • /ratelimiter/events/{retryName}

  • /ratelimiter/stream/events/{retryName}

  • /ratelimiter/events/{retryName}/{eventType}

  • /ratelimiter/stream/events/{retryName}/{eventType}

Example of response:

{
  "rateLimiterEvents": [
    {
      "rateLimiterName": "backendA",
      "rateLimiterEventType": "SUCCESSFUL_ACQUIRE",
      "rateLimiterCreationTime": "2017-05-05T21:29:40.463+03:00[Europe/Uzhgorod]"
    },
    {
      "rateLimiterName": "backendA",
      "rateLimiterEventType": "SUCCESSFUL_ACQUIRE",
      "rateLimiterCreationTime": "2017-05-05T21:29:40.469+03:00[Europe/Uzhgorod]"
    },
    {
      "rateLimiterName": "backendA",
      "rateLimiterEventType": "FAILED_ACQUIRE",
      "rateLimiterCreationTime": "2017-05-05T21:29:41.268+03:00[Europe/Uzhgorod]"
    }
  ]
}
Retry

These are the same endpoints as implemented for CircuitBreaker,
so for detailed documentation please refer to previous sections.

List of available endpoints:

  • /retry/events

  • /retry/stream/events

  • /retry/events/{retryName}

  • /retry/stream/events/{retryName}

  • /retry/events/{retryName}/{eventType}

  • /retry/stream/events/{retryName}/{eventType}

Example of response:

{
  "retryEvents": [
    {
      "retryName": "backendA",
      "retryEventType": "ERROR",
      "numberOfRetryAttempts":3,
      "retryCreationTime": "2017-05-05T21:29:40.463+03:00[Europe/Uzhgorod]"
    },
    {
      "retryName": "backendA",
      "retryEventType": "ERROR",
      "numberOfRetryAttempts":3,
      "retryCreationTime": "2017-05-05T21:29:40.469+03:00[Europe/Uzhgorod]"
    },
    {
      "retryName": "backendA",
      "retryEventType": "ERROR",
      "numberOfRetryAttempts":3,
      "retryCreationTime": "2017-05-05T21:29:41.268+03:00[Europe/Uzhgorod]"
    }
  ]
}

4.3. Retrofit

Retrofit client circuit breaking & rate limiting.

4.3.1. Circuit Breaking

Circuit breaking http client calls is based upon the CircuitBreaker instance provided to a CircuitBreakerCallAdaptor.

// Create a CircuitBreaker
private final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");

// Create a retrofit instance with CircuitBreaker call adapter
Retrofit retrofit = new Retrofit.Builder()
                .addCallAdapterFactory(CircuitBreakerCallAdapter.of(circuitBreaker))
                .baseUrl("http://localhost:8080/")
                .build();

// Get an instance of your service with circuit breaking built in.
RetrofitService service = retrofit.create(RetrofitService.class);
Timeouts

To trigger circuit breaking by timeout, the time thresholds should be set on a OkHttpClient instance passed into the
Retrofit.Builder.

// Create a CircuitBreaker
private final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");

final long TIMEOUT = 300; // ms
OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
        .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
        .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .addCallAdapterFactory(CircuitBreakerCallAdapter.of(circuitBreaker))
        .baseUrl("http://localhost:8080/")
        .client(client)
        .build();
Error responses

By default, all exceptions and responses where !Response.isSuccessful() will be recorded as an error in the CircuitBreaker.

Customising what is considered a successful response is possible like so:

Retrofit retrofit = new Retrofit.Builder()
        .addCallAdapterFactory(CircuitBreakerCallAdapter.of(circuitBreaker, (r) -> r.code() < 500));
        .baseUrl("http://localhost:8080/")
        .build();

4.3.2. Rate Limiting

Rate limiting of http client calls is based upon the configuration passed to the RateLimiterCallAdaptor.

RateLimiter rateLimiter = RateLimiter.ofDefaults("testName");

Retrofit retrofit = new Retrofit.Builder()
        .addCallAdapterFactory(RateLimiterCallAdapter.of(rateLimiter))
        .baseUrl("http://localhost:8080/")
        .build();

If the number of calls are exceeded within the period defined by the RateLimiter, a HTTP 429 response (too many requests) will be returned.

4.4. Dropwizard Metrics exporter

4.4.1. Introduction

Integration with Dropwizard Metrics.
With this add-on you can easily add your bulkhead, circuit breaker, rate limiter, retry metrics in your Dropwizard MetricRegistry.

4.4.2. Usage

Bulkhead
final MetricRegistry collectorRegistry = new MetricRegistry();

final BulkheadRegistry bulkheadRegistry = BulkheadRegistry.ofDefaults();
final Bulkhead foo = bulkheadRegistry.bulkhead("foo");
final Bulkhead boo = bulkheadRegistry.bulkhead("boo");

// you can register all bulkheads at once
collectorRegistry.registerAll(BulkheadMetrics.ofBulkheadRegistry(bulkheadRegistry));
// or register them one by one
collectorRegistry.registerAll(BulkheadMetrics.ofBulkhead(foo));

For each bulkhead this registry will export:

  • available_concurrent_calls - instantaneous read of the number of currently available concurrent calls [int]

CircuitBreaker
final MetricRegistry collectorRegistry = new MetricRegistry();

final CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
final CircuitBreaker foo = circuitBreakerRegistry.circuitBreaker("foo");
final CircuitBreaker boo = circuitBreakerRegistry.circuitBreaker("boo");

// you can register all circuit breakers at once
collectorRegistry.registerAll(CircuitBreakerMetrics.ofCircuitBreakerRegistry(circuitBreakerRegistry));
// or register them one by one
collectorRegistry.registerAll(CircuitBreakerMetrics.ofCircuitBreaker(foo));

// there is some additional useful methods in CircuitBreakerMetrics class

For each circuit breaker this registry will export:

  • state - instantaneous read of the current state where 0-CLOSED, 1-OPEN, 2-HALF-OPEN [int]

  • successful - current number of successful calls [int]

  • failed - current number of failed calls [int]

  • buffered - current number of buffered calls [int]

  • buffered_max - maximum number of buffered calls [int]

  • not_permitted - current number of not permitted calls [int]

RateLimiter
final MetricRegistry metricRegistry = new MetricRegistry();
final RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.ofDefaults();
final RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter("testLimit");

// you can register all rate limiters at once
metricRegistry.registerAll(RateLimiterMetrics.ofRateLimiterRegistry(rateLimiterRegistry));
// or register them one by one
metricRegistry.registerAll(RateLimiterMetrics.ofRateLimiter(rateLimiter));

For each rate limiter this registry will export:

  • available_permissions - current number of available permissions [int]

  • number_of_waiting_threads - current number of threads waiting for permission [int]

Retry
final MetricRegistry metricRegistry = new MetricRegistry();
final RetryRegistry retryRegistry = RetryRegistry.ofDefaults();
final Retry retry = retryRegistry.retry("testLimit");

// you can register all rate limiters at once
metricRegistry.registerAll(RetryMetrics.ofRetryRegistry(retryRegistry));
// or register them one by one
metricRegistry.registerAll(RetryMetrics.ofRetry(retry));

For each retry this registry will export:

  • successful_calls_without_retry - the number of successful calls without a retry attempt [long]

  • successful_calls_with_retry - the number of successful calls after a retry attempt [long]

  • failed_calls_without_retry - the number of failed calls without a retry attempt [long]

  • failed_calls_with_retry - the number of failed calls after all retry attempts [long]

4.5. Prometheus Metrics Integration

4.5.1. Introduction

Integration with Prometheus simple client

Module provides exporters for CircuitBreaker and RateLimiter metrics.

For the circuit breaker library exports 2 metrics:

  1. By state with default metric name circuit_breaker_states and label state:

    • closed

    • open

    • half_open

  2. By call result with default metric name circuit_breaker_calls and label call_result:

    • successful

    • failed

    • not_permitted

    • buffered

    • buffered_max

For the rate limiter following metric with default name rate_limiter and label param exported:

  • available_permissions

  • waiting_threads

The names of the rate limiters and circuit breakers are exposed using label name.

This module also provides CallMeter — a composite metric to measure single call/request metrics such as:
- execution time distribution,
- number of attempts and
- number of failures.

It is implemented in Prometheus simple client’s style, supports labels and produces histogram and counter metrics.

Usage examples provided bellow in this section.

4.5.2. Dashboard Example

Circuit Breaker Dashboard Example

4.5.3. Usage

CircuitBreaker
final CollectorRegistry collectorRegistry = CollectorRegistry.defaultRegistry;

final CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();

final CircuitBreaker foo = circuitBreakerRegistry.circuitBreaker("foo");
final CircuitBreaker boo = circuitBreakerRegistry.circuitBreaker("boo");

// Registering metrics in prometeus CollectorRegistry
collectorRegistry.register(CircuitBreakerExports.ofCircuitBreakerRegistry(circuitBreakerRegistry));
RateLimiter
final CollectorRegistry collectorRegistry = CollectorRegistry.defaultRegistry;

final RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.ofDefaults();

final RateLimiter foo = rateLimiterRegistry.rateLimiter("foo");
final RateLimiter boo = rateLimiterRegistry.rateLimiter("boo");

// Registering metrics in prometeus CollectorRegistry
collectorRegistry.register(RateLimiterExports.ofRateLimiterRegistry(rateLimiterRegistry));

For both it is possible to use just a collection of breakers and limiters instead of registry.

Call Meter

Simple example without labels

final CollectorRegistry registry = new CollectorRegistry();

final CallMeter meter = CallMeter.of("foo_call", "Foo call help", registry);

CallMeter.decorateCompletionStageSupplier(meter, () -> supplyAsync(() -> { /* ... */ }));

Advanced example with labels

final CollectorRegistry registry = new CollectorRegistry();

final CallMeter meter = CallMeter
        .builder()
        .name("foo_call")
        .help("Foo call help")
        .labelNames("label_1")
        .build()
        .register(registry);

meter.labels("boo").executeRunnable(() -> { /* ... */ });

CallMeter.decorateCompletionStageSupplier(
    meter.labels("baz"),
    () -> supplyAsync(() -> { /* ... */ })
);

4.6. Micrometer Metrics exporter

4.6.1. Introduction

Integration with Micrometer.
With this add-on you can easily add your bulkhead, circuit breaker, rate limiter, retry metrics in Micrometer MeterRegistry.

4.6.2. Usage

Bulkhead
final BulkheadRegistry bulkheadRegistry = BulkheadRegistry.ofDefaults();
final Bulkhead foo = bulkheadRegistry.bulkhead("foo");
final Bulkhead boo = bulkheadRegistry.bulkhead("boo");

// Register all bulkheads at once
BulkheadMetrics bulkheadMetrics = BulkheadMetrics.ofBulkheadRegistry(bulkheadRegistry);
bulkheadMetrics.bindTo(meterRegistry);

For each bulkhead this registry will export:

  • available_concurrent_calls - instantaneous read of the number of currently available concurrent calls [int]

CircuitBreaker
final CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
final CircuitBreaker foo = circuitBreakerRegistry.circuitBreaker("foo");
final CircuitBreaker boo = circuitBreakerRegistry.circuitBreaker("boo");

// Register all circuit breakers at once
CircuitBreakerMetrics circuitBreakerMetrics = CircuitBreakerMetrics.ofCircuitBreakerRegistry(circuitBreakerRegistry);
circuitBreakerMetrics.bindTo(meterRegistry);

For each circuit breaker this registry will export:

  • state - instantaneous read of the current state where 0-CLOSED, 1-OPEN, 2-HALF-OPEN [int]

  • successful - current number of successful calls [int]

  • failed - current number of failed calls [int]

  • buffered - current number of buffered calls [int]

  • buffered_max - maximum number of buffered calls [int]

  • not_permitted - current number of not permitted calls [int]

RateLimiter
final RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.ofDefaults();
final RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter("testLimit");

// Register rate limiters at once
RateLimiterMetrics rateLimiterMetrics = RateLimiterMetrics.ofRateLimiterRegistry(rateLimiterRegistry);
rateLimiterMetrics.bindTo(meterRegistry);

For each rate limiter this registry will export:

  • available_permissions - current number of available permissions [int]

  • number_of_waiting_threads - current number of threads waiting for permission [int]

Retry
final MetricRegistry metricRegistry = new MetricRegistry();
final RetryRegistry retryRegistry = RetryRegistry.ofDefaults();
final Retry retry = retryRegistry.retry("testLimit");

// Register all retries at once
RetryMetrics retryMetrics = RetryMetrics.ofRetryRegistry(retryRegistry);
retryMetrics.bindTo(meterRegistry);

For each retry this registry will export:

  • successful_calls_without_retry - the number of successful calls without a retry attempt [long]

  • successful_calls_with_retry - the number of successful calls after a retry attempt [long]

  • failed_calls_without_retry - the number of failed calls without a retry attempt [long]

  • failed_calls_with_retry - the number of failed calls after all retry attempts [long]

5. Implementation details

5.1. CircuitBreaker

The CircuitBreaker is implemented via a finite state machine with three normal states: CLOSED, OPEN and HALF_OPEN and two special states DISABLED and FORCED_OPEN.

state machine

The CircuitBreaker does not know anything about the backend’s state by itself, but uses the information provided by the decorators via CircuitBreaker::onSuccess and CircuitBreaker::onError. See example:

static <T> Supplier<T> decorateSupplier(CircuitBreaker circuitBreaker, Supplier<T> supplier){
    return () -> {
        CircuitBreakerUtils.isCallPermitted(circuitBreaker);
        long start = System.nanoTime();
        try {
            T returnValue = supplier.get();
            long durationInNanos = System.nanoTime() - start;
            circuitBreaker.onSuccess(durationInNanos);
            return returnValue;
        } catch (Throwable throwable) {
            long durationInNanos = System.nanoTime() - start;
            circuitBreaker.onError(durationInNanos, throwable);
            throw throwable;
        }
    };
}

The state of the CircuitBreaker changes from CLOSED to OPEN when the failure rate is above a (configurable) threshold.
Then, all access to the backend is blocked for a (configurable) time duration. CircuitBreaker::isCallPermitted() throws a CircuitBreakerOpenException, if the CircuitBreaker is OPEN.

The CircuitBreaker uses a Ring Bit Buffer in the CLOSED state to store the success or failure statuses of the calls. A successful call is stored as a 0 bit and a failed call is stored as a 1 bit. The Ring Bit Buffer has a (configurable) fixed-size. The Ring Bit Buffer uses internally a BitSet like data structure to store the bits which is saving memory compared to a boolean array. The BitSet uses a long[] array to store the bits. That means the BitSet only needs an array of 16 long (64-bit) values to store the status of 1024 calls.

Ring Bit Buffer

The Ring Bit Buffer must be full, before the failure rate can be calculated.
For example, if the size of the Ring Buffer is 10, then at least 10 calls must evaluated, before the failure rate can be calculated. If only 9 calls have been evaluated the CircuitBreaker will not trip open even if all 9 calls have failed.

After the time duration has elapsed, the CircuitBreaker state changes from OPEN to HALF_OPEN and allows calls to see if the backend is still unavailable or has become available again. The CircuitBreaker uses another (configurable) Ring Bit Buffer to evaluate the failure rate in the HALF_OPEN state. If the failure rate is above the configured threshold, the state changes back to OPEN. If the failure rate is below or equal to the threshold, the state changes back to CLOSED.
CircuitBreaker::onError checks if the exception should be recorded as a failure or should be ignored. You can configure a custom Predicate which decides whether an exception should be recorded as a failure. The default Predicate records all exceptions as a failure.

The CircuitBreaker supports resetting to its original state, losing all the metrics and resetting its Ring Bit Buffer.

CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");
circuitBreaker.reset();

The CircuitBreaker supports two more special states, DISABLED (always allow access) and FORCED_OPEN (always deny access). In these two states no CircuitBreakerEvents (apart from the state transition) are generated, and no metrics are recorded. The only way to exit from those states are to trigger a state transition or to reset the CircuitBreaker.

CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");
circuitBreaker.transitionToDisabledState();
// circuitBreaker.onFailure(...) won't trigger a state change
circuitBreaker.transitionToClosedState(); // will transition to CLOSED state and re-enable normal behaviour, keeping metrics
circuitBreaker.transitionToForcedOpenState();
// circuitBreaker.onSuccess(...) won't trigger a state change
circuitBreaker.reset(); //  will transition to CLOSED state and re-enable normal behaviour, losing metrics

The CircuitBreaker publishes a stream of CircuitBreakerEvents to any Subscriber/Consumer who subscribes. An event can be a state transition or a recorded error. This library uses RxJava to to provide this functionality. If you want to consume events, you have to subscribe to the event stream. This library provides a consumer CircuitBreakerEventConsumer which can be used to store events in a circular buffer with a fixed capacity. You can use RxJava to filter certain events.

CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");
CircuitBreakerEventConsumer ringBuffer = new CircuitBreakerEventConsumer(10);
circuitBreaker.getEventStream()
        .filter(event -> event.getEventType() == Type.ERROR)
        .subscribe(ringBuffer);

5.2. RateLimiter

Conceptually RateLimiter splits all nanoseconds from the start of epoch into cycles.
Each cycle has duration configured by RateLimiterConfig.limitRefreshPeriod.
By contract on start of each cycle RateLimiter should set activePermissions to RateLimiterConfig.limitForPeriod.
For the RateLimiter callers it is really looks so, but for example AtomicRateLimiter implementation has
some optimisations under the hood that will skip this refresh if RateLimiter is not used actively.

Rate Limiter

The default implementation of RateLimiter is AtomicRateLimiter it manages state via AtomicReference.
AtomicRateLimiter.State is completely immutable and has the folowing fields:

  • activeCycle - cycle number that was used by the last call.

  • activePermissions - count of available permissions after the last call.
    Can be negative if some permissions where reserved.

  • nanosToWait - count of nanoseconds to wait for permission for the last call.

AtomicRateLimiter is also very fast on i7-5557U processor and with x64 Java-1.8.0_112
it takes only 143±1 [ns] to acquire permission.
So you can easily restrict not only network calls but your local in-memory operations, too.

6. License

Copyright 2017 Robert Winkler, Bohdan Storozhuk, Oleksandr Goldobin, Christopher Pilsworth and Dan Maas

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.