The 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.
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 GitHub repository, fork it there or clone it using Git:
git clone https://github.com/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
Article Updates
-
2015-12-02: Reference to the guava-retrying project added.