flowable/flowable-engine · error · FlowableIllegalStateException
Invocation result + invocationResult + from invocation + inv
Error message
Invocation result + invocationResult + from invocation + invocation + was not a CompletableFuture
What it means
ServiceTaskFutureJavaDelegateActivityBehavior.execute throws FlowableIllegalStateException when a FutureJavaDelegate invocation returns a result that is not a CompletableFuture. Flowable plans the async completion (FutureJavaDelegateCompleteAction) on that future, so the contract requires every invocation to yield a CompletableFuture instance.
Source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/ServiceTaskFutureJavaDelegateActivityBehavior.java:141
try {
if (loggingSessionEnabled) {
BpmnLoggingSessionUtil.addLoggingData(LoggingSessionConstants.TYPE_SERVICE_TASK_ENTER,
"Executing service task with java class " + futureJavaDelegate.getClass().getName(), execution);
}
FutureJavaDelegate<Object> futureJavaDelegate = (FutureJavaDelegate<Object>) this.futureJavaDelegate;
FutureJavaDelegateInvocation invocation = new FutureJavaDelegateInvocation(futureJavaDelegate, execution,
processEngineConfiguration.getAsyncTaskInvoker());
processEngineConfiguration.getDelegateInterceptor().handleInvocation(invocation);
Object invocationResult = invocation.getInvocationResult();
if (invocationResult instanceof CompletableFuture) {
CompletableFuture<Object> future = (CompletableFuture<Object>) invocationResult;
CommandContextUtil.getAgenda(commandContext).planFutureOperation(future, new FutureJavaDelegateCompleteAction(futureJavaDelegate, execution, loggingSessionEnabled));
} else {
throw new FlowableIllegalStateException(
"Invocation result " + invocationResult + " from invocation " + invocation + " was not a CompletableFuture");
}
} catch (RuntimeException e) {
if (loggingSessionEnabled) {
BpmnLoggingSessionUtil.addErrorLoggingData(LoggingSessionConstants.TYPE_SERVICE_TASK_EXCEPTION,
"Service task with java class " + futureJavaDelegate.getClass().getName() + " threw exception " + e.getMessage(), e, execution);
}
throw e;
}
} else {
if (loggingSessionEnabled) {
BpmnLoggingSessionUtil.addLoggingData(LoggingSessionConstants.TYPE_SKIP_TASK, "Skipped service task " + execution.getCurrentActivityId() +
" with skip expression " + skipExpressionText, execution);
}
if (!triggerable) {View on GitHub (pinned to d6d39ce1c6)
Solutions
- Return a non-null CompletableFuture from the FutureJavaDelegate (wrap immediate values with CompletableFuture.completedFuture)
- Update delegates to the current FutureJavaDelegate interface after a Flowable upgrade
- Remove or fix code paths that return null early instead of an already-completed future
- If fully synchronous behavior is wanted, switch the class to implement JavaDelegate instead
Example fix
// before
public Object execute(DelegateExecution execution) {
return lookup(execution); // plain value, not a future
}
// after
public CompletableFuture<Object> execute(DelegateExecution execution) {
return CompletableFuture.completedFuture(lookup(execution));
} Defensive patterns
Strategy: type-guard
Validate before calling
// Test that the FutureJavaDelegate always returns a CompletableFuture
Object result = futureJavaDelegate.execute(execution);
if (!(result instanceof CompletableFuture)) {
throw new IllegalStateException("FutureJavaDelegate must return a non-null CompletableFuture");
} Type guard
public static boolean isCompletableFuture(Object o) {
return o instanceof CompletableFuture;
} Try / catch
try {
processEngine.getRuntimeService().signal(executionId);
} catch (FlowableIllegalStateException e) {
if (e.getMessage().contains("was not a CompletableFuture")) {
// return CompletableFuture.completedFuture(...) instead
}
} Prevention
- Always wrap immediate values with CompletableFuture.completedFuture
- Return completed futures on guard/early-exit paths instead of null
- Migrate legacy delegates to the CompletableFuture-returning contract after upgrades
When it happens
Trigger: A FutureJavaDelegate's execute/invoke returns null or a plain object instead of CompletableFuture; the instanceof check on invocation.getInvocationResult() fails inside the service task execution path.
Common situations: Legacy FutureJavaDelegate implementations upgraded across Flowable versions (pre-async contract returned plain values); null returns on guard clauses; asynchronous code refactored to return a custom promise-like type instead of CompletableFuture.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Invocation result + invocationResult + from invocation + inv
- Invalid usage of async_activate job handler, variable scope
- Future was interrupted
- None of the available futures completed within the max timeo
- No processDefinitionId, processDefinitionKey provided
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/a25f169bd42c228d.
Report an issue: GitHub.