Skip to content

dyb10101/failsafe

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Failsafe

Build Status Maven Central License JavaDoc Join the chat at https://gitter.im/jhalterman/failsafe

Introduction

Failsafe is a lightweight, zero-dependency library for handling failures in Java 8+, with a concise API for handling everyday use cases and the flexibility to handle everything else. It works by wrapping executable logic with one or more resilience policies, which can be combined and composed as needed. These policies include:

It also provides features that allow you to integrate with various scenarios, including:

Setup

Add the latest Failsafe Maven dependency to your project.

Migrating from 1.x

Failsafe 2.0 has API and behavior changes from 1.x. See the CHANGES doc for more details.

Usage

Getting Started

To start, we'll create a RetryPolicy that defines which failures should be handled and when retries should be performed:

RetryPolicy<Object> retryPolicy = new RetryPolicy<>()
  .handle(ConnectException.class)
  .withDelay(Duration.ofSeconds(1))
  .withMaxRetries(3);

We can then execute a Runnable or Supplier with retries:

// Run with retries
Failsafe.with(retryPolicy).run(() -> connect());

// Get with retries
Connection connection = Failsafe.with(retryPolicy).get(() -> connect());

We can also execute a Runnable or Supplier asynchronously with retries:

// Run with retries asynchronously
CompletableFuture<Void> future = Failsafe.with(retryPolicy).runAsync(() -> connect());

// Get with retries asynchronously
CompletableFuture<Connection> future = Failsafe.with(retryPolicy).getAsync(() -> connect());

Composing Policies

Multiple policies can be arbitrarily composed to add additional layers of resilience or to handle different failures in different ways:

CircuitBreaker<Object> circuitBreaker = new CircuitBreaker<>();
Fallback<Object> fallback = Fallback.of(this::connectToBackup);

Failsafe.with(fallback, retryPolicy, circuitBreaker).get(this::connect);

Order does matter when composing policies. See the section below for more details.

Failsafe Executor

Policy compositions can also be saved for later use via a FailsafeExecutor:

FailsafeExecutor<Object> executor = Failsafe.with(fallback, retryPolicy, circuitBreaker);
executor.run(this::connect);

Failure Policies

Failsafe uses policies to handle failures. By default, policies treat any Exception as a failure. But policies can also be configured to handle more specific failures or conditions:

policy
  .handle(ConnectException.class, SocketException.class)
  .handleIf(failure -> failure instanceof ConnectException);

They can also be configured to handle specific results or result conditions:

policy
  .handleResult(null)
  .handleResultIf(result -> result == null);  

Retries

Retry policies express when retries should be performed for an execution failure.

By default, a RetryPolicy will perform a maximum of 3 execution attempts. You can configure a max number of attempts or retries:

retryPolicy.withMaxAttempts(3);

And a delay between attempts:

retryPolicy.withDelay(Duration.ofSeconds(1));

You can add delay that backs off exponentially:

retryPolicy.withBackoff(1, 30, ChronoUnit.SECONDS);

A random delay for some range:

retryPolicy.withDelay(1, 10, ChronoUnit.SECONDS);

Or a computed delay based on an execution. You can add a random jitter factor to a delay:

retryPolicy.withJitter(.1);

Or a time based jitter:

retryPolicy.withJitter(Duration.ofMillis(100));

You can add a max retry duration:

retryPolicy.withMaxDuration(Duration.ofMinutes(5));

You can specify which results, failures or conditions to abort retries on:

retryPolicy
  .abortWhen(true)
  .abortOn(NoRouteToHostException.class)
  .abortIf(result -> result == true)

And of course you can arbitrarily combine any of these things into a single policy.

Circuit Breakers

Circuit breakers allow you to create systems that fail-fast by temporarily disabling execution as a way of preventing system overload. Creating a CircuitBreaker is straightforward:

CircuitBreaker<Object> breaker = new CircuitBreaker<>()
  .handle(ConnectException.class)
  .withFailureThreshold(3, 10)
  .withSuccessThreshold(5)
  .withDelay(Duration.ofMinutes(1));

When a configured threshold of execution failures occurs on a circuit breaker, the circuit is opened and further execution requests fail with CircuitBreakerOpenException. After a delay, the circuit is half-opened and trial executions are attempted to determine whether the circuit should be closed or opened again. If the trial executions meet a success threshold, the breaker is closed again and executions will proceed as normal.

Circuit Breaker Configuration

Circuit breakers can be flexibly configured to express when the circuit should be opened or closed.

A circuit breaker can be configured to open when a successive number of executions have failed:

breaker.withFailureThreshold(5);

Or when, for example, the last 3 out of 5 executions have failed:

breaker.withFailureThreshold(3, 5);

After opening, a breaker will delay for 1 minute by default before before attempting to close again, or you can configure a specific delay:

breaker.withDelay(Duration.ofSeconds(30));

The breaker can be configured to close again if a number of trial executions succeed, else it will re-open:

breaker.withSuccessThreshold(5);

The breaker can also be configured to close again if, for example, the last 3 out of 5 executions succeed, else it will re-open:

breaker.withSuccessThreshold(3, 5);

And the breaker can be configured to recognize executions that exceed a certain timeout as failures:

breaker.withTimeout(Duration.ofSeconds(10));

Circuit Breaker Metrics