Fading Coder

An Old Coder’s Final Dance

Home > Tech > Content

Handling "failed and no fallback available" in Feign with Hystrix

Tech 2

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;
        }
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.