I am doing multiple api calls parallely using completableFuture. I am Using RetryTemplate to invoke the Api when the api calls fail with 500 then it should retry. if multiple api calls fail I want the retry to happen for all the calls which got failed. I can see only one api call is retrying where as others calls are not getting retried
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class ApiCaller {
@Autowired
private RetryTemplate retryTemplate;
// Method to call your API using RetryTemplate
public String callApiWithRetry(String apiUrl) {
return retryTemplate.execute(context -> {
System.out.println("Attempt: " + (context.getRetryCount() + 1));
return makeApiCall(apiUrl);
});
}
// Simulated API call method
private String makeApiCall(String apiUrl) {
// Simulate an API call (replace with actual implementation)
// For demonstration purposes, throw an exception to simulate a failure
throw new RuntimeException("Simulated API failure for " + apiUrl);
}
// Method to execute API calls asynchronously
public void executeApiCalls() {
CompletableFuture<String> call1 = CompletableFuture.supplyAsync(() -> callApiWithRetry("http://api.example.com/endpoint1"));
CompletableFuture<String> call2 = CompletableFuture.supplyAsync(() -> callApiWithRetry("http://api.example.com/endpoint2"));
// Combine results
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(call1, call2);
// Handle results after both calls complete
combinedFuture.thenRun(() -> {
try {
String result1 = call1.get();
String result2 = call2.get();
System.out.println("Result from call 1: " + result1);
System.out.println("Result from call 2: " + result2);
} catch (Exception e) {
e.printStackTrace();
}
});
// Wait for completion
combinedFuture.join();
}
}
In the above snippet, both call1 and call2 are failing, but I can see only one of the api call is retrying. I am expecting both calls to be retried.
Retry Template config:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import
org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.HttpServerErrorException;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RetryConfig {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
// Retry only for 500 errors (HttpServerErrorException)
Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
retryableExceptions.put(HttpServerErrorException.class, true); // Retry on 500 errors
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3, retryableExceptions); // 3 retries for 500 error
retryTemplate.setRetryPolicy(retryPolicy);
// Exponential backoff policy (Optional)
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000); // Initial interval in milliseconds
backOffPolicy.setMultiplier(2.0); // Backoff multiplier
backOffPolicy.setMaxInterval(10000); // Maximum backoff interval
retryTemplate.setBackOffPolicy(backOffPolicy);
return retryTemplate;
}
}
2
Your RetryTemplate
is specifically configured to retry on HttpServerErrorException
(which typically corresponds to HTTP 500 errors), you need to throw HttpServerErrorException
when simulating API failures and not RuntimeException
which is not part of the retryable exceptions as defined in your RetryTemplate
configuration.
Updated code:
private String makeApiCall(String apiUrl) {
// Simulate an API call failure with 500 status code
throw new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR, "Simulated API failure for " + apiUrl);
}
2
The problem here is that CompletableFuture
does not handle exceptions properly for parallel tasks. When one API call fails, it affects the overall execution, and retries aren’t properly triggered for both calls.
You need to add exception handling for each CompletableFuture
using .exceptionally()
to make sure retries are handled independently.
Here is an example of how you might change your calls:
CompletableFuture<String> call1 = CompletableFuture.supplyAsync(() -> {
try {
return callApiWithRetry("http://api.example.com/endpoint1");
} catch (Exception e) {
throw new RuntimeException("Call 1 failed after retries", e);
}
}).exceptionally(e -> {
System.err.println("Error in call 1: " + e.getMessage());
return "Failed call 1"; // Fallback or handle failure
});
Apply similar exception handling to your other CompletableFuture
calls, in this example call2
, to guarantee independent retries and proper failure handling.
The rest of your code looks fine.