How to Customize Feign’s Retry Mechanism

Ruhshan Ahmed Abir
The Startup
Published in
4 min readNov 30, 2020

--

Image Credit: Quotefancy.com

In micro-service architecture it is common that one micro-service communicates with another by consuming REST APIs. In the spring ecosystem a popular REST client is Feign because of its declarative style and DRY approach for adding different configuration.

In this post I will talk about enabling retry mechanism for feign Client. By instinct what we can do is update our business code where we wrote the statement for api call inside a try catch and while loop and write code for another API call until our condition is met. That might serve our purpose but it will make our code ugly and under-implemented.

In a perfect world, everything works perfectly and we don’t have to retry any http requests. Thus retry is not enabled in feign out of the box. Unfortunately this never happens, for a tcp packet there are a millions ways to die in the web. So to enable retry, you have to put the following bean in your client configuration.

@Bean
public Retryer retryer() {
return new Retryer.Default();
}

You can pass some parameters like interval, maximum retry attempt inside Default(), otherwise it will retry for 5 times with 1 second interval.

This will make feign to retry in case of IOException only. That kind of make sense right? X should retry to reach Y only when Y is unreachable. But this doesn’t happen all the time. May be communication between Y and Z is broken thus Y returned some 5xx, and you want to retry in that case. To employ that you hove to raise RetryableException . And the place for this is an implementation of ErrorDecoder class. It looks like this:

public class MyErrorDecoder implements ErrorDecoder {

private final ErrorDecoder defaultErrorDecoder = new Default();

@Override
public Exception decode(String s, Response response) {
Exception exception = defaultErrorDecoder.decode(s, response);

if(exception instanceof RetryableException){
return exception;
}


if(response.status() == 504){
return new RetryableException("504 error", response.request().httpMethod(), null );
}

return exception;
}
}

To bring this class into play you have to put following configuration in your application properties:

feign.client.config.default.error-decoder=com.example.somepackage.MyErrorDecoder

Now, as things are arranged at their places let’s get to know what MyErrorDecoder is actually doing. It’s clear that it implements ErrorDecoder class and overrides its decode method. Inside the decode method on first conditional block we are checking if the raised exception is already a retryable exception or not. If it is, than it is surely an exception raised by feign itself and will retry by itself if we just return this exception.

If the exception is not a retryable exception, then the second conditional block will execute. In this block we are checking if the response’s status is 504 or not. If it is then returning a new instance of retryable exception.

You can do many things on this errorDecoder class. Think about a scenario, where you want to retry in case of any 5xx error, whether it is practical or not. Then what you will do? Write if else block for all server error codes? Not you don’t have to instead you can:

if (HttpStatus.valueOf(response.status()).is5xxServerError()) {
return new RetryableException("Server error", response.request().httpMethod(), null);
}

There is also an way to customize the retryer. Why would you do that? In my case I wanted to put logs on each retry attempt. To customize the retryer first remove the retryer bean from configuration. Then create a component like this:

@Slf4j
@Component
@NoArgsConstructor
public class CustomRetryer implements Retryer {

private int retryMaxAttempt;

private long retryInterval;

private int attempt = 1;


public CustomRetryer(int retryMaxAttempt, Long retryInterval) {
this.retryMaxAttempt = retryMaxAttempt;
this.retryInterval = retryInterval;
}

@Override
public void continueOrPropagate(RetryableException e) {
log.info("Feign retry attempt {} due to {} ", attempt, e.getMessage());

if(attempt++ == retryMaxAttempt){
throw e;
}
try {
Thread.sleep(retryInterval);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}

}

@Override
public Retryer clone() {
return new CustomRetryer(6, 2000L);
}
}

Here our CustomRetryer overrides the continueOrPropagate and clone method of feign’s default Retryer. In the clone method we create an instance of CustomRetryer with required parameters, in this case 6 is maximum number of attempts and 2000L is the interval between each retry.

Inside continueOrPropagate method you can customize retry mechanism. Remember that, to stop retrying and propagate the error, you have to throw the retryable exception this method receives. Otherwise, it will continue to retry. In our case we are throwing the exception after the value if attempt variable exceeds the maximum number we set, otherwise it waits for the interval we set, before continuing to another attempt.

Till now what we’ve seen is to how to create a custom error decoder and retryer to extend feign’s reliability according to our need. If you create error decoder and retryers in this fashion, it will work for any number of feign clients you add in your project. But, think about the case where you want to have different retry mechanism for different clients, or may be not retry at all for some clients. What will you do? Its really easy to bind each clients to a separate retryer and error decoder. Just configure this in properties like this:

feign.client.config.default.<your_client_name>.error-decoder=com.example.somepackage.MyErrorDecoderfeign.client.config.client1.retryer=com.example.somepackage.CustomRetryer

Happy retrying !!

If you like this write-up, consider becoming a member of medium. It’s only 5$/month for unlimited access to all the articles published here. It will also support my writing career.

--

--

Ruhshan Ahmed Abir
The Startup

Started with poetry, ended up with codes. Have a university degree on Biotechnology. Works and talks about Java, Python, JS. www.buymeacoffee.com/ruhshan