Reactive Programming: Handling Service Timeouts and Retries with Retry4j
December 2nd, 2015 by Micha KopsThe Reactive Manifesto specifies responsive, resilient, elastic and message-driven as attributes for a reactive application.
When implementing specific mechanisms to achieve this requirements, we often need to deal with timeout and retry-operations in our application and depending on our setup and environment, different tools and libraries exist to help us here.
In the following short tutorial I’d like to demonstrate how to handle exceptions and operation retries on service boundaries with a lesser know, slim library named Retry4j.
Contents
Dependencies
Using Gradle here we just need to add one dependency, com.evanlennick:retry4j:0.4.0 to our project – this is my build.gradle:
apply plugin: 'java' apply plugin: 'eclipse' sourceCompatibility = 1.8 version = '1.0.0' repositories { mavenCentral() } dependencies { compile 'com.evanlennick:retry4j:0.4.0' }
Handling a Failing Service with Retry4j
In this short example, we’ve added a sample service that throws an exception every time its service method is called until a specific limit is reached .. then the service returns a generated identifier instead of throwing an exception.
This should simulate an overloaded service that we’re trying to call here.
We’re now using Retry4j and we’re specifying the contract for our service call retries creating a RetryConfig using the RetryConfigBuilder.
In this configuration we’re telling Retry4j to
- retry the operation when a specific exception is thrown: ApiException (retryOnSpecificExceptions())
- stop trying after 6 failed tries (withMaxNumberOfTries())
- wait 5 seconds before the next try (withDelayBetweenTries)
- uses a fixed back-off strategy so that the delay always uses the same interval. Other possible options here allow to use exponential, Fibonacci, no-delay, random-numbered and random-exponential
- for more detailed information please feel free to have a look at the project documentation here
package com.hascode.tutorial; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.UUID; import java.util.concurrent.Callable; import com.evanlennick.retry4j.CallExecutor; import com.evanlennick.retry4j.CallResults; import com.evanlennick.retry4j.RetryConfig; import com.evanlennick.retry4j.RetryConfigBuilder; import com.evanlennick.retry4j.exception.RetriesExhaustedException; import com.evanlennick.retry4j.exception.UnexpectedException; public class Example { public static void main(String[] args) { SomeApi api = new SomeApi(); Callable<String> callable = () -> api.call(); RetryConfig config = new RetryConfigBuilder() .retryOnSpecificExceptions(ApiException.class) .withMaxNumberOfTries(6).withDelayBetweenTries(Duration.of(5, ChronoUnit.SECONDS)) .withFixedBackoff() .build(); try { CallResults<String> results = new CallExecutor(config).execute(callable); String result = results.getResult(); System.out.println("result is: " + result); } catch (RetriesExhaustedException ree) { System.err.println("retries exhausted.."); } catch (UnexpectedException ue) { System.err.println("we're out.."); } } static class ApiException extends Exception { } static class SomeApi { private int count = 1; String call() throws ApiException { System.out.println("API call #" + count); count++; if (count < 5) { System.out.println("throwing exception.."); throw new ApiException(); } return UUID.randomUUID().toString().toUpperCase(); } } }
Running the code above should produce a similar output here, failing 3 times but succeeding at last to get a result from the service:
API call #1 throwing exception.. API call #2 throwing exception.. API call #3 throwing exception.. API call #4 result is: 8E6CA51D-3AD2-4194-8732-4F84EEFC97EE
Tutorial Sources
Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/retry4j-samples.git
Guava-based Alternative: guava-retrying
There’s a tool based on Guava named guava-retrying offering similar functionality (thanks @joschi83 for mentioning!)
Please feel free to take a look at this project on GitHub: https://github.com/rholder/guava-retrying
Resources
Article Updates
- 2015-12-02: Reference to the guava-retrying project added.
Tags: boundary, reactive, resilient, retry, retry4j, timeout, tool
December 2nd, 2015 at 12:29 pm
retry4j looks awfully similar to https://github.com/rholder/guava-retrying (without the dependency to Google Guava, though).
December 2nd, 2015 at 12:59 pm
Hi Joschi,
thanks for your input, I’ve added a reference to the guava-retrying project in the article, didn’t know this one yet :)
Best regards,
Micha
December 5th, 2015 at 8:49 pm
The retry4j API was very much inspired by guava-retrying and the older project it forks, Retryer. However, I wanted to create something that did not rely on Guava (or any other libraries at all). Anyhow, thanks for checking it out.
To leave off I’ll do the same as joschi and point you at another good retry library (albeit more complex) in Spring Retry -> http://docs.spring.io/spring-batch/reference/html/retry.html
-Evan
December 6th, 2015 at 6:52 pm
Hi Evan,
thanks for your update! :)
April 15th, 2016 at 7:06 pm
This looks similar to Recurrent, which has a much simpler API and really nice callbacks https://github.com/jhalterman/recurrent
April 16th, 2016 at 9:20 pm
Hi Christphe,
I didn’t know Recurrent before but it definitely looks very nice, thanks for posting this update! :)
Cheers,
Micha