Mastering API Retry Strategies in Java: 8 Proven Techniques
This article presents eight practical ways to implement retry mechanisms for third‑party API calls in Java, covering simple loops, recursion, built‑in HttpClient handlers, Spring Retry, Resilience4j, custom utilities, asynchronous thread‑pool retries, and message‑queue based retries, plus best‑practice guidelines.
Retry Mechanism Implementation
1. Loop Retry
This is the simplest approach: wrap the request code in a loop and retry until success or the maximum number of attempts is reached.
<code>int retryTimes = 3;
for (int i = 0; i < retryTimes; i++) {
try {
// request code
break;
} catch (Exception e) {
// handle exception
Thread.sleep(1000); // delay 1 second before retry
}
}
</code>The example uses a for loop with a maximum of three attempts and a Thread.sleep() delay to avoid flooding the service.
2. Recursive Retry
A recursive method can call itself when a request fails, decreasing the remaining retry count each time.
<code>public void requestWithRetry(int retryTimes) {
if (retryTimes <= 0) return;
try {
// request code
} catch (Exception e) {
Thread.sleep(1000);
requestWithRetry(retryTimes - 1);
}
}
</code>The method requestWithRetry receives the maximum retry count; when the count reaches zero the recursion stops.
3. Built‑in HttpClient Retry
Apache HttpClient provides built‑in retry handlers.
HttpClient 4.5+: HttpClients.custom().setRetryHandler(...)
<code>CloseableHttpClient httpClient = HttpClients.custom()
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
.build();
</code>HttpClient 5.x: HttpClients.custom().setRetryStrategy(...)
<code>CloseableHttpClient httpClient = HttpClients.custom()
.setRetryStrategy(new DefaultHttpRequestRetryStrategy(3, NEG_ONE_SECOND))
.build();
</code>Both examples configure a maximum of three retries; the client automatically retries on failure.
4. Spring Retry Library
Add the dependency:
<code><dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1</version>
</dependency>
</code>Two usage styles are available.
Explicit RetryTemplate
<code>RetryTemplate retryTemplate = new RetryTemplate();
RetryPolicy retryPolicy = new SimpleRetryPolicy(3);
retryTemplate.setRetryPolicy(retryPolicy);
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(1000);
retryTemplate.setBackOffPolicy(backOffPolicy);
retryTemplate.execute(context -> {
// request code
return null;
});
</code>Annotation‑Driven Retry
<code>@Retryable(value = Exception.class, maxAttempts = 3)
public void request() {
// request code
}
</code>Enable retry with @EnableRetry on a configuration class.
5. Resilience4j Library
Add the Maven dependency:
<code><dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.0</version>
</dependency>
</code>Programmatic Use
<code>RetryRegistry retryRegistry = RetryRegistry.ofDefaults();
RetryConfig config = RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.ofMillis(1000))
.retryOnResult(response -> response.getStatus() == 500)
.retryOnException(e -> e instanceof WebServiceException)
.retryExceptions(IOException.class, TimeoutException.class)
.ignoreExceptions(BusinessException.class, OtherBusinessException.class)
.failAfterMaxAttempts(true)
.build();
Retry retry = retryRegistry.retry("name", config);
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(
retry, () -> "result");
</code>Annotation Use
<code>@Service
public class MyService {
@Retryable(value = {MyException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public void doSomething() {
// request code
}
}
</code>6. Custom Retry Utility
Define an abstract Callback with a doProcess() method returning a RetryResult :
<code>public abstract class Callback {
public abstract RetryResult doProcess();
}
</code>RetryResult holds a boolean isRetry and an obj result:
<code>public class RetryResult {
private Boolean isRetry;
private Object obj;
// constructors, getters omitted
public static RetryResult ofResult(Boolean isRetry, Object obj) {
return new RetryResult(isRetry, obj);
}
public static RetryResult ofResult(Boolean isRetry) {
return new RetryResult(isRetry, null);
}
}
</code>RetryExecutor runs the callback up to the configured count:
<code>public class RetryExecutor {
public static Object execute(int retryCount, Callback callback) {
for (int cur = 0; cur < retryCount; cur++) {
RetryResult result = callback.doProcess();
if (result.isRetry()) {
continue;
}
return result.getObj();
}
return null;
}
}
</code>Usage example:
<code>int maxRetryCount = 3;
Object result = RetryExecutor.execute(maxRetryCount, new Callback() {
@Override
public RetryResult doProcess() {
// retry logic
// return RetryResult.ofResult(true) to retry
// return RetryResult.ofResult(false, actualResult) to finish
}
});
</code>7. Asynchronous Retry with ThreadPoolExecutor
<code>int maxRetryTimes = 3;
int currentRetryTimes = 0;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
Callable<String> task = () -> {
// request code
return "result";
};
Future<String> future;
while (currentRetryTimes < maxRetryTimes) {
try {
future = executor.submit(task);
String result = future.get();
break;
} catch (Exception e) {
currentRetryTimes++;
try { Thread.sleep(1000); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); }
}
}
</code>8. Message‑Queue Retry (RocketMQ)
<code>@Component
@RocketMQMessageListener(topic = "myTopic", consumerGroup = "myConsumerGroup")
public class MyConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
try {
// request code
} catch (Exception e) {
DefaultMQProducer producer = new DefaultMQProducer("myProducerGroup");
producer.setNamesrvAddr("127.0.0.1:9876");
try {
producer.start();
Message msg = new Message("myTopic", "myTag", message.getBytes());
producer.send(msg);
} catch (Exception ex) {
// handle send failure
} finally {
producer.shutdown();
}
}
}
}
</code>Best Practices and Cautions
Set reasonable retry counts and intervals to avoid overwhelming the service.
Consider idempotency; avoid duplicate writes on non‑idempotent APIs.
Handle concurrency to prevent duplicate or out‑of‑order requests (e.g., locks or distributed locks).
Classify exceptions: retry only for transient issues like timeouts, not for permanent failures such as database errors.
Avoid infinite loops; enforce a maximum retry limit.
References
[1] 《Java业务开发常见错误100例》
[2] https://juejin.cn/post/7028947828248412168#heading-9
[3] https://resilience4j.readme.io/docs/retry
Sanyou's Java Diary
Passionate about technology, though not great at solving problems; eager to share, never tire of learning!
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.