HomeGuidesChangelog
GuidesGitHubLog In
Guides

Retry

Getting started with resilience4j-retry

Create a RetryRegistry

Just like the CircuitBreaker module, this module provides an in-memory RetryRegistry which you can use to manage (create and retrieve) Retry instances.

RetryRegistry retryRegistry = RetryRegistry.ofDefaults();

Create and configure Retry

You can provide a custom global RetryConfig. In order to create a custom global RetryConfig, you can use the RetryConfig builder. You can use the builder to configure:

  • the maximum number of attempts
  • the wait duration between successive attempts
  • a custom IntervalBiFunction which calculates the waiting interval after a failure based on attempt number and result or exception.
  • a custom Predicate which evaluates if a certain response should trigger a retry attempt
  • a custom Predicate which evaluates if an exception should trigger a retry attempt
  • a list of exceptions which should trigger a retry attempt
  • a list of exceptions which should be ignored and not trigger a retry attempt
Config propertyDefault valueDescription
maxAttempts3The maximum number of attempts (including the initial call as the first attempt)
waitDuration500 [ms]A fixed wait duration between retry attempts
intervalFunctionnumOfAttempts -> waitDurationA function to modify the waiting interval after a failure. By default the wait duration remains constant.
intervalBiFunction(numOfAttempts, Either<throwable, result>) -> waitDurationA function to modify the waiting interval after a failure based on attempt number and result or exception. When used together with intervalFunction will throw an IllegalStateException.
retryOnResultPredicateresult -> falseConfigures a Predicate which evaluates if a result should be retried. The Predicate must return true, if the result should be retried, otherwise it must return false.
retryExceptionPredicatethrowable -> trueConfigures a Predicate which evaluates if an exception should be retried. The Predicate must return true, if the exception should be retried, otherwise it must return false.
retryExceptionsemptyConfigures a list of Throwable classes that are recorded as a failure and thus are retried. This parameter supports subtyping.

Note: If you are using Checked Exceptions you must use a CheckedSupplier
ignoreExceptionsemptyConfigures a list of Throwable classes that are ignored and thus are not retried. This parameter supports subtyping.
failAfterMaxAttemptsfalseA boolean to enable or disable throwing of MaxRetriesExceededException when the Retry has reached the configured maxAttempts, and the result is still not passing the retryOnResultPredicate
RetryConfig config = RetryConfig.custom()
  .maxAttempts(2)
  .waitDuration(Duration.ofMillis(1000))
  .retryOnResult(response -> response.getStatus() == 500)
  .retryOnException(e -> e instanceof WebServiceException)
  .retryExceptions(IOException.class, TimeoutException.class)
  .ignoreExceptions(BusinessException.class, OtherBusinessException.class)
  .failAfterMaxAttempts(true)
  .build();

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

// Get or create a Retry from the registry - 
// Retry will be backed by the default config
Retry retryWithDefaultConfig = registry.retry("name1");

// Get or create a Retry from the registry, 
// use a custom configuration when creating the retry
RetryConfig custom = RetryConfig.custom()
    .waitDuration(Duration.ofMillis(100))
    .build();

Retry retryWithCustomConfig = registry.retry("name2", custom);

Decorate and execute a functional interface

As you can guess Retry has all sort of higher order decorator functions just like CircuitBreaker. You can decorate any Callable, Supplier, Runnable, Consumer, CheckedRunnable, CheckedSupplier, CheckedConsumer or CompletionStage with a Retry.

// 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");

Consume emitted RegistryEvents

You can register event consumer on a RetryRegistry and take actions whenever a Retry is created, replaced or deleted.

RetryRegistry registry = RetryRegistry.ofDefaults();
registry.getEventPublisher()
  .onEntryAdded(entryAddedEvent -> {
    Retry addedRetry = entryAddedEvent.getAddedEntry();
    LOG.info("Retry {} added", addedRetry.getName());
  })
  .onEntryRemoved(entryRemovedEvent -> {
    Retry removedRetry = entryRemovedEvent.getRemovedEntry();
    LOG.info("Retry {} removed", removedRetry.getName());
  });

Use a custom IntervalFunction

If you don't want to use a fixed wait duration between retry attempts, you can configure an IntervalFunction which is used instead to calculate the wait duration for every attempt. Resilience4j provides several factory methods to simplify the creation of an IntervalFunction.

IntervalFunction defaultWaitInterval = IntervalFunction
  .ofDefaults();

// This interval function is used internally 
// when you only configure waitDuration
IntervalFunction fixedWaitInterval = IntervalFunction
  .of(Duration.ofSeconds(5));

IntervalFunction intervalWithExponentialBackoff = IntervalFunction
  .ofExponentialBackoff();

IntervalFunction intervalWithCustomExponentialBackoff = IntervalFunction
  .ofExponentialBackoff(IntervalFunction.DEFAULT_INITIAL_INTERVAL, 2d);

IntervalFunction randomWaitInterval = IntervalFunction
  .ofRandomized();

// Overwrite the default intervalFunction with your custom one
RetryConfig retryConfig = RetryConfig.custom()
  .intervalFunction(intervalWithExponentialBackoff)
  .build();