Mastering API Retry Strategies: 8 Proven Java Implementations

This article explains why retry mechanisms are essential for cross‑border services, then walks through eight practical Java approaches—including simple loops, recursion, Apache HttpClient settings, Spring Retry, Resilience4j, custom utilities, thread‑pool async retries, and message‑queue based retries—plus best‑practice tips.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Mastering API Retry Strategies: 8 Proven Java Implementations

Retry Mechanism Implementation

In cross‑border scenarios third‑party servers are distributed worldwide, so network failures are common when calling external APIs. Adding a retry mechanism helps improve reliability.

1. Loop Retry

The simplest method is to wrap the request in a for loop and retry until success or a maximum count is reached.

int retryTimes = 3;
for (int i = 0; i < retryTimes; i++) {
    try {
        // request API code
        break;
    } catch (Exception e) {
        // handle exception
        Thread.sleep(1000); // delay 1 second before retry
    }
}

2. Recursive Retry

Recursion can achieve the same effect by calling the method itself on failure.

public void requestWithRetry(int retryTimes) {
    if (retryTimes <= 0) return;
    try {
        // request API code
    } catch (Exception e) {
        Thread.sleep(1000);
        requestWithRetry(retryTimes - 1);
    }
}

3. Built‑in HttpClient Retry

Apache HttpClient provides built‑in retry handlers.

CloseableHttpClient httpClient = HttpClients.custom()
    .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
    .build();

For version 5.x use .setRetryStrategy(...) with DefaultHttpRequestRetryStrategy.

4. Spring Retry Library

Add the dependency:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.3.1</version>
</dependency>

Configure a RetryTemplate with a SimpleRetryPolicy (max attempts) and a FixedBackOffPolicy (delay), then execute the request:

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 API code
    return null;
});

Or use the annotation‑driven approach:

@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000))
public void request() {
    // request API code
}

5. Resilience4j

Resilience4j offers a lightweight retry module.

RetryRegistry retryRegistry = RetryRegistry.ofDefaults();
RetryConfig config = RetryConfig.custom()
    .maxAttempts(3)
    .waitDuration(Duration.ofMillis(1000))
    .retryOnResult(response -> response.getStatus() == 500)
    .retryOnException(e -> e instanceof WebServiceException)
    .build();
Retry retry = retryRegistry.retry("name", config);

CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(
    retry, () -> "result");

Resilience4j also supports the @Retryable annotation in Spring Boot.

6. Custom Retry Utility

Define an abstract Callback with a doProcess() method returning a RetryResult. RetryExecutor.execute() loops until the callback signals no further retry.

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); }
    // getters omitted
}

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;
    }
}

Usage example:

int maxRetryCount = 3;
Object result = RetryExecutor.execute(maxRetryCount, new Callback() {
    @Override
    public RetryResult doProcess() {
        // request logic
        // return RetryResult.ofResult(true) to retry
        // return RetryResult.ofResult(false, result) on success
    }
});

7. Asynchronous Retry with ThreadPoolExecutor

int maxRetryTimes = 3;
int currentRetryTimes = 0;
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>());
Callable<String> task = () -> {
    // request API code
    return "result";
};
Future<String> future;
while (currentRetryTimes < maxRetryTimes) {
    try {
        future = executor.submit(task);
        String result = future.get();
        break;
    } catch (Exception e) {
        currentRetryTimes++;
        Thread.sleep(1000);
    }
}

8. Message‑Queue Retry (RocketMQ)

@Component
@RocketMQMessageListener(topic = "myTopic", consumerGroup = "myConsumerGroup")
public class MyConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String message) {
        try {
            // request API 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 Precautions

Set reasonable retry counts and intervals to avoid overwhelming the downstream service.

Ensure the API is idempotent; otherwise, repeated writes may cause data inconsistency.

Handle concurrency properly—use locks or distributed locks when multiple threads may retry the same request.

Classify exceptions: network timeouts are usually retry‑able, while database or file‑system errors may need different handling.

Avoid infinite loops; always enforce a maximum retry limit to prevent resource exhaustion.

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.

Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

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.