Search code examples
springspring-bootspring-cloudspring-cloud-feignspring-retry

Feign client and Spring retry


I have a restful service calling an external service using Spring Cloud Feign client

@FeignClient(name = "external-service", configuration = FeignClientConfig.class)
public interface ServiceClient {

    @RequestMapping(value = "/test/payments", method = RequestMethod.POST)
    public void addPayment(@Valid @RequestBody AddPaymentRequest addPaymentRequest);

    @RequestMapping(value = "/test/payments/{paymentId}", method = RequestMethod.PUT)
    public ChangePaymentStatusResponse updatePaymentStatus(@PathVariable("paymentId") String paymentId,
            @Valid @RequestBody PaymentStatusUpdateRequest paymentStatusUpdateRequest);

}

I noticed the following failure 3-4 times in the last 3 months in my log file:

json.ERROR_RESPONSE_BODY:Connection refused executing POST http://external-service/external/payments json.message:Send Payment Add Payment Failure For other reason: {ERROR_RESPONSE_BODY=Connection refused executing POST http://external-service/external/payments, EVENT=ADD_PAYMENT_FAILURE, TRANSACTION_ID=XXXXXXX} {} json.EVENT:ADD_PAYMENT_FAILURE json.stack_trace:feign.RetryableException: Connection refused executing POST http://external-service/external/payments at feign.FeignException.errorExecuting(FeignException.java:67) at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:104) at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)

Is it possible to add Spring Retry on a Feign client. What I wanted to annotate the addPayment operation with

@Retryable(value = {feign.RetryableException.class }, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier=2))

But this is not possible, what other options do I have?


Solution

  • You can add a Retryer in the FeignClientConfig

    @Configuration
    public class FeignClientConfig {
    
        @Bean
        public Retryer retryer() {
            return new Custom();
        }
    
    }
    
    class Custom implements Retryer {
    
        private final int maxAttempts;
        private final long backoff;
        int attempt;
    
        public Custom() {
            this(2000, 3);
        }
    
        public Custom(long backoff, int maxAttempts) {
            this.backoff = backoff;
            this.maxAttempts = maxAttempts;
            this.attempt = 1;
        }
    
        public void continueOrPropagate(RetryableException e) {
            if (attempt++ >= maxAttempts) {
                throw e;
            }
    
            try {
                Thread.sleep(backoff);
            } catch (InterruptedException ignored) {
                Thread.currentThread().interrupt();
            }
        }
    
        @Override
        public Retryer clone() {
            return new Custom(backoff, maxAttempts);
        }
    }
    

    Updated with sample Retryer example config based on the Retryer.Default.