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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
