{"record":{"id":"8de04fd081201a47","repo":"flowable/flowable-engine","slug":"delegate-expression-expression-did-neither","errorCode":null,"errorMessage":"Delegate expression \" + expression + \" did neither resolve to an implementation of \" + ActivityBehavior.class + \" nor \" + JavaDelegate.class","messagePattern":"Delegate expression \" \\+ expression \\+ \" did neither resolve to an implementation of \" \\+ ActivityBehavior\\.class \\+ \" nor \" \\+ JavaDelegate\\.class","errorType":"exception","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java","lineNumber":620,"sourceCode":"            String preUpgradeJavaDelegate, CommandContext commandContext) {\n        \n        CommandContextUtil.getProcessEngineConfiguration(commandContext).getDelegateInterceptor()\n            .handleInvocation(new JavaDelegateInvocation((JavaDelegate) defaultInstantiateDelegate(preUpgradeJavaDelegate, Collections.emptyList()),\n                (ExecutionEntityImpl) processInstance));\n    }\n\n    protected void executeExpression(ProcessInstance processInstance, ProcessDefinition procDefToMigrateTo, \n            String preUpgradeJavaDelegateExpression, CommandContext commandContext) {\n        \n        Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(preUpgradeJavaDelegateExpression);\n\n        Object delegate = DelegateExpressionUtil.resolveDelegateExpression(expression, (VariableContainer) processInstance, Collections.emptyList());\n        if (delegate instanceof ActivityBehavior) {\n            CommandContextUtil.getProcessEngineConfiguration(commandContext).getDelegateInterceptor().handleInvocation(new ActivityBehaviorInvocation((ActivityBehavior) delegate, (ExecutionEntityImpl) processInstance));\n        } else if (delegate instanceof JavaDelegate) {\n            CommandContextUtil.getProcessEngineConfiguration(commandContext).getDelegateInterceptor().handleInvocation(new JavaDelegateInvocation((JavaDelegate) delegate, (ExecutionEntityImpl) processInstance));\n        } else {\n            throw new FlowableIllegalArgumentException(\"Delegate expression \" + expression + \" did neither resolve to an implementation of \" + ActivityBehavior.class + \" nor \" + JavaDelegate.class);\n        }\n    }\n\n    protected List<ChangeActivityStateBuilderImpl> prepareChangeStateBuilders(ExecutionEntity processInstanceExecution, ProcessDefinition procDefToMigrateTo, ProcessInstanceMigrationDocument document, CommandContext commandContext) {\n        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);\n\n        // Check processDefinition tenant\n        String procDefTenantId = procDefToMigrateTo.getTenantId();\n        if (!isSameOrDefaultTenant(processInstanceExecution.getTenantId(), procDefToMigrateTo.getKey(), \n                procDefTenantId, CommandContextUtil.getProcessEngineConfiguration(commandContext))) {\n            \n            throw new FlowableException(\"Tenant mismatch between Process Instance ('\" + processInstanceExecution.getTenantId() + \"') and Process Definition ('\" + procDefTenantId + \"') to migrate to\");\n        }\n\n        List<ChangeActivityStateBuilderImpl> changeActivityStateBuilders = new ArrayList<>();\n        ChangeActivityStateBuilderImpl mainProcessChangeActivityStateBuilder = new ChangeActivityStateBuilderImpl();\n        mainProcessChangeActivityStateBuilder.processInstanceId(processInstanceExecution.getId());\n        changeActivityStateBuilders.add(mainProcessChangeActivityStateBuilder);","sourceCodeStart":602,"sourceCodeEnd":638,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java#L602-L638","documentation":"Thrown when a delegate expression in the migration document resolves to an object that implements neither ActivityBehavior nor JavaDelegate. Flowable's migration path executes migrated activity instances' delegate expressions via the delegate interceptor, which requires one of those two types. The check happens at migration execution time inside ProcessInstanceMigrationManagerImpl.","triggerScenarios":"Migrating a process instance to a new definition whose activity carries a delegate expression that resolves (via expression resolution against process instance variables) to a plain object, String, bean method result, or other non-delegate type.","commonSituations":"A process variable referenced by the delegate expression was changed (or removed/re-added) so it no longer holds a JavaDelegate/ActivityBehavior; the expression points to a Spring bean of the wrong type; a refactored class no longer implements JavaDelegate; expression typo returning a String.","solutions":["Change the expression target object so it implements JavaDelegate or ActivityBehavior","Fix the process variable so it stores a JavaDelegate/ActivityBehavior instance before migrating","Correct the delegate expression string in the process definition to point at the right bean/class","Prefer class-based delegates (flowable:class) instead of delegate expressions to avoid type resolution issues"],"exampleFix":"// before\npublic class MyTask { public void execute() {} } // expression ${myTask} resolves to wrong type\n// after\npublic class MyTask implements JavaDelegate { public void execute(DelegateExecution e) {} }","handlingStrategy":"type-guard","validationCode":"Object d = DelegateExpressionUtil.resolveDelegateExpression(expr, processInstance, Collections.emptyList());\nif (!(d instanceof JavaDelegate) && !(d instanceof ActivityBehavior)) {\n    throw new IllegalStateException(\"Delegate expression resolves to non-delegate type: \" + d.getClass());\n}","typeGuard":"boolean isUsableDelegate(Object o) {\n    return o instanceof JavaDelegate || o instanceof ActivityBehavior;\n}","tryCatchPattern":"try {\n    migrationBuilder.migrate(instanceId);\n} catch (FlowableIllegalArgumentException e) {\n    if (e.getMessage().contains(\"Delegate expression\")) {\n        // fix the variable/bean type, then retry\n    }\n}","preventionTips":["Make delegate expression targets implement JavaDelegate explicitly","Unit-test expression resolution against real process variables before migration","Prefer flowable:class over delegate expressions for stable types","Lock process variables holding delegates against accidental overwrites"],"tags":["flowable","delegate-expression","type-mismatch","migration"],"backgroundTag":"type-mismatch","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}