Mastering Guava‑Retrying: Build Robust Retry Logic in Java

This article explains why retry strategies are essential for unreliable external services, introduces the flexible Guava‑Retrying library, provides detailed code examples, describes its core execution flow, shows how to add the Maven dependency, and outlines the key interfaces and strategies for configuring retries.

Programmer DD
Programmer DD
Programmer DD
Mastering Guava‑Retrying: Build Robust Retry Logic in Java

Usage Scenario

In everyday development we often need to call external services and APIs, which can be unreliable, especially under poor network conditions where timeouts and exceptions occur. A retry strategy is required to re‑invoke the API, and such strategies are also used for service health checks.

Guava‑Retrying is a flexible retry component that offers many strategies and is easy to extend.

This is a small extension to Google’s Guava library to allow for the creation of configurable retrying strategies for an arbitrary function call, such as something that talks to a remote service with flaky uptime.

Using Guava‑Retrying you can customize retries, monitor each attempt, and benefit from Guava‑style fluent configuration.

Code Example

Below is a simple usage of Guava‑Retrying:

If an IOException is thrown, or the result is null or equals 2, retry; wait 300 ms between attempts, up to 3 attempts.

Callable<Integer> task = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        return 2;
    }
};

Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
        .retryIfResult(Predicates.<Integer>isNull())
        .retryIfResult(Predicates.equalTo(2))
        .retryIfExceptionOfType(IOException.class)
        .withStopStrategy(StopStrategies.stopAfterAttempt(3))
        .withWaitStrategy(WaitStrategies.fixedWait(300, TimeUnit.MILLISECONDS))
        .build();
try {
    retryer.call(task);
} catch (ExecutionException e) {
    e.printStackTrace();
} catch (RetryException e) {
    e.printStackTrace();
}

If any exception occurs, retry; each task is limited to 3 s, initial wait 3 s, maximum total retry time 1 min, with the wait increasing by 1 s per attempt; each failure is logged.

@Override
public Integer call() throws Exception {
    return 2;
}
};
Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
        .retryIfException()
        .withStopStrategy(StopStrategies.stopAfterDelay(30, TimeUnit.SECONDS))
        .withWaitStrategy(WaitStrategies.incrementingWait(3, TimeUnit.SECONDS, 1, TimeUnit.SECONDS))
        .withAttemptTimeLimiter(AttemptTimeLimiters.<Integer>fixedTimeLimit(3, TimeUnit.SECONDS))
        .withRetryListener(new RetryListener() {
            @Override
            public <V> void onRetry(Attempt<V> attempt) {
                if (attempt.hasException()) {
                    attempt.getExceptionCause().printStackTrace();
                }
            }
        })
        .build();
try {
    retryer.call(task);
} catch (ExecutionException e) {
    e.printStackTrace();
} catch (RetryException e) {
    e.printStackTrace();
}

Core Execution Logic

long startTime = System.nanoTime();
for (int attemptNumber = 1; ; attemptNumber++) {
    Attempt<V> attempt;
    try {
        // execution succeeded
        V result = attemptTimeLimiter.call(callable);
        attempt = new ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
    } catch (Throwable t) {
        // execution failed
        attempt = new ExceptionAttempt<V>(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
    }
    // listener processing
    for (RetryListener listener : listeners) {
        listener.onRetry(attempt);
    }
    // check rejection predicate
    if (!rejectionPredicate.apply(attempt)) {
        return attempt.get();
    }
    // check stop strategy
    if (stopStrategy.shouldStop(attempt)) {
        throw new RetryException(attemptNumber, attempt);
    } else {
        // compute next sleep time
        long sleepTime = waitStrategy.computeSleepTime(attempt);
        try {
            blockStrategy.block(sleepTime);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RetryException(attemptNumber, attempt);
        }
    }
}

Dependency Inclusion

<dependency>
    <groupId>com.github.rholder</groupId>
    <artifactId>guava-retrying</artifactId>
    <version>2.0.0</version>
</dependency>

The default Guava library also contains a retrying module.

Main Interfaces Overview

Attempt: a single execution of a task.

AttemptTimeLimiter: limits the execution time of a single task.

BlockStrategies: defines how to block between attempts (default is Thread.sleep).

RetryException: exception thrown when retries are exhausted.

RetryListener: custom listener for logging or asynchronous handling.

StopStrategy: defines when to stop retrying; includes StopAfterDelayStrategy, NeverStopStrategy, StopAfterAttemptStrategy.

WaitStrategy: defines the wait time between attempts; includes FixedWaitStrategy, RandomWaitStrategy, IncrementingWaitStrategy, ExponentialWaitStrategy, FibonacciWaitStrategy, ExceptionWaitStrategy, CompositeWaitStrategy.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentRetryGuavaRetryStrategy
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.