try {
reponseType = retryTemplate.execute((RetryCallback<X, RetryException>) context -> {
try {
log.error("Calling api attempt #" + context.getRetryCount());
HttpEntity<x> xResponse = httpRestTemplate.exchange(requestUrl, HttpMethod.POST, entity, x.class);
return xResponse.getBody();
} catch (HttpStatusCodeException e) {
if (e.getStatusCode().is5xxServerError()) {
throw new RetryException("api returned Server Error", e);
}
return null;
}
});
} catch (RetryException e) {
throw e;
Defined retryTemplate policy in configuration file and httpRestTemplate is normal template
#Bean
RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(30000);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
retryTemplate.setRetryPolicy(new CustomRetryPolicy(3));
return retryTemplate;
}
But not able to call retryTemplate again after time given.
Can anyone help me, i think me getting confused in exception game.
This will only retry if e.getStatusCode().is5xxServerError(). Otherwise you are returning null, which is "success" from the retry template's perspective. The template will only retry when an exception is thrown.
You can classify which exceptions are retryable in the retry policy.
Related
So I'm trying to retry for specific exceptions and created a bean which has shouldRetry(Throwable t) function. The function returns true if exception has to be retried, otherwise false.
But What I'm observing is shouldRetry(Throwable t) is executing twice(log is printing twice) for one retry attempt, however serviceImpl from where exception is being thrown is executing only once for one retry attempt.
Could someone please let me know if I'm doing something wrong here, or is it the default behavior/bug with spring retry itself.
#Component("dbRecoverableExceptionHandler")
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
#Slf4j
public class DBRecoverableExceptionHandler {
private final Environment environment;
private final MultiTaggedCounter exceptionRetryCounter;
public Boolean isRetryable(Throwable t) {
String[] recoverableExceptionClasses = environment
.getRequiredProperty("db-recoverable-exception-classes", String[].class);
for (String s1 : recoverableExceptionClasses) {
if (t.getClass().getSimpleName().contains(s1)) {
exceptionRetryCounter.increment(1, s1);
log.warn("Retrying for exception " + t.toString());
return true;
}
}
return false;
}
}
#Retryable(exceptionExpression = "#{#dbRecoverableExceptionHandler.isRetryable(#root)}",
maxAttemptsExpression = "#{${max-attempts}}",
backoff = #Backoff(delayExpression = "#{${retry-backoff-delay-time}}",
multiplierExpression = "#{${retry-backoff-multiplier}}"))
It is as expected.
The method will be called by the RetryTemplate twice for each execution...
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Retry: count=" + context.getRetryCount());
}
// Reset the last exception, so if we are successful
// the close interceptors will not think we failed...
lastException = null;
return retryCallback.doWithRetry(context);
}
catch (Throwable e) {
lastException = e;
try {
registerThrowable(retryPolicy, state, context, e);
}
catch (Exception ex) {
throw new TerminatedRetryException("Could not register throwable",
ex);
}
finally {
doOnErrorInterceptors(retryCallback, context, e);
}
if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
...
The first call to canRetry() (in the while loop) is skipped on the very first call since there is no exception yet, on subsequent iterations, when the method throws an exception, it is called twice.
I'm struggling with understanding #Retryable. What I need is to retry 3 times when I get 5xx Exception and if retry also fails then throw a custom exception in the recovery method. And if some other exception is thrown then catch it and throw a custom exception.
#Retryable(value = HttpServerErrorException.class, maxAttempts = 3, backoff = #Backoff(delay = 3000))
public String callToService(String key) {
String response;
try {
response = //assume a service call here
}catch (Exception ex) {
throw new customException("some message");
}
return response;
}
#Recover
public void retryFailed(HttpServerErrorException httpServerErrorException) {
throw new customException("some message");
}
In your case as you have added:
#Retryable(value = HttpServerErrorException.class, maxAttempts = 3, backoff = #Backoff(delay = 3000))
The #Retryable is used with:
value = HttpServerErrorException.class, so your method will be retried only if HttpServerErrorException is occure/thrown from your method code, and Note: if any other exception thrown retry will not be done, and recover method will also not be invoked, as recover method is only invoked with exception mentioned in value in #Retryable.
maxAttempts = 3, so it will retry executing your method 3 times by maximum
backoff = #Backoff(delay = 3000), so it will keep a delay of 3000ms in between retry.
And after retrying 3 times, if your method still not working, your method with #Recover will be envoked with the HttpServerErrorException
I hope it make sense and help yo understand the concept of #Retryable
Now to implement what you want you need to implement it as below:
#Retryable(value = HttpServerErrorException.class, maxAttempts = 3, backoff = #Backoff(delay = 3000))
public String callToService(String key) {
String response;
try {
response = //assume a service call here
} catch (HttpServerErrorException httpServerErrorException) {
throw httpServerErrorException;
} catch (Exception ex) {
throw new CustomException("some message");
}
return response;
}
#Recover
public void retryFailed(HttpServerErrorException httpServerErrorException) {
//do whatever you want here, when HttpServerErrorException occured more than 3 times
}
I am using Spring Boot 2.1.14.RELEASE, Java8, Spring Boot.
I have a client from which I have to access another rest service.
I need to retry an Http404 and HTTP500 2 times whereas not retry any other exceptions.
I am using RestTemplate to invoke the rest service like this:
restTemplate.postForEntity(restUrl, requestEntity, String.class);
I looked into using Retryable as well as RetryTemplate and implemented the retry functionality using RetryTemplate.
I have implemented this in 2 ways:
OPTION1:
The RetryTemplate bean is:
#Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(retryProperties.getDelayForCall());
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
retryTemplate.setRetryPolicy(exceptionClassifierRetryPolicy);
return retryTemplate;
}
ClassifierRetryPolicy is:
#Component
public class ExceptionClassifierRetryPolicy1 extends ExceptionClassifierRetryPolicy {
#Inject
private RetryProperties retryProperties;
public ExceptionClassifierRetryPolicy1(){
final SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(2);
this.setExceptionClassifier(new Classifier<Throwable, RetryPolicy>() {
#Override
public RetryPolicy classify(Throwable classifiable) {
if (classifiable instanceof HttpServerErrorException) {
// For specifically 500
if (((HttpServerErrorException) classifiable).getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
return simpleRetryPolicy;
}
return new NeverRetryPolicy();
}
else if (classifiable instanceof HttpClientErrorException) {
// For specifically 404
if (((HttpClientErrorException) classifiable).getStatusCode() == HttpStatus.NOT_FOUND) {
return simpleRetryPolicy;
}
return new NeverRetryPolicy();
}
return new NeverRetryPolicy();
}
});
}
}
In my client class, I am using retryTemplate like this:
public void postToRestService(...,...){
...
retryTemplate.execute(context -> {
logger.info("Processing request...");
responseEntity[0] = restTemplate.postForEntity(restURL, requestEntity, String.class);
return null;
}, context -> recoveryCallback(context));
...
}
The rest service being invoked is throwing HTTP404 on every request.
My expectation is: The client should submit one request, receive HTTP404, and perform 2 retries. So a total of 3 requests submitted to rest service before invoking recovery callback method.
My observation is: The client is submitting 2 requests to rest service.
Above observation makes sense from what I have read about RetryTemplate.
So the questions are:
Is the above implementation of retryTemplate correct? If not, how to implement and invoke it? Another option that I tried implementing (but didn't get any far) was using a RetryListenerSupport on the client method and invoking the retryTemplate inside the onError method.
Are we supposed to bump up the retry count by 1 to achieve what is desired? I have tried this and it gets me what I need but the RetryTemplate isn't created with this purpose in mind.
OPTION2: Code implementing option mentioned in #1 above:
Client method:
#Retryable(listeners = "RestClientListener")
public void postToRestService(...,...){
...
responseEntity[0] = restTemplate.postForEntity(restURL, requestEntity, String.class);
...
}
Listener:
public class RestClientListener extends RetryListenerSupport {
private static final Logger logger = LoggerFactory.getLogger(RestClientListener.class);
#Inject
RestTemplate restTemplate;
#Inject
RetryTemplate retryTemplate;
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
logger.info("Retrying count for RestClientListener "+context.getRetryCount());
...
final ResponseEntity<String>[] responseEntity = new ResponseEntity[]{null};
if( context.getLastThrowable().getCause() != null &&
(context.getLastThrowable().getCause() instanceof RestClientResponseException &&
((RestClientResponseException) context.getLastThrowable().getCause()).getRawStatusCode() == HttpStatus.NOT_FOUND.value()))
{
logger.info("Retrying now: ", context.getLastThrowable().toString());
retryTemplate.execute(context2 -> {
logger.info("Processing request...: {}", context2);
responseEntity[0] = restTemplate.postForEntity(restURL, requestEntity, String.class);
return responseEntity;
}, context2 -> recoveryCallback(context2));
}
else {
// Only retry for the above if condition
context.setExhaustedOnly();
}
}
}
The problem with this approach is that I cannot find a way to share objects between my client and clientListener classes. These objects are required in order to create requestEntity and header objects. How can this be achieved?
simpleRetryPolicy.setMaxAttempts(2);
Means 2 attempts total, not 2 retries.
I am using RetryTemplate of spring boot i am trying hit third party URL if not able to connect then i want to retry.it is working in local machine as expected but if i deployed my application on UAT and tried for retry it working but it retries more then mentioned maxattempts and at the end it giving 504 bad_gateway exception without any response on swagger?
any one help me in this
retryTemplate.execute(arg0 -> {
log.error("Sending Company Request to SIRA.");
final String xmlResponse = submitPayload(request, ResponseTimer, Client);
final ScreeningResponse screeningResponseFromXml = resultMapper
.getScreeningResponseFromXml(soapRequestXml, xmlResponse, screeningProvider, sourceDataId);
if (!screeningResponseFromXml.getErrors().isEmpty()
&& Arrays.asList(SiraRetryableFaultCode.values()).toString()
.contains(screeningResponseFromXml.getErrors().stream().findFirst().get().getCode())) {
throw new FraudFaultCodeException(screeningResponseFromXml.getErrors().stream().findFirst().get().getCode(),
sourceDataId, screeningResponseFromXml.getErrors().stream().findFirst().get().getMessage());
}
return screeningResponseFromXml;
});
========my retry config file====
#Configuration
public class RetryAppConfig {
#Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000L);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
ExceptionClassifierRetryPolicy exRetryPolicy = new ExceptionClassifierRetryPolicy();
exRetryPolicy.setPolicyMap(new HashMap<Class<? extends Throwable>, RetryPolicy>() {
{
put(RetryableException.class, new SimpleRetryPolicy(5));
put(FraudFaultCodeException.class, new SimpleRetryPolicy(5));
}
});
retryTemplate.setRetryPolicy(exRetryPolicy);
return retryTemplate;
}
}
Is it possible to set RetryPolicy in spring retry (https://github.com/spring-projects/spring-retry) based on error status code? e.g. I want to retry on HttpServerErrorException with HttpStatus.INTERNAL_SERVER_ERROR status code, which is 503. Therefore it should ignore all other error codes -- [500 - 502] and [504 - 511].
The RestTemplate has setErrorHandler option and DefaultResponseErrorHandler is the default one.
Its code looks like:
public void handleError(ClientHttpResponse response) throws IOException {
HttpStatus statusCode = getHttpStatusCode(response);
switch (statusCode.series()) {
case CLIENT_ERROR:
throw new HttpClientErrorException(statusCode, response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
case SERVER_ERROR:
throw new HttpServerErrorException(statusCode, response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
default:
throw new RestClientException("Unknown status code [" + statusCode + "]");
}
}
So, you can provide your own implementation for that method to simplify your RetryPolicy around desired status codes.
For others who are facing same problem, I'm posting this answer.
Implement custom retry policy as follows:
class InternalServerExceptionClassifierRetryPolicy extends ExceptionClassifierRetryPolicy {
public InternalServerExceptionClassifierRetryPolicy() {
final SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(3);
this.setExceptionClassifier(new Classifier<Throwable, RetryPolicy>() {
#Override
public RetryPolicy classify(Throwable classifiable) {
if (classifiable instanceof HttpServerErrorException) {
// For specifically 500 and 504
if (((HttpServerErrorException) classifiable).getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR
|| ((HttpServerErrorException) classifiable)
.getStatusCode() == HttpStatus.GATEWAY_TIMEOUT) {
return simpleRetryPolicy;
}
return new NeverRetryPolicy();
}
return new NeverRetryPolicy();
}
});
}}
Ans the simply call it as below:
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(new InternalServerExceptionClassifierRetryPolicy())
You can also add the specific error code in the retryableExceptions list of the SinmpleRetryPolicy.
Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
retryableExceptions.put(HttpClientErrorException.Unauthorized.class, true);
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(5, retryableExceptions));