8 Interface Retry Mechanisms – Which One Should You Choose?
This article compares eight ways to implement retry logic for remote API calls—including simple loops, recursion, Apache HttpClient settings, Spring Retry, Resilience4j, a custom utility, thread‑pool asynchronous retries, and message‑queue based retries—while outlining best‑practice guidelines such as idempotency, retry limits, and concurrency control.
When calling third‑party services that may be distributed worldwide, network failures are common, so adding a retry mechanism is essential. The article presents eight concrete implementations, each with code examples and explanations.
1. Loop Retry
The simplest approach uses a for loop that repeats the request until it succeeds or a maximum number of attempts is reached, inserting a Thread.sleep() delay between attempts.
int retryTimes = 3;
for (int i = 0; i < retryTimes; i++) {
try {
// request code
break;
} catch (Exception e) {
// handle exception
Thread.sleep(1000); // 1‑second delay
}
}2. Recursive Retry
A recursive method calls itself after a failure, decrementing the remaining retry count. The method returns early when the count reaches zero.
public void requestWithRetry(int retryTimes) {
if (retryTimes <= 0) return;
try {
// request code
} catch (Exception e) {
Thread.sleep(1000);
requestWithRetry(retryTimes - 1);
}
}3. Built‑in HttpClient Retry
Apache HttpClient provides built‑in retry handlers. For version 4.5+, use HttpClients.custom().setRetryHandler(); for 5.x, use setRetryStrategy(). The handlers can be configured with a maximum of three attempts.
CloseableHttpClient httpClient = HttpClients.custom()
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
.build(); CloseableHttpClient httpClient = HttpClients.custom()
.setRetryStrategy(new DefaultHttpRequestRetryStrategy(3, NEG_ONE_SECOND))
.build();4. Spring Retry Library
Spring Retry offers a RetryTemplate for programmatic retries and the @Retryable annotation for declarative retries. Configuration includes a SimpleRetryPolicy (max attempts) and a FixedBackOffPolicy (fixed delay).
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1</version>
</dependency>Programmatic example:
RetryTemplate retryTemplate = new RetryTemplate();
RetryPolicy retryPolicy = new SimpleRetryPolicy(3);
retryTemplate.setRetryPolicy(retryPolicy);
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(1000);
retryTemplate.setBackOffPolicy(backOffPolicy);Declarative example:
@Retryable(value = Exception.class, maxAttempts = 3)
public void request() {
// request code
}5. Resilience4j
Resilience4j supplies a lightweight retry module. A RetryRegistry creates a Retry instance with custom configuration such as max attempts, wait duration, result‑based and exception‑based predicates, and ignored exceptions.
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);Using the retry:
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(
retry,
() -> {
// retryable code
return "result";
}
);6. Custom Retry Utility
A lightweight custom utility can be built with an abstract Callback class, a RetryResult wrapper, and a RetryExecutor that loops until the callback signals success or the maximum count is reached.
public abstract class Callback {
public abstract RetryResult doProcess();
} public class RetryResult {
private Boolean isRetry;
private Object obj;
public static RetryResult ofResult(Boolean isRetry, Object obj) { return new RetryResult(isRetry, obj); }
public static RetryResult ofResult(Boolean isRetry) { return new RetryResult(isRetry, null); }
} 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;
}
}7. Asynchronous Retry with ThreadPoolExecutor
For low‑latency scenarios, a thread pool can submit a Callable task repeatedly until it succeeds, applying a delay between attempts.
int maxRetryTimes = 3;
int currentRetryTimes = 0;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
Callable<String> task = () -> {
// request code
return "result";
};
while (currentRetryTimes < maxRetryTimes) {
try {
Future<String> future = executor.submit(task);
String result = future.get();
break;
} catch (Exception e) {
currentRetryTimes++;
Thread.sleep(1000);
}
}8. Message‑Queue Based Retry
Using a message queue (e.g., RocketMQ) ensures retry tasks survive service interruptions. Failed requests are re‑published to a topic for later processing.
@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();
}
}
}
}Best Practices and Considerations
Set reasonable retry counts and intervals to avoid overwhelming the target service.
Ensure the operation is idempotent; otherwise, repeated writes may cause inconsistencies.
Handle concurrency carefully—use locks or distributed locks when multiple threads may retry the same request.
Classify exceptions: network‑related errors are usually retryable, while business or database errors may require different handling.
Avoid infinite loops; enforce a hard limit on attempts and consider circuit‑breaker patterns for persistent failures.
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.
LouZai
10 years of front‑line experience at leading firms (Xiaomi, Baidu, Meituan) in development, architecture, and management; discusses technology and life.
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.
