flowable/flowable-engine · error · ActivitiException
signal() can only be called on a %s instance
Error message
signal() can only be called on a %s instance
What it means
ClassDelegate is a proxy that loads a user-configured delegate class and forwards behavior calls to it. When signal() is invoked, it checks at runtime that the resolved delegate instance implements SignallableActivityBehavior; if not, this ActivitiException is thrown. The library throws it because signaling a delegate that has no signal-handling behavior is meaningless and would silently do nothing otherwise.
Source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/helper/ClassDelegate.java:178
} catch (BpmnError error) {
ErrorPropagation.propagateError(error, activityExecution);
} catch (RuntimeException e) {
if (!ErrorPropagation.mapException(e, activityExecution, mapExceptions))
throw e;
}
}
// Signallable activity behavior
@Override
public void signal(ActivityExecution execution, String signalName, Object signalData) throws Exception {
if (activityBehaviorInstance == null) {
activityBehaviorInstance = getActivityBehaviorInstance(execution);
}
if (activityBehaviorInstance instanceof SignallableActivityBehavior) {
((SignallableActivityBehavior) activityBehaviorInstance).signal(execution, signalName, signalData);
} else {
throw new ActivitiException("signal() can only be called on a " + SignallableActivityBehavior.class.getName() + " instance");
}
}
// Subprocess activityBehaviour
@Override
public void completing(DelegateExecution execution, DelegateExecution subProcessInstance) throws Exception {
if (activityBehaviorInstance == null) {
activityBehaviorInstance = getActivityBehaviorInstance((ActivityExecution) execution);
}
if (activityBehaviorInstance instanceof SubProcessActivityBehavior) {
((SubProcessActivityBehavior) activityBehaviorInstance).completing(execution, subProcessInstance);
} else {
throw new ActivitiException("completing() can only be called on a " + SubProcessActivityBehavior.class.getName() + " instance");
}
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Make the configured delegate class implement org.activiti.engine.impl.bpmn.behavior.SignallableActivityBehavior and provide a signal(ActivityExecution, String, Object) method.
- Verify the className/fieldDeclarations configured on the ClassDelegate point at the intended class (inspect getActivityBehaviorInstance/determineBehaviour output).
- If the activity should not be signaled, remove the trigger that calls signal() (e.g. the message/signal event or Execution.signal) or use execute() path instead.
Example fix
// before
public class MyDelegate implements ActivityBehavior {
public void execute(ActivityExecution execution) { ... }
}
// after
public class MyDelegate implements ActivityBehavior, SignallableActivityBehavior {
public void execute(ActivityExecution execution) { ... }
public void signal(ActivityExecution execution, String signalName, Object signalData) { ... }
} Defensive patterns
Strategy: type-guard
Validate before calling
Object delegate = ReflectUtil.instantiate(className);
if (!(delegate instanceof SignallableActivityBehavior)) {
throw new IllegalStateException(className + " must implement SignallableActivityBehavior before signaling");
} Type guard
boolean canSignal(ClassDelegate d) {
try { return d.getActivityBehaviorInstance(nullDelegateExec()) instanceof SignallableActivityBehavior; }
catch (Exception e) { return false; }
} Try / catch
try {
classDelegate.signal(execution, signalName, signalData);
} catch (ActivitiException e) {
if (e.getMessage().contains("SignallableActivityBehavior")) {
log.error("Delegate {} is not signallable; fix delegate class", className, e);
} else throw e;
} Prevention
- Only wire signaling (message/signal events) to delegates implementing SignallableActivityBehavior
- Add a startup test that instantiates every configured delegate and asserts its interfaces
- Keep delegate classnames centralized in constants to avoid config drift
When it happens
Trigger: Calling signal(execution, signalName, signalData) on a ClassDelegate whose configured className resolves to an instance that implements only ActivityBehavior or JavaDelegate, not SignallableActivityBehavior.
Common situations: A BPMN process reached a service task or receive-task-like activity whose delegate class was swapped or misconfigured; a developer changed the delegate class from a SignallableActivityBehavior implementation to a plain JavaDelegate and an external trigger then signals the execution; wrong delegateExpression pointing at the wrong bean.
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
- completing() can only be called on a %s instance
- completed() can only be called on a %s instance
- ${delegateInstance.getClass().getName()} doesn't implement $
- ${delegateInstance.getClass().getName()} doesn't implement $
- ${sequenceFlowId} does not match a sequence flow for ${deleg
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/1e9896493efa7228.
Report an issue: GitHub.