Spring Retry with RetryTemplate in Spring Boot, Java8

9.6k Views Asked by At

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:

  1. 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.

  2. 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?

1

There are 1 best solutions below

4
On

simpleRetryPolicy.setMaxAttempts(2);

Means 2 attempts total, not 2 retries.