How implement a retry mechanism for restTemplate
Asked Answered
P

3

6

I've implemented a java method which call to external services via a Resttemplate. As well, i've implemented some additional business logic also inside that method. How can i implement a retry mechanism for these rest calls. Need to consider below points as well.

  1. I cannot add a retry for entire method.
  2. It's better to add retry for rest calls(via resttemplate).
  3. There should be a way disable retry options for unwanted rest calls.
Prebendary answered 22/10, 2019 at 17:37 Comment(3)
I think you can do something like this. Make a loop from 0 to 9 and make a call. If you get success then break a loop and go ahead with result else iterate through it. If it is not returning somthing in 10 calls then why should you call that method and waste your time.Enthusiast
Possible duplicate of Retry java RestTemplate HTTP request if host offlineEbner
Its not like a good approach. i've already implemented these methods. I need to do this by minimizing the modifications for current implementations. If we add this retry for resttemplate using common configuration, then i maybe not need modify the current implementationPrebendary
C
12

Spring provides a retry mechanism with @Retry annotations. You have to use the following dependency.

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.1.5.RELEASE</version>
</dependency>

Spring provides the following annotations.

Spring retry annotations

@EnableRetry – to enable spring retry in spring boot project

@Retryable – to indicate any method to be a candidate of retry

@Recover – to specify fallback method

I provide below the sample code.

@Configuration
@EnableRetry
@SpringBootApplication
public class MyApplication {
}

You can refer the complete example to know more about it.

Cultus answered 22/10, 2019 at 17:48 Comment(5)
This annotation should be added to the method level. For my scenario, i cannot do that.If we got an error from one rest call, then entire method will be executed again.Prebendary
If you want, you can split the methods into small cohesive functions/methods and then you can add the annotations.Cultus
There are few limitations for this annotation. Specially, it can't be added to same class methods and private methods as well.Prebendary
Yes, there are some limitations, but it abstracts away lot of things and make it easier to implement.Cultus
If you are working with Spring-boot you can just include the starter spring-boot-starter-batch to get that dependency with the version aligned to the spring-boot version.Gerlach
E
11

You may add retry mechanism inside HttpClient and use it for RestTemplate, somethng like this:

@Bean
public ClientHttpRequestFactory clientFactory() {
    HttpClient httpClient = HttpClients.custom()            
        .setRetryHandler((exception, executionCount, context) -> {
            if (executionCount > 3) {
                log.warn("Maximum retries {} reached", 3);
                return false;
            }
            if (<some condition for retry>) {
                log.warn("Retry {}", executionCount);
                return true;
            }
            return false;
        })
        .build();

    return new HttpComponentsClientHttpRequestFactory(httpClient);
}
@Bean
public RestTemplate customRestTemplate(@Qualifier("clientFactory") ClientHttpRequestFactory clientFactory){ 
    return new RestTemplate(clientFactory);
}
Evora answered 17/5, 2020 at 21:49 Comment(2)
Just note that this solution only works for low level network failures. It does not retry on http status != 2xx, like for example a 502.Stack
@AnnevanderBom Did you find a solution that works with custom HTTP statuses?Erlineerlinna
K
0

for rest api call you can implement the retry mechanism on the client level where actual rest call going

 @Retryable(value = Exception.class, maxAttemptsExpression = "${retry.maxAttempts}", backoff = @Backoff(delayExpression = "${retry.maxDelay}"))
    public Optional<T> getApiCall(String url, String token, Class<T> resClass) {
        ResponseEntity<T> response = null;
        try {
            logger.info(url);
            // create headers
            HttpHeaders headers = new HttpHeaders();
            // set `accept` header
            headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
            headers.setBearerAuth(token);
            // set custom header
            // headers.set("x-request-src", "desktop");
            // build the request
            HttpEntity<String> entity = new HttpEntity<>("", headers);
            // use exchange method for HTTP call
            try {
                response = this.restTemplate.exchange(url, HttpMethod.GET, entity, resClass, 1);
            } catch (HttpStatusCodeException e) {
                return errorService.throwException(HttpStatus.NOT_FOUND, EKDError.LIFPRO406);
            }
            if (response.getStatusCode() == HttpStatus.OK) {
                return Optional.ofNullable(response.getBody());
            } else {
                return errorService.throwException(HttpStatus.NOT_FOUND, EKDError.LIFPRO406);
            }
        } catch (HttpClientErrorException | HttpServerErrorException e) {
            logger.error("Exception in api call : ", e);
            return errorService.throwException(HttpStatus.INTERNAL_SERVER_ERROR, EKDError.LIFPRO407);
        }
    }

now max attempts and delay is configurable set application.properties maxAttemptsExpression = "${retry.maxAttempts}", backoff = @Backoff(delayExpression = "${retry.maxDelay}"

Krasnodar answered 14/4, 2022 at 12:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.