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

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.