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.
Hi, I have used your solution and found out that it works only when your custom exception you want to throw from the handler implements ExceptionNotWrappedByHystrix
Hey Sabfir. Thats true. I intentionally didn’t complicate things with Hystrix. In that case you have to use Hystrix hooks.
Hi,
solved a big problem with simple code.
It would be better if you can post the complete code git link to understand the complete code flow. I feel one thing missing here is the @HandleFeignError part.
@HandleFeignError(UserServiceClientExceptionHandler.class)
Where is this annotation defined how this part is working is unclear. Rest neatly explined.
sorry it was my bad
Hey there.
I’m getting a BeanCurrentlyInCreationException on line `Map feignClients = applicationContext.getBeansWithAnnotation(FeignClient.class);`
Looks like my `CustomErrorDecoder`’s instantiation is being called from my `CustomFeignClient` construction causing a circular reference.
Did anything change with `FeignClient` since May?
Hi Sid. I’m sorry, I just noticed your comment. I checked the current version of Feign with the latest available Spring version. Indeed, there was a change. I’m getting the same exception as well. feignClients =
I’ll post an edit to the original article to fix it, but before I do that, here you go:
– In the ErrorDecoder, instead of using the InitializingBean interface to subscribe to the bean loading event, use the refresh application context event
…
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
Map
applicationContext.getBeansWithAnnotation(FeignClient.class);
…
This should fix the issue.
Let me know how it goes.
I still have the same exception even though I’m using your latest update and what you are suggesting.
Could you enlight us please?
Hi Lucas. Sorry to hear that. The example works fine, probably you’re missing some piece of code somewhere.
Do you mind posting a question to stackoverflow with the related code you have? I’m happy to check it and help you.
Hi! I have the same problem. It would be useful if you upload the code to a git repo.
Hi Agustin.
Sure, here you go: https://github.com/galovics/mastering-feign-practice/tree/spring-feign/online-store-service/src/main/java/com/arnoldgalovics/online/store/service/external/error
Let me know if you’re still struggling. I’m happy to assist.
Thanks, Arnold. I’m gonna give it a try and let you know. I was on paternity leave so I missed your reply.
Hi Sid,
It’s okay and congratz. :))
Let me know how it goes, I’m more than happy to support.
Arnold
Hi thank you for your article.
Actually there is a way in Spring to specify a different error decoder for each FeignClient.
The key is to avoid the “@Configuration” annotation on the Feign configuration class.
See here for more detail:
https://stackoverflow.com/questions/56352215/how-implement-error-decoder-for-multiple-feign-clients
In our use cases having a single error decoding per external service is ok (we don’t need a different error handling per single operation). If you could add a note to your article I think this info could be useful to others too.
Thank you
Hi Arnold,
Thank you for this article. This was a great help. One thing i did not understand was where are we overriding the default decoder for feign client. It only worked for me after i specified it explicitly in yaml.
feign:
client:
config:
default:
errorDecoder: com.abc.test.exceptionHandlers.HandlingFeignErrorDecoder
Unfortunately, the issue with circular dependency is still present in updated code with Spring 5.x.
The line
`Map feignClients = applicationContext.getBeansWithAnnotation(FeignClient.class);`
gives the error
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘xxx.XXXClient’: Requested bean is currently in creation: Is there an unresolvable circular reference?