{"record":{"id":"a25f169bd42c228d","repo":"flowable/flowable-engine","slug":"invocation-result-invocationresult-from-invoca-a25f16","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/ServiceTaskFutureJavaDelegateActivityBehavior.java","lineNumber":141,"sourceCode":"            try {\n                if (loggingSessionEnabled) {\n                    BpmnLoggingSessionUtil.addLoggingData(LoggingSessionConstants.TYPE_SERVICE_TASK_ENTER,\n                                    \"Executing service task with java class \" + futureJavaDelegate.getClass().getName(), execution);\n                }\n\n                FutureJavaDelegate<Object> futureJavaDelegate = (FutureJavaDelegate<Object>) this.futureJavaDelegate;\n\n                FutureJavaDelegateInvocation 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            } catch (RuntimeException e) {\n                if (loggingSessionEnabled) {\n                    BpmnLoggingSessionUtil.addErrorLoggingData(LoggingSessionConstants.TYPE_SERVICE_TASK_EXCEPTION,\n                                    \"Service task with java class \" + futureJavaDelegate.getClass().getName() + \" threw exception \" + e.getMessage(), e, execution);\n                }\n\n                throw e;\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            if (!triggerable) {","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/ServiceTaskFutureJavaDelegateActivityBehavior.java#L123-L159","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\npublic Object execute(DelegateExecution execution) {\n  return lookup(execution); // plain value, not a future\n}\n// after\npublic CompletableFuture<Object> execute(DelegateExecution execution) {\n  return CompletableFuture.completedFuture(lookup(execution));\n}","handlingStrategy":"type-guard","validationCode":"// Test that the FutureJavaDelegate always returns a CompletableFuture\nObject result = futureJavaDelegate.execute(execution);\nif (!(result instanceof CompletableFuture)) {\n    throw new IllegalStateException(\"FutureJavaDelegate must return a non-null CompletableFuture\");\n}","typeGuard":"public static boolean isCompletableFuture(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        // return CompletableFuture.completedFuture(...) instead\n    }\n}","preventionTips":["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"],"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"}