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.

LouZai
LouZai
LouZai
8 Interface Retry Mechanisms – Which One Should You Choose?

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.

Retry Mechanisms Illustration
Retry Mechanisms Illustration
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.

JavaConcurrencyretryMessage Queuehttp clientspring retryresilience4j
LouZai
Written by

LouZai

10 years of front‑line experience at leading firms (Xiaomi, Baidu, Meituan) in development, architecture, and management; discusses technology and life.

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.