Maintainable error handling with Feign clients? Not a dream anymore

Over the last couple of years, I’ve been using Feign to invoke HTTP APIs, let it be external or internal. If you are not familiar with Feign, here’s a very brief intro. Feign is a declarative HTTP client. You define an interface, take some magical annotations and you have yourself a fully functioning client that you can use to communicate via HTTP.

Feign is a standalone library, anybody can use it on a project. It comes with its own annotations, types and configuration. Here’s an example client from the docs:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}

Having a tool to define APIs like this is a great way to reduce application complexity. Think about writing the same thing with Apache HttpComponents. Let me give you an idea:

CloseableHttpClient httpClient = HttpClients.createDefault();
try {
	String owner = ...
	String repo = ...
	HttpGet request = new HttpGet("https://api.github.com/repos/" 
					               + owner + "/" + repo +"/contributors");
	CloseableHttpResponse response = httpClient.execute(request);

	try {
		HttpEntity entity = response.getEntity();
		if (entity != null) {
			String result = EntityUtils.toString(entity);
			Gson gson = new Gson();
			List<Contributor> contributors = gson.fromJson(result, 
							                      new TypeToken<ArrayList<Contributor>>(){}.getType());
			...
		}
	} finally {
		response.close();
	}
} finally {
	httpClient.close();
}

I hope the difference is obvious. There’s tons of boilerplate code in the latter example.

Since Spring is one of the most used base frameworks in any Java project, there’s another level of abstraction that the Spring ecosystem provides. What if you don’t need to rely on the custom Feign annotations but you use the same Spring annotations just like when a controller is defined, e.g. @RequestMapping, @PathVariable and so on.

Well, Spring Cloud adds this capability to your application. The former example with Spring annotations looks the following:

@FeignClient("github")
interface GitHub {
  @RequestMapping(value = "/repos/{owner}/{repo}/contributors", method = GET)
  List<Contributor> contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo);

  @RequestMapping(value = "/repos/{owner}/{repo}/issues", method = POST)
  void createIssue(Issue issue, @PathVariable("owner") String owner, @PathVariable("repo") String repo);

}

Quite neat compared to the previous examples.

Although, there’s one downside to any abstraction. Or maybe downside is not even the best word to describe it, its rather a trade-off that we – engineers – often forget.

One of the points of an abstraction is to hide details from its users to ease development, which is absolutely spot on in case of Feign. However, the trade-off you are going to make is less control over that particular piece of code. For normal use-cases it’s often perfectly fine but as soon as you hit a wall and you need some specific behavior, you have to start digging. Where? I guess most of us just Google for some time, hoping that somebody has asked a similar question on Stackoverflow. Sometimes it’s just not the case, and you have to jump right into the code to figure out how to work-around the framework.

Error handling in Feign

Fortunately the framework creators have thought about having some way of reacting to errors during an API call. The ErrorDecoder interface is used for that purpose.

public interface ErrorDecoder {
  public Exception decode(String methodKey, Response response);
}

Such an interface implementation can be tied to creating a particular Feign client. In the standard Feign world, you can specify it during the Builder calls, like:

Feign.builder()
.decoder(new CustomDecoder())

So you can customize the Decoder you’d like to use on a per client basis, but not on a method basis. That’s just a limitation of the capabilities the library is providing.

How does this look in the Spring world? If you’d like to use an ErrorDecoder, you can just simply register it as a bean and the framework will automatically pick it up and assign it to every Feign client.

@Configuration
public class CustomFeignConfiguration {
    @Bean
    public CustomDecoder customDecoder() {
        return new CustomDecoder();
    }
}

Very neat, but since within an application there could be several Feign clients used, does it make sense to use a single decoder for all clients? Probably not. But even if you go one level deeper, you might need to have different error handling logic for different API calls within the same client.

The junior implementation

So if you were carefully reading, you might have noticed a parameter in the signature of the ErrorDecoder, methodKey. The methodKey is automatically generated by the Feign library whenever an error response is received from the downstream API. An example methodKey looks the following:

UserServiceClient#findById(UUID)

It starts with the Feign client class name, then a hash symbol and then the name of the method, followed by its parameter types within parentheses. The key generation can be found here: Feign#configKey

The first implementation one might think would be some kind of string magic on the methodKey:

@Override
    public Exception decode(String methodKey, Response response) {
        if (methodKey.startsWith("UserServiceClient")) {
           // dosomething specific to UserServiceClient
        } else if (...) {
           ...
        }
    }

Obviously this is going to work, but it won’t scale and as soon as you rename a class/method, you’ll end up in some functional problems in your application since the String renaming might be easily messed up (even though IDEs are very smart these days).

Testing is going to be hell with an implementation like this, cyclomatic complexity is going to increase with the number of clients and API methods. There must be a solution out there and somebody must have figured it out already.

Well, I thought the same, but so far I haven’t found anything on this.

The proper solution

First of all, this solution is aiming to address Spring only, when using the bare Feign client library, there are some minor tweaks required.

For a Spring Cloud based Feign client, you need to use the @FeignClient annotation on the interface like this:

@FeignClient(name = "user-service", url = "http://localhost:9002")
public interface UserServiceClient {
    @GetMapping("/users/{id}")
    UserResponse findById(@PathVariable("id") UUID id)
}

Pretty easy I’d say. So what happens when there’s an error, for example 404 returned by the API?

2020-09-29 20:31:29.510 ERROR 26412 --- [ctor-http-nio-2] a.w.r.e.AbstractErrorWebExceptionHandler : [c04278db-1]  500 Server Error for HTTP GET "/users/d6535843-effe-4eb7-b9ff-1689420921a3"

feign.FeignException$NotFound: [404] during [GET] to [http://localhost:9002/users/d6535843-effe-4eb7-b9ff-1689420921a3] [UserServiceClient#findById(UUID)]: []

As you can see, the original API I was calling resulted in a 500 Internal Server Error because the downstream user-service was responding with a 404. Not good.

So what can I do if I want to translate this 404 response into a UserNotFoundException within the service? And what if I’d like to do something else for another method within the same client?

Well, let’s create a generic ErrorDecoder that can defer the error handling to other classes, similarly how we do with a @ControllerAdvice and @ExceptionHandler class in the Sprint MVC world.

I’m going to use a custom annotation to mark methods or the client class which needs special treatment on its error handling:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandleFeignError {
    Class<? extends FeignHttpExceptionHandler> value();
}

FeignHttpExceptionHandler is a simple interface with a single method:

public interface FeignHttpExceptionHandler {
    Exception handle(Response response);
}

The usage is going to look the following:

@FeignClient(name = "user-service", url = "http://localhost:9002")
public interface UserServiceClient {
    @GetMapping("/users/{id}")
    @HandleFeignError(UserServiceClientExceptionHandler.class)
    UserResponse findById(@PathVariable("id") UUID id) throws UserNotFoundException;
}

The implementation for UserServiceClientExceptionHandler is very simple:

@Component
public class UserServiceClientExceptionHandler implements FeignHttpExceptionHandler {
    @Override
    public Exception handle(Response response) {
        HttpStatus httpStatus = HttpStatus.resolve(response.status());
        String body = FeignUtils.readBody(response.body());
        if (HttpStatus.NOT_FOUND.equals(httpStatus)) {
            return new UserNotFoundException(body);
        }
        return new RuntimeException(body);
    }
}

Of course you can make it more sophisticated, this is just an example.

So how does the annotation work? As I said, we are going to use a special ErrorDecoder. First of all, we have to understand the signature of the ErrorDecoder interface. Since there’s no information within the decoder which client’s which method was called, somehow we have to figure it out so we can invoke the corresponding error handler.

One way to do it is to utilize the methodKey parameter and build a map based on that with the error handlers. But before that, we need to somehow get a reference to all the Feign clients registered within the application:

@Component
@RequiredArgsConstructor
public class ExceptionHandlingFeignErrorDecoder implements ErrorDecoder {
    private final ApplicationContext applicationContext;

    private final Map<String, FeignHttpExceptionHandler> exceptionHandlerMap = new HashMap<>();

    @EventListener
    public void onApplicationEvent(ContextRefreshedEvent event) {
        Map<String, Object> feignClients = applicationContext.getBeansWithAnnotation(FeignClient.class);
        List<Method> clientMethods = feignClients.values().stream()
                .map(Object::getClass)
                .map(aClass -> aClass.getInterfaces()[0])
                .map(ReflectionUtils::getDeclaredMethods)
                .flatMap(Arrays::stream)
                .collect(Collectors.toList());
        for (Method m : clientMethods) {
            String configKey = Feign.configKey(m.getDeclaringClass(), m);
            HandleFeignError handlerAnnotation = getHandleFeignErrorAnnotation(m);
            if (handlerAnnotation != null) {
                FeignHttpExceptionHandler handler = applicationContext.getBean(handlerAnnotation.value());
                exceptionHandlerMap.put(configKey, handler);
            }
        }
    }

    private HandleFeignError getHandleFeignErrorAnnotation(Method m) {
        HandleFeignError result = m.getAnnotation(HandleFeignError.class);
        if (result == null) {
            result = m.getDeclaringClass().getAnnotation(HandleFeignError.class);
        }
        return result;
    }
}

First off, it’s loading all the Spring beans with the @FeignClient annotation. Since Feign is based on interfaces, there are JDK proxies involved, that’s why we need to call aClass.getInterfaces()[0] to get the actual interface with its methods.

Then the only trick is to calculate the methodKey which I’m doing using the Feign.configKey method call (that’s the one Feign is also using). And as well we’re searching for the HandleFeignError annotation on method and on class level in the same order.

So as soon as the Spring context is set up, we’ll have a map of methodKeys to actual exception handlers.

The second thing we need to do is to make sure this class implements the ErrorDecoder interface.

@Component
@RequiredArgsConstructor
public class ExceptionHandlingFeignErrorDecoder implements ErrorDecoder {
    private final ErrorDecoder.Default defaultDecoder = new Default();
    
    // rest is omitted for simplicity

    @Override
    public Exception decode(String methodKey, Response response) {
        FeignHttpExceptionHandler handler = exceptionHandlerMap.get(methodKey);
        if (handler != null) {
            return handler.handle(response);
        }
        return defaultDecoder.decode(methodKey, response);
    }
}

So the decode method is very simple. If the map contains the methodKey with its corresponding exception handler, we’ll use that for resolving the proper Exception, otherwise the solution is falling back to the Default ErrorDecoder.

Using the Feign client afterwards is quite easy:

            try {
                UserResponse userResponse = userServiceClient.findById(id);
                // do something with the UserResponse
            } catch (UserNotFoundException e) {
                // Oops, the user is not found in the system
                // let's do some error handling
            }

Conclusion

With this solution in place, you can easily define proper error handling on the level of Feign client methods with any custom logic/custom exceptions you want to use. It allows you to build a more maintainable and robust application.

As usual, if you are interested in more, follow me on Twitter for updates.

UPDATE: the code can be found on my GitHub here.

15 Replies to “Maintainable error handling with Feign clients? Not a dream anymore”

  1. Sabfir says:
    1. Arnold Galovics says:
  2. Vinod says:
  3. Sid says:
    1. Arnold Galovics says:
      1. Lucas G says:
        1. Arnold Galovics says:
          1. Agustín says:
      2. Sid says:
        1. Arnold Galovics says:
  4. Elena says:
  5. Aparna Mishra says:
  6. Igor says:

Leave a Reply to vinod Cancel reply

Your email address will not be published. Required fields are marked *