Netflix/Hystrix · error · FallbackDefinitionException

fallback cannot return Observable if command isn't…

Error message

fallback cannot return Observable if command isn't observable.

What it means

Third branch of the fallback execution-type matrix: if the command is neither asynchronous nor observable (a plain synchronous command), a fallback returning rx.Observable is rejected, because a synchronous HystrixCommand cannot transparently subscribe to and block on an Observable fallback to produce a plain value.

Solutions

  1. Change the fallback to return the plain type matching the command (String)
  2. If reactive fallback semantics are required, make the command itself Observable-returning so the pair is legal
  3. Extract the value-producing logic from the reactive fallback into a synchronous method both can call
  4. Validate all command/fallback pairs with a startup or CI smoke test

Example fix

// before
@HystrixCommand
public String getUser(String id) { ... }

private Observable<String> getUserFallback(String id) { ... }

// after
@HystrixCommand
public String getUser(String id) { ... }

private String getUserFallback(String id) { ... }
Defensive patterns

Strategy: validation

Validate before calling

Class<?> cmdRet = commandMethod.getReturnType();
Class<?> fbRet = fallbackMethod.getReturnType();
boolean cmdObs = Observable.class.isAssignableFrom(cmdRet) || Single.class.isAssignableFrom(cmdRet) || Completable.class.isAssignableFrom(cmdRet);
if (!cmdObs && (Observable.class.isAssignableFrom(fbRet) || Single.class.isAssignableFrom(fbRet) || Completable.class.isAssignableFrom(fbRet))) {
    throw new IllegalStateException("Sync command cannot have Observable-returning fallback: " + fallbackMethod);
}

Try / catch

catch (FallbackDefinitionException e) { log.error("Observable fallback on non-observable command: {}", e.getMessage()); failBuild(e); }

Prevention

When it happens

Trigger: @HystrixCommand String call() with fallback Observable<String> callFallback(Throwable e); mixing reactive-style fallbacks (perhaps reused from an Rx codebase) onto sync commands.

Common situations: Migrating a service from Rx-based commands to sync commands while keeping reactive fallbacks; shared fallback helpers that return Observable; IDE auto-generated fallbacks from reactive interfaces.

Related errors


AI-assisted analysis of Netflix/Hystrix@5ce3bc58c3 (2026-08-14). Data as JSON: /api/errors/ca75bcf084c3c1f0. Report an issue: GitHub.

Appendix: source

Thrown at hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java:136

                if (isCommand() && ExecutionType.ASYNCHRONOUS == getExecutionType()) {
                    validateReturnType(commandMethod, method);
                }
                if (ExecutionType.ASYNCHRONOUS != getExecutionType()) {
                    Type commandParametrizedType = commandMethod.getGenericReturnType();
                    if (isReturnTypeParametrized(commandMethod)) {
                        commandParametrizedType = getFirstParametrizedType(commandMethod);
                    }
                    validateParametrizedType(commandParametrizedType, method.getGenericReturnType(), commandMethod, method);
                }
                if (!isCommand() && ExecutionType.ASYNCHRONOUS == getExecutionType()) {
                    throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return Future if the fallback isn't command when the command is async."));
                }
            } else {
                if (ExecutionType.ASYNCHRONOUS == getExecutionType()) {
                    throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return Future if command isn't asynchronous."));
                }
                if (ExecutionType.OBSERVABLE == getExecutionType()) {
                    throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return Observable if command isn't observable."));
                }
                validateReturnType(commandMethod, method);
            }

        }
    }

    private Type getFirstParametrizedType(Method m) {
        Type gtype = m.getGenericReturnType();
        if (gtype instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) gtype;
            return pType.getActualTypeArguments()[0];
        }
        return null;
    }

    // everything can be wrapped into completable except 'void'
    private void validateCompletableReturnType(Method commandMethod, Class<?> callbackReturnType) {

View on GitHub (pinned to 5ce3bc58c3)