Activiti/Activiti · critical · ActivitiException
Programmatic error: should not be used for anything else…
Error message
Programmatic error: should not be used for anything else than a boundary event
What it means
BoundaryTimerEventActivityBehavior.execute asserts that the current flow element is a BoundaryEvent before creating a timer job. If any other flow element (start event, intermediate catch, activity, etc.) is mapped to this behavior, the precondition fails and this 'Programmatic error' ActivitiException is thrown. It signals behavior-to-element wiring is wrong, not user input.
Solutions
- Review custom ActivityBehaviorFactory/BpmnParseHandler overrides and ensure BoundaryTimerEventActivityBehavior is only returned for BoundaryEvent elements.
- Check which element id the failing execution was on (from the execution's current flow element) and verify its type in the deployed BPMN XML.
- Redeploy the process definition after fixing the model so the correct behavior mapping applies.
- If you truly need timer behavior on a non-boundary element, use IntermediateCatchEventActivityBehavior or the timer start-event behavior instead.
Example fix
// before
public ActivityBehavior createIntermediateCatchEventBehavior(IntermediateCatchEvent catchEvent) {
return new BoundaryTimerEventActivityBehavior(); // wrong binding
}
// after
public ActivityBehavior createIntermediateCatchEventBehavior(IntermediateCatchEvent catchEvent) {
return new IntermediateCatchEventActivityBehavior();
} Defensive patterns
Strategy: type-guard
Validate before calling
if (!(flowElement instanceof BoundaryEvent)) {
throw new IllegalStateException("BoundaryTimerEventActivityBehavior bound to non-boundary element: " + flowElement.getId());
} Type guard
boolean isBoundaryTimerBinding(FlowElement el) {
return el instanceof BoundaryEvent
&& el.getEventDefinitions().stream().anyMatch(d -> d instanceof TimerEventDefinition);
} Try / catch
try { behavior.execute(execution); } catch (ActivitiException e) { if (e.getMessage().contains("anything else than a boundary event")) { log.error("Behavior miswired for element {}", execution.getCurrentFlowElement(), e); } else { throw e; } } Prevention
- Only map BoundaryTimerEventActivityBehavior to BoundaryEvent+TimerEventDefinition in custom ActivityBehaviorFactory overrides.
- Use IntermediateCatchEventActivityBehavior for standalone timer catch events.
- Review custom parse handlers after engine upgrades, as default bindings may change.
When it happens
Trigger: execute() runs with execution.getCurrentFlowElement() not instanceof BoundaryEvent — caused by a custom BpmnParseHandler or ActivityBehaviorFactory override that assigns BoundaryTimerEventActivityBehavior to a non-boundary timer element (e.g. intermediateCatchEvent or startEvent with a TimeCycle/TimeDuration).
Common situations: Custom process engine configurators changing default behavior bindings; copy-paste of behavior assignments in custom parse handlers; model refactor that turned a boundary timer into a standalone timer event while old custom bindings remained deployed.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Compensation activity could not be found (or it is missing…
- Could not find a scope execution for compensation boundary…
- Failed to parse cron expression:
- No execution found for sub process of boundary cancel event
- Process model (id = ) could not be found
AI-assisted analysis of Activiti/Activiti@56435b1a97 (2026-09-09).
Data as JSON: /api/errors/abddafdb5513e153.
Report an issue: GitHub.
Appendix: source
Thrown at activiti-core/activiti-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/BoundaryTimerEventActivityBehavior.java:47
/**
*/
public class BoundaryTimerEventActivityBehavior extends BoundaryEventActivityBehavior {
private static final long serialVersionUID = 1L;
protected TimerEventDefinition timerEventDefinition;
public BoundaryTimerEventActivityBehavior(TimerEventDefinition timerEventDefinition, boolean interrupting) {
super(interrupting);
this.timerEventDefinition = timerEventDefinition;
}
@Override
public void execute(DelegateExecution execution) {
ExecutionEntity executionEntity = (ExecutionEntity) execution;
if (!(execution.getCurrentFlowElement() instanceof BoundaryEvent)) {
throw new ActivitiException(
"Programmatic error: " + this.getClass() + " should not be used for anything else than a boundary event"
);
}
JobManager jobManager = Context.getCommandContext().getJobManager();
TimerJobEntity timerJob = jobManager.createTimerJob(
timerEventDefinition,
interrupting,
executionEntity,
TriggerTimerEventJobHandler.TYPE,
TimerEventHandler.createConfiguration(
execution.getCurrentActivityId(),
timerEventDefinition.getEndDate(),
timerEventDefinition.getCalendarName()
)
);
if (timerJob != null) {
jobManager.scheduleTimerJob(timerJob);View on GitHub (pinned to 56435b1a97)