{"record":{"id":"55ad50b997a51f3a","repo":"Netflix/Hystrix","slug":"incompatible-return-types-ncommand-method-c","errorCode":null,"errorMessage":"Incompatible return types. \\nCommand method: \" + commandMethod + \";\\nFallback method: \" + fallbackMethod + \";\\n\" + \"Hint: \" + StringUtils.join(msg, \"\\n\")","messagePattern":"Incompatible return types\\. \\\\nCommand method: \" \\+ commandMethod \\+ \";\\\\nFallback method: \" \\+ fallbackMethod \\+ \";\\\\n\" \\+ \"Hint: \" \\+ StringUtils\\.join\\(msg, \"\\\\n\"\\)","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":180,"sourceCode":"            List<Type> commandParametrizedTypes = flattenTypeVariables(commandMethod.getGenericReturnType());\n            List<Type> fallbackParametrizedTypes = flattenTypeVariables(fallbackMethod.getGenericReturnType());\n            Result result = equalsParametrizedTypes(commandParametrizedTypes, fallbackParametrizedTypes);\n            if (!result.success) {\n                List<String> msg = new ArrayList<String>();\n                for (Error error : result.errors) {\n                    Optional<Type> parentKindOpt = getParentKind(error.commandType, commandParametrizedTypes);\n                    String extraHint = \"\";\n                    if (parentKindOpt.isPresent()) {\n                        Type parentKind = parentKindOpt.get();\n                        if (isParametrizedType(parentKind)) {\n                            extraHint = \"--> \" + ((ParameterizedType) parentKind).getRawType().toString() + \"<Ooops!>\\n\";\n                        }\n                    }\n                    msg.add(String.format(error.reason + \"\\n\" + extraHint + \"Command type literal pos: %s; Fallback type literal pos: %s\",\n                            positionAsString(error.commandType, commandParametrizedTypes),\n                            positionAsString(error.fallbackType, fallbackParametrizedTypes)));\n                }\n                throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, StringUtils.join(msg, \"\\n\")));\n            }\n        }\n        validatePlainReturnType(commandMethod, fallbackMethod);\n    }\n\n    private void validatePlainReturnType(Method commandMethod, Method fallbackMethod) {\n        validatePlainReturnType(commandMethod.getReturnType(), fallbackMethod.getReturnType(), commandMethod, fallbackMethod);\n    }\n\n    private void validatePlainReturnType(Class<?> commandReturnType, Class<?> fallbackReturnType, Method commandMethod, Method fallbackMethod) {\n        if (!commandReturnType.isAssignableFrom(fallbackReturnType)) {\n            throw new FallbackDefinitionException(createErrorMsg(commandMethod, fallbackMethod, \"Fallback method '\"\n                    + fallbackMethod + \"' must return: \" + commandReturnType + \" or its subclass\"));\n        }\n    }\n\n    private void validateParametrizedType(Type commandReturnType, Type fallbackReturnType, Method commandMethod, Method fallbackMethod) {\n        if (!commandReturnType.equals(fallbackReturnType)) {","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/Netflix/Hystrix/blob/5ce3bc58c38e7ca60ef2fe0e516e390e294ad941/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java#L162-L198","documentation":"validateReturnType performs deep generic comparison between the command's and fallback's parametrized return types (e.g. Future<User> vs Future<Admin>, Observable<List<String>> vs Observable<List<Integer>>). When the flattened type-argument lists mismatch, each Error gets a reason plus a position hint ('Command type literal pos: ...; Fallback type literal pos: ...' and an '<Ooops!>' marker for the offending parent generic), all joined into createErrorMsg's 'Incompatible return types' FallbackDefinitionException.","triggerScenarios":"Command returns Observable<User> while fallback returns Observable<UserExt> (type-variable mismatch at a nested literal); command ListenableFuture<List<A>> vs fallback ListenableFuture<List<B>>; generics erased or substituted differently through a generic base class.","commonSituations":"Fallback copy-pasted from another command with similar DTOs; refactoring DTO hierarchies so a fallback's generic argument is a sibling rather than identical type; generic repository methods where type variables resolve differently for command and fallback.","solutions":["Read the hint: it names the exact type literal positions that differ in both signatures","Make the fallback's generic arguments exactly equal (equals-comparison, not assignability) to the command's, e.g. Observable<User> on both sides","If a different subtype is genuinely needed, change the command's return type to the common supertype first","Add compile-time or CI validation invoking each annotated method once so mismatches surface before production"],"exampleFix":"// before\n@HystrixCommand\npublic Observable<User> getUser(String id) { ... }\n\nprivate Observable<AdminUser> getUserFallback(String id) { ... }\n\n// after\n@HystrixCommand\npublic Observable<User> getUser(String id) { ... }\n\nprivate Observable<User> getUserFallback(String id) { ... }","handlingStrategy":"validation","validationCode":"Type cmdType = commandMethod.getGenericReturnType();\nType fbType = fallbackMethod.getGenericReturnType();\nif (!Objects.equals(flatten(cmdType), flatten(fbType))) {\n    throw new IllegalStateException(\"Generic return types differ: \" + cmdType + \" vs \" + fbType);\n}\n// flatten = recursively collect raw type + type arguments into a List<Type>","typeGuard":"boolean fallbackTypeMatches(Method command, Method fallback) {\n    return Objects.equals(command.getGenericReturnType(), fallback.getGenericReturnType());\n}","tryCatchPattern":"catch (FallbackDefinitionException e) { log.error(\"{}\", e.getMessage()); /* message contains type-literal positions — align generics exactly and restart */ }","preventionTips":["Use identical generic signatures for command and fallback (copy-paste then change only the body)","Avoid sibling DTO types inside generics; unify on the command's declared type","Add an ArchUnit/reflection rule comparing getGenericReturnType() of every pair"],"tags":["hystrix","javanica","fallback","generics","type-mismatch","validation"],"backgroundTag":null,"analyzedSha":"5ce3bc58c38e7ca60ef2fe0e516e390e294ad941","analyzedAt":"2026-08-14T10:55:35.600Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}