Propagating Service Exceptions Through Feign Without Triggering Hystrix Circuit Breaker
This article explains how to configure Feign and Hystrix so that HTTP error responses from a downstream service are wrapped in a HystrixBadRequestException, allowing the calling service to receive the original status code without activating Hystrix's circuit‑breaker logic.
Scenario and Pain Points
Feign is used to implement a RESTful architecture.
When an exception occurs, the error message cannot display the original service exception.
No callback is defined for handling exceptions.
The business system calls a base service via Feign; the base service throws various HTTP‑status‑code exceptions.
These exceptions must not trigger Hystrix’s circuit‑breaker.
Problem Analysis and Solution Idea
The author discovered that HystrixBadRequestException does not trigger Hystrix’s circuit‑breaker, but most articles do not show how to use it in practice. By examining the Hystrix source code, the author aimed to understand its design and find a way to propagate the original exception.
Main Classes Overview
interface UserRemoteCall– Feign client interface annotated with @FeignClient, which references a custom FeignConfiguration. class FeignConfiguration – Defines a custom configuration for the Feign client; it should not be scanned as a global configuration. class UserErrorDecoder implements ErrorDecoder – Handles response status codes outside the 2xx range and converts them into exceptions.
Source Code Analysis
Feign’s default configuration resides in
org.springframework.cloud.netflix.feign.FeignClientsConfiguration. If no custom Feign.Builder is provided, Spring uses HystrixFeign.Builder, which makes Feign calls subject to Hystrix control.
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}In Hystrix, HystrixBadRequestException is treated specially: it bypasses the circuit‑breaker and is re‑thrown directly.
if (t instanceof HystrixBadRequestException) {
return handleBadRequestByEmittingError(e);
}Solution
Disable Hystrix entirely (not recommended for production) by setting feign.hystrix.enabled=false.
Prefer a custom Feign configuration that provides a regular Feign.Builder instead of the Hystrix one.
Implement an ErrorDecoder that wraps non‑2xx responses into HystrixBadRequestException so the exception propagates without triggering circuit‑breaker.
public class UserErrorDecoder implements ErrorDecoder {
private Logger logger = LoggerFactory.getLogger(getClass());
public Exception decode(String methodKey, Response response) {
ObjectMapper om = new JiaJianJacksonObjectMapper();
JiaJianResponse resEntity;
Exception exception = null;
try {
resEntity = om.readValue(Util.toString(response.body().asReader()), JiaJianResponse.class);
exception = new WebApplicationException(
javax.ws.rs.core.Response.status(response.status())
.entity(resEntity)
.type(MediaType.APPLICATION_JSON)
.build()
);
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
if (400 <= response.status() && response.status() < 500) {
exception = new HystrixBadRequestException("request exception wrapper", exception);
} else {
logger.error(exception.getMessage(), exception);
}
return exception;
}
} @Configuration
public class FeignConfiguration {
@Bean
public ErrorDecoder errorDecoder() {
return new UserErrorDecoder();
}
}In the business service, catch the wrapped exception and re‑throw the original WebApplicationException to obtain the HTTP status code.
@Override
public UserSigninResEntity signIn(UserSigninReqEntity param) throws Exception {
try {
UserSigninResEntity entity = userRemoteCall.signin(secretConfiguration.getKeys().get("user-service"), param);
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
if (ex.getCause() instanceof WebApplicationException) {
throw (WebApplicationException) ex.getCause();
}
throw ex;
}
}Conclusion
The article demonstrates how to solve the problem of Feign‑Hystrix swallowing service‑side request exceptions.
By wrapping error responses in HystrixBadRequestException and providing a custom ErrorDecoder, the original HTTP status code can be propagated to the caller.
The approach works with Jersey and can be adapted to other stacks.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
