[Java Tip] Retrying operation with Guava Retrying

API calls failing, Network calls failing are normal part of our programs and we handle them in Application specific ways. Many times we need to retry the operations before logging it as failure. Guava retrying is a very nice library that provides a variety of ways of retrying, without writing custom code.

Lets look at the scenario we shall be using for example

Scenario

For demonstration we have a Remote Server from where we need to fetch the data. It could be streaming the data or a REST API or any other RPC call.

Lets look at the our class

static class UrlFetcher implements Callable<Boolean> {
  private String url;
  // To emulate failure
  private String opMode;

  public UrlFetcher(String url, String opMode) {
    this.url = url;
    this.opMode = opMode;
  }

  @Override
  public Boolean call() throws Exception {
    System.out.println("Trying to get data from "+url);
    // fetch URL content
    // store somewhere
    if("fail".equals(opMode)) {
      throw new TimeoutException("Connection timed out");
    }
    System.out.println("URL Content fetched");
    return true;
  }
}

The class is take a URL and mode as an argument. Mode is just to emulate failure here. We are doing anything here with URL for simplicity, but in real life you can do whatever you want to.

Now let's see how we use the retrying mechanism.

 Retryer<Boolean> retrier = RetryerBuilder.<Boolean>newBuilder()
            .retryIfExceptionOfType(TimeoutException.class)
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.stopAfterAttempt(3))
            .build();

    try {
      retrier.call(new UrlFetcher("http://www.google.com", "normal"));
      retrier.call(new UrlFetcher("http://www.doesnotexist.com", "fail"));
    } catch (RetryException e) {
      e.printStackTrace();
    } catch (ExecutionException e) {
      e.printStackTrace();
    }

We need to create a Retryer using the builder. We can specify the conditions on which to retry. Here we have specified TimeoutException or any other RuntimeException. If there is any other exception it won'r retry. We also need to specify strategy to stop. In the example we have specified, retry 3 times and then abort.

Let's see how this execution looks like

Trying to get data from http://www.google.comat time Tue Mar 03 12:24:49 IST 2015
URL Content fetched
Trying to get data from http://www.doesnotexist.comat time Tue Mar 03 12:24:49 IST 2015
Trying to get data from http://www.doesnotexist.comat time Tue Mar 03 12:24:49 IST 2015
Trying to get data from http://www.doesnotexist.comat time Tue Mar 03 12:24:49 IST 2015
com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 3 attempts.
	at com.github.rholder.retry.Retryer.call(Retryer.java:120)
	at com.ashishpaliwal.javatips.Retrier.main(Retrier.java:53)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.util.concurrent.TimeoutException: Connection timed out
	at com.ashishpaliwal.javatips.Retrier$UrlFetcher.call(Retrier.java:36)
	at com.ashishpaliwal.javatips.Retrier$UrlFetcher.call(Retrier.java:18)
	at com.github.rholder.retry.AttemptTimeLimiters$NoAttemptTimeLimit.call(AttemptTimeLimiters.java:78)
	at com.github.rholder.retry.Retryer.call(Retryer.java:110)
	... 6 more

Wait Strategies

Here we retried immediately after failure, what if we want to change the behaviour. To change the retry behaviour, we need to specify WaitStrategy while building the retryer. The code below uses exponential wait.

Retryer<Boolean> retrier = RetryerBuilder.<Boolean>newBuilder()
            .retryIfExceptionOfType(TimeoutException.class)
            .retryIfRuntimeException()
            .withWaitStrategy(WaitStrategies.exponentialWait(1000, 5, TimeUnit.SECONDS))
            .withStopStrategy(StopStrategies.stopAfterAttempt(3))
            .build();

There are a lot of other options on customising the retry behaviour, explore and share the feedback.

2 thoughts on “[Java Tip] Retrying operation with Guava Retrying

    • Nope it’s not async, but we can wrap the complete flow in async call. Like you submit a URL for fetching in async mode, this code can be made part of it which would make the flow async to the application

Leave a Reply to paliwalashish Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.