Netflix/Hystrix · error · FallbackDefinitionException
Incompatible return types. \nCommand method: " + commandMeth
Error message
Incompatible return types. \nCommand method: " + commandMethod + ";\nFallback method: " + fallbackMethod + ";\n" + "Hint: " + StringUtils.join(msg, "\n")
What it means
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.
Source
Thrown at hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java:180
List<Type> commandParametrizedTypes = flattenTypeVariables(commandMethod.getGenericReturnType());
List<Type> fallbackParametrizedTypes = flattenTypeVariables(fallbackMethod.getGenericReturnType());
Result result = equalsParametrizedTypes(commandParametrizedTypes, fallbackParametrizedTypes);
if (!result.success) {
List<String> msg = new ArrayList<String>();
for (Error error : result.errors) {
Optional<Type> parentKindOpt = getParentKind(error.commandType, commandParametrizedTypes);
String extraHint = "";
if (parentKindOpt.isPresent()) {
Type parentKind = parentKindOpt.get();
if (isParametrizedType(parentKind)) {
extraHint = "--> " + ((ParameterizedType) parentKind).getRawType().toString() + "<Ooops!>\n";
}
}
msg.add(String.format(error.reason + "\n" + extraHint + "Command type literal pos: %s; Fallback type literal pos: %s",
positionAsString(error.commandType, commandParametrizedTypes),
positionAsString(error.fallbackType, fallbackParametrizedTypes)));
}
throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, StringUtils.join(msg, "\n")));
}
}
validatePlainReturnType(commandMethod, fallbackMethod);
}
private void validatePlainReturnType(Method commandMethod, Method fallbackMethod) {
validatePlainReturnType(commandMethod.getReturnType(), fallbackMethod.getReturnType(), commandMethod, fallbackMethod);
}
private void validatePlainReturnType(Class<?> commandReturnType, Class<?> fallbackReturnType, Method commandMethod, Method fallbackMethod) {
if (!commandReturnType.isAssignableFrom(fallbackReturnType)) {
throw new FallbackDefinitionException(createErrorMsg(commandMethod, fallbackMethod, "Fallback method '"
+ fallbackMethod + "' must return: " + commandReturnType + " or its subclass"));
}
}
private void validateParametrizedType(Type commandReturnType, Type fallbackReturnType, Method commandMethod, Method fallbackMethod) {
if (!commandReturnType.equals(fallbackReturnType)) {View on GitHub (pinned to 5ce3bc58c3)
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
Example fix
// before
@HystrixCommand
public Observable<User> getUser(String id) { ... }
private Observable<AdminUser> getUserFallback(String id) { ... }
// after
@HystrixCommand
public Observable<User> getUser(String id) { ... }
private Observable<User> getUserFallback(String id) { ... } Defensive patterns
Strategy: validation
Validate before calling
Type cmdType = commandMethod.getGenericReturnType();
Type fbType = fallbackMethod.getGenericReturnType();
if (!Objects.equals(flatten(cmdType), flatten(fbType))) {
throw new IllegalStateException("Generic return types differ: " + cmdType + " vs " + fbType);
}
// flatten = recursively collect raw type + type arguments into a List<Type> Type guard
boolean fallbackTypeMatches(Method command, Method fallback) {
return Objects.equals(command.getGenericReturnType(), fallback.getGenericReturnType());
} Try / catch
catch (FallbackDefinitionException e) { log.error("{}", e.getMessage()); /* message contains type-literal positions — align generics exactly and restart */ } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- fallback cannot return Future if the fallback isn't command
- fallback cannot return Future if command isn't asynchronous.
- fallback cannot return Observable if command isn't observabl
- fallback cannot return 'void' if command return type is " +
- Incompatible return types. \nCommand method: " + commandMeth
AI-assisted analysis of Netflix/Hystrix@5ce3bc58c3 (2026-08-14).
Data as JSON: /api/errors/55ad50b997a51f3a.
Report an issue: GitHub.