flowable/flowable-engine · error · FlowableIllegalArgumentException
Delegate expression " + expression + " did neither resolve…
Error message
Delegate expression " + expression + " did neither resolve to an implementation of " + ActivityBehavior.class + " nor " + JavaDelegate.class
What it means
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.
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
Example fix
// before
public class MyTask { public void execute() {} } // expression ${myTask} resolves to wrong type
// after
public class MyTask implements JavaDelegate { public void execute(DelegateExecution e) {} } Defensive patterns
Strategy: type-guard
Validate before calling
Object d = DelegateExpressionUtil.resolveDelegateExpression(expr, processInstance, Collections.emptyList());
if (!(d instanceof JavaDelegate) && !(d instanceof ActivityBehavior)) {
throw new IllegalStateException("Delegate expression resolves to non-delegate type: " + d.getClass());
} Type guard
boolean isUsableDelegate(Object o) {
return o instanceof JavaDelegate || o instanceof ActivityBehavior;
} Try / catch
try {
migrationBuilder.migrate(instanceId);
} catch (FlowableIllegalArgumentException e) {
if (e.getMessage().contains("Delegate expression")) {
// fix the variable/bean type, then retry
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- Custom properties resolver delegate expression " +…
- Delegate expression + expression + did neither resolve to…
- Delegate expression " + expression + " did not resolve to…
- Delegate expression " + expression + " did not resolve to…
- Delegate expression " + expression + " did not resolve to…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/8de04fd081201a47.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java:620
String preUpgradeJavaDelegate, CommandContext commandContext) {
CommandContextUtil.getProcessEngineConfiguration(commandContext).getDelegateInterceptor()
.handleInvocation(new JavaDelegateInvocation((JavaDelegate) defaultInstantiateDelegate(preUpgradeJavaDelegate, Collections.emptyList()),
(ExecutionEntityImpl) processInstance));
}
protected void executeExpression(ProcessInstance processInstance, ProcessDefinition procDefToMigrateTo,
String preUpgradeJavaDelegateExpression, CommandContext commandContext) {
Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(preUpgradeJavaDelegateExpression);
Object delegate = DelegateExpressionUtil.resolveDelegateExpression(expression, (VariableContainer) processInstance, Collections.emptyList());
if (delegate instanceof ActivityBehavior) {
CommandContextUtil.getProcessEngineConfiguration(commandContext).getDelegateInterceptor().handleInvocation(new ActivityBehaviorInvocation((ActivityBehavior) delegate, (ExecutionEntityImpl) processInstance));
} else if (delegate instanceof JavaDelegate) {
CommandContextUtil.getProcessEngineConfiguration(commandContext).getDelegateInterceptor().handleInvocation(new JavaDelegateInvocation((JavaDelegate) delegate, (ExecutionEntityImpl) processInstance));
} else {
throw new FlowableIllegalArgumentException("Delegate expression " + expression + " did neither resolve to an implementation of " + ActivityBehavior.class + " nor " + JavaDelegate.class);
}
}
protected List<ChangeActivityStateBuilderImpl> prepareChangeStateBuilders(ExecutionEntity processInstanceExecution, ProcessDefinition procDefToMigrateTo, ProcessInstanceMigrationDocument document, CommandContext commandContext) {
ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
// Check processDefinition tenant
String procDefTenantId = procDefToMigrateTo.getTenantId();
if (!isSameOrDefaultTenant(processInstanceExecution.getTenantId(), procDefToMigrateTo.getKey(),
procDefTenantId, CommandContextUtil.getProcessEngineConfiguration(commandContext))) {
throw new FlowableException("Tenant mismatch between Process Instance ('" + processInstanceExecution.getTenantId() + "') and Process Definition ('" + procDefTenantId + "') to migrate to");
}
List<ChangeActivityStateBuilderImpl> changeActivityStateBuilders = new ArrayList<>();
ChangeActivityStateBuilderImpl mainProcessChangeActivityStateBuilder = new ChangeActivityStateBuilderImpl();
mainProcessChangeActivityStateBuilder.processInstanceId(processInstanceExecution.getId());
changeActivityStateBuilders.add(mainProcessChangeActivityStateBuilder);View on GitHub (pinned to d6d39ce1c6)