8 Effective Retry Strategies for Java APIs

This article presents eight practical retry mechanisms for Java applications—including loop, recursion, HttpClient configuration, Spring Retry, Resilience4j, custom utilities, asynchronous thread‑pool retries, and message‑queue based retries—along with code examples, configuration snippets, and best‑practice guidelines to help developers handle transient network failures reliably.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
8 Effective Retry Strategies for Java APIs

Retry Mechanism Implementation

Retry illustration
Retry illustration

1. Loop Retry

This is the simplest approach: wrap the request in a for loop and retry until success or the maximum number of attempts is reached, adding a delay with Thread.sleep() after each failure.

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

2. Recursive Retry

Define a method that calls itself when an exception occurs, decreasing the remaining retry count each time.

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

3. HttpClient Built‑in Retry

Many HTTP clients provide built‑in retry handlers. For Apache HttpClient you can configure a HttpRequestRetryHandler (4.5+) or RetryStrategy (5.x) 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

Add the spring-retry dependency and use either RetryTemplate programmatically or the @Retryable annotation to declaratively retry a method.

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

retryTemplate.execute(context -> {
    // request code
    return null;
});

Annotation example:

@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public void request() {
    // request code
}

5. Resilience4j

Resilience4j offers a lightweight retry module that can be used via code or annotations.

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

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

Annotation example:

@Service
public class MyService {
    @Retryable(value = MyException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000))
    public void doSomething() {
        // request code
    }
}

6. Custom Retry Utility

Define an abstract Callback with a doProcess() method returning a RetryResult. Use a RetryExecutor to loop until the callback signals success.

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 Thread‑Pool Retry

Submit the request as a Callable to a

ThreadPoolExecutor** and retry the task while monitoring the <code>Future

result.

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++;
        Thread.sleep(1000);
    }
}

8. Message‑Queue Retry (RocketMQ)

When a request fails, re‑publish the payload to a RocketMQ topic so that a consumer can retry later, ensuring reliability even if the service is temporarily down.

@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 Cautions

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

Consider idempotency; avoid duplicate writes when the downstream service is not idempotent.

Handle concurrency to prevent duplicate requests, using locks or distributed locks if needed.

Classify exceptions: retry only transient errors (e.g., timeouts, connection issues) and treat others (e.g., database errors) specially.

Avoid infinite loops; enforce a maximum number of attempts to protect system stability.

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.

Javaconcurrencyspringbest practicesRetryHttpClientresilience4j
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.