flowable/flowable-engine · error · FlowableException

completed() can only be called on a…

Error message

completed() can only be called on a SubProcessActivityBehavior instance for + execution

What it means

ClassDelegate.completed() forwards the subprocess-finished callback only when the resolved ActivityBehavior implements SubProcessActivityBehavior; otherwise it throws a FlowableException stating completed() requires a SubProcessActivityBehavior. This mirrors the completing() check on the parent-execution side after the subprocess instance has ended.

Solutions

  1. Make the delegate behavior implement SubProcessActivityBehavior (or extend AbstractBpmnActivityBehavior).
  2. Confirm the BPMN element uses a delegate class intended for subprocess semantics.
  3. Restrict custom calls to completed() to subprocess-configured delegates.
  4. Rebuild/redeploy after any delegate class refactor to keep the process definition consistent.

Example fix

// before
public class MyBehavior implements ActivityBehavior { public void execute(...) {...} }
// after
public class MyBehavior extends AbstractBpmnActivityBehavior {
    @Override public void completed(DelegateExecution execution) { ... }
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!(behavior instanceof SubProcessActivityBehavior)) throw new IllegalStateException("Delegate must be a SubProcessActivityBehavior");

Type guard

boolean isSubProcessBehavior(ActivityBehavior b) { return b instanceof SubProcessActivityBehavior; }

Try / catch

try {
    runtimeService.startProcessInstanceByKey("proc");
} catch (FlowableException e) {
    if (e.getMessage().contains("completed() can only be called")) { /* fix delegate class */ }
    throw e;
}

Prevention

When it happens

Trigger: A subprocess child activity's ClassDelegate resolves to a behavior that is not a SubProcessActivityBehavior, and completed(execution) is invoked when the subprocess instance completes and control returns to the parent scope.

Common situations: Same family as 2105: wrong delegate class on a subprocess element, refactoring away from AbstractBpmnActivityBehavior, or custom orchestration code invoking completed() on a plain delegate.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ClassDelegate.java:269

        }

        if (activityBehaviorInstance instanceof SubProcessActivityBehavior) {
            ((SubProcessActivityBehavior) activityBehaviorInstance).completing(execution, subProcessInstance);
        } else {
            throw new FlowableException("completing() can only be called on a " + SubProcessActivityBehavior.class.getName() + " instance for " + execution);
        }
    }

    @Override
    public void completed(DelegateExecution execution) throws Exception {
        if (activityBehaviorInstance == null) {
            activityBehaviorInstance = getActivityBehaviorInstance();
        }

        if (activityBehaviorInstance instanceof SubProcessActivityBehavior) {
            ((SubProcessActivityBehavior) activityBehaviorInstance).completed(execution);
        } else {
            throw new FlowableException("completed() can only be called on a " + SubProcessActivityBehavior.class.getName() + " instance for " + execution);
        }
    }

    protected ActivityBehavior getActivityBehaviorInstance() {
        Object delegateInstance = instantiateDelegate(className, fieldDeclarations);

        if (delegateInstance instanceof ActivityBehavior) {
            return determineBehaviour((ActivityBehavior) delegateInstance);
        } else if (delegateInstance instanceof JavaDelegate) {
            return determineBehaviour(new ServiceTaskJavaDelegateActivityBehavior((JavaDelegate) delegateInstance, triggerable, skipExpression));
        } else if (delegateInstance instanceof FutureJavaDelegate) {
            return determineBehaviour(new ServiceTaskFutureJavaDelegateActivityBehavior((FutureJavaDelegate<?>) delegateInstance, triggerable, skipExpression, mapExceptions));
        } else {
            throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + " doesn't implement " + JavaDelegate.class.getName() + ", " + FutureJavaDelegate.class.getName() + " nor " + ActivityBehavior.class.getName());
        }
    }

    // Adds properties to the given delegation instance (eg multi instance) if needed

View on GitHub (pinned to d6d39ce1c6)