Handling "failed and no fallback available" in Feign with Hystrix
When a Feign invocation is wrapped by Hystrix and the call times out or fails without a defined fallback, you may see: failed and no fallback available. Hystrix acts as a circuit breaker: it isolates calls and trips when execution exceeds configured limits or fails fast.
Timeouts and basic configuration
Hystrix and the HTTP client underneath Feign both have time-based limits. If Hystrix times out first, it triggers falllback; if no falback is present, you get the error above. If the HTTP client times out first, the error bubbles up similarly. Tuning both layers is essential.
- Increase Hystrix timeout (example: 5 seconds)
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
- Disable Hystrix timeouts (not generally recommended)
hystrix.command.default.execution.timeout.enabled=false
- Disable Feign’s Hystrix support entirely (not recommended if you want circuit breaking)
feign:
hystrix:
enabled: false
- Configure Ribbon/HTTP client timeouts (real network timeouts)
ribbon.ReadTimeout=60000
ribbon.ConnectTimeout=60000
- Control retries (avoid long retry chains under failure)
ribbon.maxAutoRetries=0
If you leave network timeoust low (or defaults), slow downstreams will cause Read timed out even if Hystrix is tuned.
Why "failed and no fallback available" appears
With Hystrix enabled for Feign:
feign:
hystrix:
enabled: true
Hystrix expects a fallback to handle failures. If none is defined, Hystrix throws the error above when a call fails or times out. Provide a fallback or a fallbackFactory on your Feign client.
Defining a fallback that captures error details with FallbackFactory
Using FallbackFactory lets you examine the cause of the failure and produce a meaningful response.
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(
name = "catalog-service",
fallbackFactory = CatalogClientFallbackFactory.class
)
public interface CatalogClient {
@GetMapping("/items/{id}")
ApiResult<ItemView> getItem(@PathVariable("id") Long id);
}
import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class CatalogClientFallbackFactory implements FallbackFactory<CatalogClient> {
private static final Logger log = LoggerFactory.getLogger(CatalogClientFallbackFactory.class);
@Override
public CatalogClient create(Throwable cause) {
final String reason = (cause == null || cause.getMessage() == null) ? "" : cause.getMessage();
if (!reason.isEmpty()) {
log.error("Feign call failed: {}", reason);
}
return id -> ApiResult.failure("Catalog service unavailable: " + reason);
}
}
Note: the Throwable seen in FallbackFactory is typically a Feign/Hystrix-wrapped exception. If your provider encodes business errors in a body (for example, a JSON envelope with message), you’ll need a custom ErrorDecoder to extract and propagate that message.
Preserving the remote error message with a custom ErrorDecoder
When the downstream responds with a non-2xx status code, Feign routes the response to an ErrorDecoder. By overriding it, you can read the response body, map it to your envelope, and raise a RuntimeException with the business message.
import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
public class PreserveRemoteErrorConfig {
@Bean
public ErrorDecoder feignErrorDecoder() {
return new RemoteMessageErrorDecoder();
}
static class RemoteMessageErrorDecoder implements ErrorDecoder {
private final Logger logger = LoggerFactory.getLogger(RemoteMessageErrorDecoder.class);
@Override
public Exception decode(String methodKey, Response response) {
try {
String payload = response.body() != null
? Util.toString(response.body().asReader(Util.UTF_8))
: "";
// Parse your standard response envelope
ApiResult<?> envelope = Jsons.fromJson(payload, ApiResult.class);
if (envelope != null && !envelope.isSuccess()) {
// Propagate the business error message
return new RuntimeException(envelope.getMessage());
}
// Fallback to raw payload if structure is unknown
return new RuntimeException(payload.isEmpty() ? ("HTTP " + response.status()) : payload);
} catch (Exception ex) {
logger.error("Failed to decode Feign error response", ex);
return new RuntimeException("Failed to decode error response: " + ex.getMessage(), ex);
}
}
}
}
Attach this configuration to the Feign client so the decoder is applied:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(
name = "catalog-service",
configuration = PreserveRemoteErrorConfig.class,
fallbackFactory = CatalogClientFallbackFactory.class
)
public interface CatalogClient {
@GetMapping("/items/{id}")
ApiResult<ItemView> getItem(@PathVariable("id") Long id);
}
The FallbackFactory will now receive a RuntimeException whose message is the business error extracted by the decoder.
Bypassing Hystrix fallback for business errors
In some cases, you may want business exceptions to pass through directly to the caller without triggering fallback logic. Hystrix treats HystrixBadRequestException as a non-fault (i.e., it does not trip the circuit or call fallback). You can map business errors to HystrixBadRequestException in your ErrorDecoder.
import com.netflix.hystrix.exception.HystrixBadRequestException;
import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
public class PassThroughBusinessErrorConfig {
@Bean
public ErrorDecoder feignErrorDecoder() {
return new PassThroughErrorDecoder();
}
static class PassThroughErrorDecoder implements ErrorDecoder {
private final Logger logger = LoggerFactory.getLogger(PassThroughErrorDecoder.class);
@Override
public Exception decode(String methodKey, Response response) {
try {
String payload = response.body() != null
? Util.toString(response.body().asReader(Util.UTF_8))
: "";
ApiResult<?> envelope = Jsons.fromJson(payload, ApiResult.class);
if (envelope != null && !envelope.isSuccess()) {
// Business error: do not engage Hystrix fallback
return new HystrixBadRequestException(envelope.getMessage());
}
// For unexpected errors, allow Hystrix to run fallback
return new RuntimeException(payload.isEmpty() ? ("HTTP " + response.status()) : payload);
} catch (Exception ex) {
logger.error("Failed to decode Feign error response", ex);
return new RuntimeException("Failed to decode error response: " + ex.getMessage(), ex);
}
}
}
}
Use this configuration when you want business-layer failures to be surfaced directly to controllers or callers instead of falling back.
Example response envelope types
The snippets above assume a project-level response wrapper. Replace ApiResult and Jsons with equivalents in your codebase.
public class ApiResult<T> {
private boolean success;
private String message;
private T data;
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public T getData() { return data; }
public static <T> ApiResult<T> failure(String msg) {
ApiResult<T> r = new ApiResult<>();
r.success = false;
r.message = msg;
return r;
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
public final class Jsons {
private static final ObjectMapper MAPPER = new ObjectMapper();
private Jsons() { }
public static <T> T fromJson(String json, Class<T> type) {
try {
return MAPPER.readValue(json, type);
} catch (Exception e) {
return null;
}
}
}