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

  1. Review custom ActivityBehaviorFactory/BpmnParseHandler overrides and ensure BoundaryTimerEventActivityBehavior is only returned for BoundaryEvent elements.
  2. 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.
  3. Redeploy the process definition after fixing the model so the correct behavior mapping applies.
  4. 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

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


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)