{"record":{"id":"ca75bcf084c3c1f0","repo":"Netflix/Hystrix","slug":"fallback-cannot-return-observable-if-command-isn-t","errorCode":null,"errorMessage":"fallback cannot return Observable if command isn't observable.","messagePattern":"fallback cannot return Observable if command isn't observable\\.","errorType":"exception","errorClass":"FallbackDefinitionException","httpStatus":null,"severity":"error","filePath":"hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java","lineNumber":136,"sourceCode":"                if (isCommand() && ExecutionType.ASYNCHRONOUS == getExecutionType()) {\n                    validateReturnType(commandMethod, method);\n                }\n                if (ExecutionType.ASYNCHRONOUS != getExecutionType()) {\n                    Type commandParametrizedType = commandMethod.getGenericReturnType();\n                    if (isReturnTypeParametrized(commandMethod)) {\n                        commandParametrizedType = getFirstParametrizedType(commandMethod);\n                    }\n                    validateParametrizedType(commandParametrizedType, method.getGenericReturnType(), commandMethod, method);\n                }\n                if (!isCommand() && ExecutionType.ASYNCHRONOUS == getExecutionType()) {\n                    throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, \"fallback cannot return Future if the fallback isn't command when the command is async.\"));\n                }\n            } else {\n                if (ExecutionType.ASYNCHRONOUS == getExecutionType()) {\n                    throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, \"fallback cannot return Future if command isn't asynchronous.\"));\n                }\n                if (ExecutionType.OBSERVABLE == getExecutionType()) {\n                    throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, \"fallback cannot return Observable if command isn't observable.\"));\n                }\n                validateReturnType(commandMethod, method);\n            }\n\n        }\n    }\n\n    private Type getFirstParametrizedType(Method m) {\n        Type gtype = m.getGenericReturnType();\n        if (gtype instanceof ParameterizedType) {\n            ParameterizedType pType = (ParameterizedType) gtype;\n            return pType.getActualTypeArguments()[0];\n        }\n        return null;\n    }\n\n    // everything can be wrapped into completable except 'void'\n    private void validateCompletableReturnType(Method commandMethod, Class<?> callbackReturnType) {","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/Netflix/Hystrix/blob/5ce3bc58c38e7ca60ef2fe0e516e390e294ad941/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java#L118-L154","documentation":"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.","triggerScenarios":"@HystrixCommand String call() with fallback Observable<String> callFallback(Throwable e); mixing reactive-style fallbacks (perhaps reused from an Rx codebase) onto sync commands.","commonSituations":"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.","solutions":["Change the fallback to return the plain type matching the command (String)","If reactive fallback semantics are required, make the command itself Observable-returning so the pair is legal","Extract the value-producing logic from the reactive fallback into a synchronous method both can call","Validate all command/fallback pairs with a startup or CI smoke test"],"exampleFix":"// before\n@HystrixCommand\npublic String getUser(String id) { ... }\n\nprivate Observable<String> getUserFallback(String id) { ... }\n\n// after\n@HystrixCommand\npublic String getUser(String id) { ... }\n\nprivate String getUserFallback(String id) { ... }","handlingStrategy":"validation","validationCode":"Class<?> cmdRet = commandMethod.getReturnType();\nClass<?> fbRet = fallbackMethod.getReturnType();\nboolean cmdObs = Observable.class.isAssignableFrom(cmdRet) || Single.class.isAssignableFrom(cmdRet) || Completable.class.isAssignableFrom(cmdRet);\nif (!cmdObs && (Observable.class.isAssignableFrom(fbRet) || Single.class.isAssignableFrom(fbRet) || Completable.class.isAssignableFrom(fbRet))) {\n    throw new IllegalStateException(\"Sync command cannot have Observable-returning fallback: \" + fallbackMethod);\n}","typeGuard":null,"tryCatchPattern":"catch (FallbackDefinitionException e) { log.error(\"Observable fallback on non-observable command: {}\", e.getMessage()); failBuild(e); }","preventionTips":["Decide execution type per command up front and mirror it in the fallback","Do not reuse reactive fallback helpers for sync commands","Automate signature-pair checks with ArchUnit or a reflection scan"],"tags":["hystrix","javanica","fallback","observable","rxjava","validation"],"backgroundTag":null,"analyzedSha":"5ce3bc58c38e7ca60ef2fe0e516e390e294ad941","analyzedAt":"2026-08-14T10:55:35.600Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}