{"record":{"id":"dae7d0a4b375e029","repo":"Netflix/Hystrix","slug":"fallback-cannot-return-future-if-command-isn-t-asy","errorCode":null,"errorMessage":"fallback cannot return Future if command isn't asynchronous.","messagePattern":"fallback cannot return Future if command isn't asynchronous\\.","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":133,"sourceCode":"\n\n            } else if (ExecutionType.ASYNCHRONOUS == ExecutionType.getExecutionType(commandReturnType)) {\n                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    }","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/Netflix/Hystrix/blob/5ce3bc58c38e7ca60ef2fe0e516e390e294ad941/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java#L115-L151","documentation":"The mirror rule of the async case: when the command's execution type is NOT asynchronous (synchronous or Observable), a fallback that returns Future is rejected with FallbackDefinitionException — Javanica will not block on a Future to produce a sync/observable result, so the signature pairing is illegal by definition.","triggerScenarios":"@HystrixCommand User getUser() (sync) paired with fallback Future<User> getUserFallback(); or an Observable-returning command whose fallback returns Observable<Future<User>>-style async types.","commonSituations":"Converting a command from async back to sync and leaving the fallback async; sharing one fallback method between sync and async overloads of a command; refactoring return types without updating the fallback.","solutions":["Make the fallback return the same execution type as the command — plain User for a sync command","Keep separate fallback methods for the sync and async variants rather than sharing one","Add a unit test that loads each command/fallback pair once so the definition validation runs in CI","Consult the Javanica fallback signature matrix (sync->sync, async->plain value or command, observable->observable or plain value)"],"exampleFix":"// before\n@HystrixCommand\npublic User getUser(String id) { ... }\n\nprivate Future<User> getUserFallback(String id) { ... }\n\n// after\n@HystrixCommand\npublic User getUser(String id) { ... }\n\nprivate User getUserFallback(String id) { ... }","handlingStrategy":"validation","validationCode":"Class<?> cmdRet = commandMethod.getReturnType();\nClass<?> fbRet = fallbackMethod.getReturnType();\nboolean cmdAsync = Future.class.isAssignableFrom(cmdRet);\nif (!cmdAsync && Future.class.isAssignableFrom(fbRet)) {\n    throw new IllegalStateException(\"Non-async command cannot have a Future-returning fallback: \" + fallbackMethod);\n}","typeGuard":null,"tryCatchPattern":"catch (FallbackDefinitionException e) { log.error(\"Fallback returns Future for non-async command: {}\", e.getMessage()); failBuild(e); }","preventionTips":["Match fallback execution type to command execution type by convention","Separate fallbacks per command variant; do not share across sync/async overloads","CI test that loads every annotated bean to trigger validation"],"tags":["hystrix","javanica","fallback","async","future","validation"],"backgroundTag":null,"analyzedSha":"5ce3bc58c38e7ca60ef2fe0e516e390e294ad941","analyzedAt":"2026-08-14T10:55:35.600Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}