Reactive Programming: Handling Service Timeouts and Retries with Retry4j

December 2nd, 2015 by

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.

Running Retry4j in Eclipse IDE

Running Retry4j in Eclipse IDE

 

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: , , , , , ,

6 Responses to “Reactive Programming: Handling Service Timeouts and Retries with Retry4j”

  1. joschi Says:

    retry4j looks awfully similar to https://github.com/rholder/guava-retrying (without the dependency to Google Guava, though).

  2. Micha Kops Says:

    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

  3. Evan Says:

    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

  4. Micha Kops Says:

    Hi Evan,

    thanks for your update! :)

  5. Christphe Says:

    This looks similar to Recurrent, which has a much simpler API and really nice callbacks https://github.com/jhalterman/recurrent

  6. Micha Kops Says:

    Hi Christphe,

    I didn’t know Recurrent before but it definitely looks very nice, thanks for posting this update! :)

    Cheers,

    Micha

Search
Tags
Categories