5

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.
Dilshan Niroda
  • 51
  • 1
  • 1
  • 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. – Laxminarayan Oct 22 '19 at 17:42
  • Possible duplicate of [Retry java RestTemplate HTTP request if host offline](https://stackoverflow.com/questions/32352484/retry-java-resttemplate-http-request-if-host-offline) – isank-a Oct 22 '19 at 17:46
  • 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 implementation – Dilshan Niroda Oct 22 '19 at 17:49

3 Answers3

11

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.

catch23
  • 17,519
  • 42
  • 144
  • 217
Sambit
  • 7,625
  • 7
  • 34
  • 65
  • 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. – Dilshan Niroda Oct 22 '19 at 17:54
  • 2
    If you want, you can split the methods into small cohesive functions/methods and then you can add the annotations. – Sambit Oct 22 '19 at 18:16
  • There are few limitations for this annotation. Specially, it can't be added to same class methods and private methods as well. – Dilshan Niroda Oct 23 '19 at 06:36
  • 2
    Yes, there are some limitations, but it abstracts away lot of things and make it easier to implement. – Sambit Oct 23 '19 at 07:13
  • 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. – Philippe Simo Feb 10 '22 at 09:42
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);
}
β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
A.K.
  • 111
  • 1
  • 3
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}"

Basharat Ali
  • 1,099
  • 9
  • 11