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.

Programmer DD
Programmer DD
Programmer DD
Propagating Service Exceptions Through Feign Without Triggering Hystrix Circuit Breaker

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaMicroservicesException HandlingfeignErrorDecoder
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.