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

ServiceTaskDelegateExpressionActivityBehavior.execute throws FlowableIllegalStateException when a delegate expression resolves to a FutureJavaDelegate whose invocation result is not a CompletableFuture. FutureJavaDelegate contract requires the delegate's invocation to return a CompletableFuture so Flowable can plan the async completion; anything else breaks the contract.

Source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/ServiceTaskDelegateExpressionActivityBehavior.java:199

                    }

                    if (loggingSessionEnabled) {
                        BpmnLoggingSessionUtil.addLoggingData(LoggingSessionConstants.TYPE_SERVICE_TASK_EXIT,
                                "Executed service task with delegate " + delegate, execution);
                    }
                } else if (delegate instanceof FutureJavaDelegate) {
                    FutureJavaDelegate<Object> futureJavaDelegate = (FutureJavaDelegate<Object>) delegate;
                    DelegateInvocation 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");
                    }


                } else {
                    throw new FlowableIllegalArgumentException("Delegate expression " + expression + " did neither resolve to an implementation of " + ActivityBehavior.class + " nor " + JavaDelegate.class);
                }

            } else {
                if (loggingSessionEnabled) {
                    BpmnLoggingSessionUtil.addLoggingData(LoggingSessionConstants.TYPE_SKIP_TASK, "Skipped service task " + execution.getCurrentActivityId() +
                            " with skip expression " + skipExpressionText, execution);
                }
                leave(execution);
            }
        } catch (Exception exc) {

            handleException(exc, execution, loggingSessionEnabled);

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Ensure the FutureJavaDelegate's invocation returns a non-null CompletableFuture (use CompletableFuture.completedFuture(value) for immediate results)
  2. Check for Flowable version upgrade breakage: FutureJavaDelegate contract now requires CompletableFuture results
  3. Audit the delegate class for early-return paths that skip building the future
  4. If plain synchronous semantics are desired, implement JavaDelegate instead of FutureJavaDelegate

Example fix

// before
public Object execute(DelegateExecution execution) {
  if (input == null) return null; // breaks contract
  return doWork(input);
}
// after
public CompletableFuture<Object> execute(DelegateExecution execution) {
  if (input == null) return CompletableFuture.completedFuture(null);
  return CompletableFuture.supplyAsync(() -> doWork(input));
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Assert delegate result type in tests
Object result = delegate.execute(execution);
if (!(result instanceof CompletableFuture)) {
    throw new IllegalStateException("FutureJavaDelegate must return CompletableFuture");
}

Type guard

public static boolean isFutureResult(Object o) {
    return o instanceof CompletableFuture;
}

Try / catch

try {
    processEngine.getRuntimeService().signal(executionId);
} catch (FlowableIllegalStateException e) {
    if (e.getMessage().contains("was not a CompletableFuture")) {
        // fix FutureJavaDelegate to always return a CompletableFuture
    }
}

Prevention

When it happens

Trigger: A delegate expression resolving to a FutureJavaDelegate implementation whose invoke/execution method returns null or a non-CompletableFuture object; the check `invocationResult instanceof CompletableFuture` fails and this error is raised.

Common situations: Mixing Flowable versions where FutureJavaDelegate semantics changed (newer versions expect CompletableFuture returns); a FutureJavaDelegate implemented against an older interface that returned plain values; returning null on early-exit code paths.

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


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/b832c30c13405ee3. Report an issue: GitHub.