Retrying Feign Calls – 重试佯装呼叫

最后修改: 2022年 2月 1日

1. Introduction


Calling external services through the REST endpoint is a common activity that was made very straightforward by libraries like Feign. However, a lot of things can go wrong during such calls. Many of these problems are random or temporary.


In this tutorial, we’ll learn how to retry failed calls and make more resilient REST clients.


2. Feign Client Setup


First, let’s create a simple Feign client builder that we’ll later enhance with retrying features. We’ll use OkHttpClient as the HTTP client. Also, we’ll use GsonEncoder and GsonDecoder for encoding and decoding the requests and the responses. Finally, we’ll need to specify the target’s URI and response type:


public class ResilientFeignClientBuilder {
    public static <T> T createClient(Class<T> type, String uri) {
        return Feign.builder()
          .client(new OkHttpClient())
          .encoder(new GsonEncoder())
          .decoder(new GsonDecoder())
          .target(type, uri);

Alternatively, if we use Spring, we could let it auto-wire Feign client with available beans.


3. Feign Retryer


Fortunately, retrying abilities are baked in Feign, and they just need to be configured. We can do that by providing an implementation of the Retryer interface to the client builder.

幸运的是,重试的能力已经在 Feign 中得到了体现,它们只需要被配置。我们可以通过为客户端构建器提供一个Retryer接口的实现来实现这一目标。

Its most important method, continueOrPropagate, accepts RetryableException as an argument and returns nothing. Upon execution, it either throws an exception or exits successfully (usually after sleeping). If it doesn’t throw an exception, Feign will continue to retry the call. If the exception is thrown, it’ll be propagated and will effectively finish the call with an error.


3.1. Naive Implementation


Let’s write a very simple implementation of Retryer that will always retry calls after waiting one second:


public class NaiveRetryer implements feign.Retryer {
    public void continueOrPropagate(RetryableException e) {
        try {
        } catch (InterruptedException ex) {
            throw e;

Because Retryer implements the Cloneable interface, we also needed to override the clone method.


public Retryer clone() {
    return new NaiveRetryer();

Finally, we need to add our implementation to client builder:


public static <T> T createClient(Class<T> type, String uri) {
    return Feign.builder()
      // ...
      .retryer(new NaiveRetryer())    
      // ...

Alternatively, if we are using Spring, we could annotate NaiveRetryer with @Component annotation or define a bean in the configuration class and let Spring do the rest of the work:


public Retryer retryer() {
    return new NaiveRetryer();

3.2. Default Implementation


Feign provides a sensible default implementation of the Retryer interface. It’ll retry only a given number of times, will start with some time interval, and then increase it with each retry up to provided maximum. Let’s define it with starting interval of 100 milliseconds, the maximum interval of 3 seconds, and the maximum number of attempts of 5:


public static <T> T createClient(Class<T> type, String uri) {
    return Feign.builder()
// ...
      .retryer(new Retryer.Default(100L, TimeUnit.SECONDS.toMillis(3L), 5))    
// ...

3.3. No Retrying


If we don’t want Feign to ever retry any calls, we can provide Retryer.NEVER_RETRY implementation to the client builder. It’ll simply propagate the exception every time.


4. Creating Retryable Exceptions


In the previous section, we learned to control how often we retry calls. Now let’s see how to control when we want to retry the call and when we want to simply throw the exception.


4.1. ErrorDecoder and RetryableException


When we receive an erroneous response, Feign passes it to an instance of the ErrorDecoder interface that decides what to do with it. Most importantly, the decoder can map an exception to an instance of RetryableException, enabling Retryer to retry the call. The default implementation of ErrorDecoder only creates a RetryableExeception instance when the response contains the “Retry-After” header. Most commonly, we can find it in 503 Service Unavailable responses.

当我们收到一个错误的响应时,Feign将其传递给ErrorDecoder接口的一个实例,该实例决定如何处理它。最重要的是,解码器可以将异常映射到RetryableException的实例,使Retryer可以重试调用。ErrorDecoder的默认实现仅在响应包含 “Retry-After “头时创建RetryableExeception实例。最常见的是,我们可以在503 Service Unavailable响应中找到它。

That’s good default behavior, but sometimes we need to be more flexible. For example, we could be communicating with an external service that, from time to time, randomly responds with 500 Internal Server Error, and we have no power to fix it. What we can do is to retry the call because we know that it’ll probably work next time. To achieve that, we’ll need to write a custom ErrorDecoder implementation.


4.2. Creating Custom Error Decoder


There is only one method that we need to implement in our custom decoder: decode. It accepts two arguments, a String method key, and a Response object. It returns an exception, should it be an instance of RetryableException or some other exception that depends on the implementation.


Our decode method will simply check if the response’s status code is higher or equal to 500. If that’s the case, it’ll create RetryableException. If not, it will return basic FeignException created with the errorStatus factory function from the FeignException class:


public class Custom5xxErrorDecoder implements ErrorDecoder {
    public Exception decode(String methodKey, Response response) {
        FeignException exception = feign.FeignException.errorStatus(methodKey, response);
        int status = response.status();
        if (status >= 500) {
            return new RetryableException(
        return exception;

Mind that in this case, we create and return the exception, not throw it.


Finally, we need to plug our decoder in the client builder:


public static <T> T createClient(Class<T> type, String uri) {
    return Feign.builder()
      // ...
      .errorDecoder(new Custom5xxErrorDecoder())
      // ...

5. Summary


In this article, we learned how to control the retry logic of the Feign library. We looked into the Retryer interface and how it can be used to manipulate the time and number of retry attempts. Then we created our ErrorDecoder to control which responses warrant retry.


As always, all code examples can be found over on GitHub.