Mastering API Retry Strategies: 8 Proven Methods for Reliable Backend Calls

This article explores eight practical retry mechanisms for handling unreliable third‑party APIs in backend systems, covering simple loops, recursion, built‑in client retries, Spring Retry, Resilience4j, custom utilities, asynchronous thread‑pool retries, and message‑queue based approaches, plus best‑practice tips.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Mastering API Retry Strategies: 8 Proven Methods for Reliable Backend Calls

In cross‑border services, third‑party servers are distributed worldwide, so network issues when calling external APIs are common; a retry mechanism becomes essential.

Retry Mechanism Implementation

1. Loop Retry

This is the simplest method: wrap the request in a loop and retry until success or the maximum attempts are reached.

Example code:

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

The loop uses a for construct with a maximum of three attempts, and Thread.sleep() adds a delay to avoid rapid retries.

2. Recursive Retry

Recursion can also be used: the method calls itself on failure until success or the retry limit.

Example code:

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

3. Built‑in Client Retry

Many HTTP clients have built‑in retry support. For Apache HttpClient:

Version 4.5+:

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

Version 5.x:

HttpClients.custom().setRetryStrategy(...)
CloseableHttpClient httpClient = HttpClients.custom()<br/>    .setRetryStrategy(new DefaultHttpRequestRetryStrategy(3, NEG_ONE_SECOND))<br/>    .build();<br/>

Custom retry strategies can be implemented by providing HttpRequestRetryHandler (4.5+) or RetryStrategy (5.x).

4. Spring Retry Library

Spring Retry offers annotations and utilities for retrying methods.

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

Explicit RetryTemplate

Create a RetryTemplate and configure policies.

RetryTemplate retryTemplate = new RetryTemplate();<br/><br/>// configure retry policy<br/>RetryPolicy retryPolicy = new SimpleRetryPolicy(3);<br/>retryTemplate.setRetryPolicy(retryPolicy);<br/><br/>// configure back‑off policy<br/>FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();<br/>backOffPolicy.setBackOffPeriod(1000);<br/>retryTemplate.setBackOffPolicy(backOffPolicy);<br/>

Execute the method via retryTemplate.execute(...).

retryTemplate.execute((RetryCallback<Void, Exception>) context -> {<br/>    // request code<br/>    return null;<br/>});<br/>

Alternatively, use @Retryable on methods:

@Retryable(value = Exception.class, maxAttempts = 3)<br/>public void request() {<br/>    // request code<br/>}<br/>

Annotation‑Based Retry

Enable retry with @EnableRetry and annotate methods with @Retryable, optionally configuring back‑off with @Backoff.

@Configuration<br/>@EnableRetry<br/>public class RetryConfig { }<br/><br/>@Service<br/>public class MyService {<br/>    @Retryable(value = MyException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000))<br/>    public void doSomething() {<br/>        // request logic<br/>    }<br/>}<br/>

5. Resilience4j

Resilience4j provides lightweight retry, circuit‑breaker, and rate‑limiter features.

<dependency><br/>    <groupId>io.github.resilience4j</groupId><br/>    <artifactId>resilience4j-spring-boot2</artifactId><br/>    <version>1.7.0</version><br/></dependency><br/>

Programmatic Use

Create a RetryRegistry.

RetryRegistry retryRegistry = RetryRegistry.ofDefaults();<br/>

Configure a Retry instance.

RetryConfig config = RetryConfig.custom()<br/>    .maxAttempts(3)<br/>    .waitDuration(Duration.ofMillis(1000))<br/>    .retryOnResult(response -> response.getStatus() == 500)<br/>    .retryOnException(e -> e instanceof WebServiceException)<br/>    .retryExceptions(IOException.class, TimeoutException.class)<br/>    .ignoreExceptions(BusinessException.class, OtherBusinessException.class)<br/>    .failAfterMaxAttempts(true)<br/>    .build();<br/><br/>Retry retry = retryRegistry.retry("name", config);<br/>

Execute code with Retry.decorateCheckedSupplier.

CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(retry, () -> {<br/>    // request code<br/>    return "result";<br/>});<br/>

Annotation Use

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

6. Custom Retry Utility

A lightweight custom retry framework can be built with a Callback abstract class, a RetryResult holder, and a RetryExecutor that loops until success or the retry limit.

public abstract class Callback {<br/>    public abstract RetryResult doProcess();<br/>}<br/><br/>public class RetryResult {<br/>    private Boolean isRetry;<br/>    private Object obj;<br/>    // constructors, getters omitted<br/>    public static RetryResult ofResult(Boolean isRetry, Object obj) {<br/>        return new RetryResult(isRetry, obj);<br/>    }<br/>    public static RetryResult ofResult(Boolean isRetry) {<br/>        return new RetryResult(isRetry, null);<br/>    }<br/>}<br/><br/>public class RetryExecutor {<br/>    public static Object execute(int retryCount, Callback callback) {<br/>        for(int cur = 0; cur < retryCount; cur++) {<br/>            RetryResult result = callback.doProcess();<br/>            if(result.isRetry()) continue;<br/>            return result.getObj();<br/>        }<br/>        return null;<br/>    }<br/>}<br/>

Usage example:

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

7. Asynchronous Thread‑Pool Retry

Use a ThreadPoolExecutor to submit request tasks and retry asynchronously.

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

8. Message‑Queue Retry

Persist failed requests to a message queue (e.g., RocketMQ) to ensure reliability.

@Component<br/>@RocketMQMessageListener(topic = "myTopic", consumerGroup = "myConsumerGroup")<br/>public class MyConsumer implements RocketMQListener<String> {<br/>    @Override<br/>    public void onMessage(String message) {<br/>        try {<br/>            // request code<br/>        } catch(Exception e) {<br/>            DefaultMQProducer producer = new DefaultMQProducer("myProducerGroup");<br/>            producer.setNamesrvAddr("127.0.0.1:9876");<br/>            try {<br/>                producer.start();<br/>                Message msg = new Message("myTopic", "myTag", message.getBytes());<br/>                producer.send(msg);<br/>            } catch(Exception ex) {<br/>                // handle send error<br/>            } finally {<br/>                producer.shutdown();<br/>            }<br/>        }<br/>    }<br/>}<br/>

Best Practices and Precautions

Set reasonable retry counts and intervals to avoid overwhelming the system.

Consider idempotency; avoid duplicate writes on non‑idempotent APIs.

Handle concurrency with locks or distributed locks to prevent duplicate requests.

Differentiate exception types: retry on transient network errors, handle permanent errors separately.

Avoid infinite loops; enforce maximum attempts 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.

AsynchronousMessage Queue
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.