Netflix/Hystrix · error · FallbackDefinitionException
fallback cannot return Future if the fallback isn't command
Error message
fallback cannot return Future if the fallback isn't command when the command is async.
What it means
FallbackMethod.validate() checks that a fallback method is compatible with its @HystrixCommand method. When the command's execution type is ASYNCHRONOUS (returns Future/CompletableFuture/ListenableFuture), a fallback that also returns Future but is NOT itself a command (no @HystrixCommand on the fallback, i.e. not a chained async fallback) is rejected with FallbackDefinitionException, because Javanica cannot wrap a plain Future-returning fallback into the async command pipeline.
Source
Thrown at hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java:129
validateParametrizedType(commandParametrizedType, method.getGenericReturnType(), commandMethod, method);
} else {
validateReturnType(commandMethod, method);
}
} else if (ExecutionType.ASYNCHRONOUS == ExecutionType.getExecutionType(commandReturnType)) {
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;View on GitHub (pinned to 5ce3bc58c3)
Solutions
- Annotate the fallback method with @HystrixCommand so it becomes a command and legal async chaining works
- Or change the fallback to return the plain value (User) while the command returns Future — Javanica lifts it
- Or implement the fallback inside HystrixCommand.getFallback() by switching from annotation-style to explicit command class
- Re-run the validation at startup with a test that invokes each async command once
Example fix
// before
@HystrixCommand
public Future<User> getUser(String id) { ... }
private Future<User> getUserFallback(String id) { ... }
// after
@HystrixCommand
public Future<User> getUser(String id) { ... }
@HystrixCommand
private Future<User> getUserFallback(String id) { ... } Defensive patterns
Strategy: validation
Validate before calling
Class<?> cmdRet = commandMethod.getReturnType();
Class<?> fbRet = fallbackMethod.getReturnType();
boolean cmdAsync = Future.class.isAssignableFrom(cmdRet);
if (cmdAsync && Future.class.isAssignableFrom(fbRet)
&& fallbackMethod.getAnnotation(HystrixCommand.class) == null) {
throw new IllegalStateException("Async fallback returning Future must itself be @HystrixCommand");
} Type guard
null // no language-level guard; Javanica validates reflectively — encode the rule in a startup check as above
Try / catch
catch (FallbackDefinitionException e) { log.error("Fallback signature invalid: {}", e.getMessage()); /* fix signature and restart */ } Prevention
- Follow the Javanica fallback matrix: async command -> plain value OR @HystrixCommand-annotated async fallback
- Smoke-test every command/fallback pair in CI
- Keep fallback signatures generated/checked by a shared base class or ArchUnit rule
When it happens
Trigger: @HystrixCommand method returning Future<User> with a fallback method `Future<User> getUserFallback(Throwable t)` that has no @HystrixCommand annotation of its own.
Common situations: Copying a synchronous fallback signature while converting the command to async; assuming Future-returning fallbacks 'just work' like synchronous ones; missing the requirement that async-over-async fallback chaining requires the fallback itself to be a Hystrix command.
Related errors
- 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
- Incompatible return types. \nCommand method: " + commandMeth
AI-assisted analysis of Netflix/Hystrix@5ce3bc58c3 (2026-08-14).
Data as JSON: /api/errors/4d59e17d10dd642d.
Report an issue: GitHub.