{"record":{"id":"b832c30c13405ee3","repo":"flowable/flowable-engine","slug":"invocation-result-invocationresult-from-invoca","errorCode":null,"errorMessage":"Invocation result + invocationResult + from invocation + invocation + was not a CompletableFuture","messagePattern":"Invocation result \\+ invocationResult \\+ from invocation \\+ invocation \\+ was not a CompletableFuture","errorType":"exception","errorClass":"FlowableIllegalStateException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/ServiceTaskDelegateExpressionActivityBehavior.java","lineNumber":199,"sourceCode":"                    }\n\n                    if (loggingSessionEnabled) {\n                        BpmnLoggingSessionUtil.addLoggingData(LoggingSessionConstants.TYPE_SERVICE_TASK_EXIT,\n                                \"Executed service task with delegate \" + delegate, execution);\n                    }\n                } else if (delegate instanceof FutureJavaDelegate) {\n                    FutureJavaDelegate<Object> futureJavaDelegate = (FutureJavaDelegate<Object>) delegate;\n                    DelegateInvocation invocation = new FutureJavaDelegateInvocation(futureJavaDelegate, execution,\n                            processEngineConfiguration.getAsyncTaskInvoker());\n                    processEngineConfiguration.getDelegateInterceptor().handleInvocation(invocation);\n\n                    Object invocationResult = invocation.getInvocationResult();\n                    if (invocationResult instanceof CompletableFuture) {\n                        CompletableFuture<Object> future = (CompletableFuture<Object>) invocationResult;\n\n                        CommandContextUtil.getAgenda(commandContext).planFutureOperation(future, new FutureJavaDelegateCompleteAction(futureJavaDelegate, execution, loggingSessionEnabled));\n                    } else {\n                        throw new FlowableIllegalStateException(\n                                \"Invocation result \" + invocationResult + \" from invocation \" + invocation + \" was not a CompletableFuture\");\n                    }\n\n\n                } else {\n                    throw new FlowableIllegalArgumentException(\"Delegate expression \" + expression + \" did neither resolve to an implementation of \" + ActivityBehavior.class + \" nor \" + JavaDelegate.class);\n                }\n\n            } else {\n                if (loggingSessionEnabled) {\n                    BpmnLoggingSessionUtil.addLoggingData(LoggingSessionConstants.TYPE_SKIP_TASK, \"Skipped service task \" + execution.getCurrentActivityId() +\n                            \" with skip expression \" + skipExpressionText, execution);\n                }\n                leave(execution);\n            }\n        } catch (Exception exc) {\n\n            handleException(exc, execution, loggingSessionEnabled);","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/ServiceTaskDelegateExpressionActivityBehavior.java#L181-L217","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the FutureJavaDelegate's invocation returns a non-null CompletableFuture (use CompletableFuture.completedFuture(value) for immediate results)","Check for Flowable version upgrade breakage: FutureJavaDelegate contract now requires CompletableFuture results","Audit the delegate class for early-return paths that skip building the future","If plain synchronous semantics are desired, implement JavaDelegate instead of FutureJavaDelegate"],"exampleFix":"// before\npublic Object execute(DelegateExecution execution) {\n  if (input == null) return null; // breaks contract\n  return doWork(input);\n}\n// after\npublic CompletableFuture<Object> execute(DelegateExecution execution) {\n  if (input == null) return CompletableFuture.completedFuture(null);\n  return CompletableFuture.supplyAsync(() -> doWork(input));\n}","handlingStrategy":"type-guard","validationCode":"// Assert delegate result type in tests\nObject result = delegate.execute(execution);\nif (!(result instanceof CompletableFuture)) {\n    throw new IllegalStateException(\"FutureJavaDelegate must return CompletableFuture\");\n}","typeGuard":"public static boolean isFutureResult(Object o) {\n    return o instanceof CompletableFuture;\n}","tryCatchPattern":"try {\n    processEngine.getRuntimeService().signal(executionId);\n} catch (FlowableIllegalStateException e) {\n    if (e.getMessage().contains(\"was not a CompletableFuture\")) {\n        // fix FutureJavaDelegate to always return a CompletableFuture\n    }\n}","preventionTips":["Use CompletableFuture.completedFuture for synchronous results","Never return null from FutureJavaDelegate invoke paths","Re-audit FutureJavaDelegate implementations after Flowable upgrades"],"tags":["future","async","service-task","flowable"],"backgroundTag":"type-mismatch","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}