flowable/flowable-engine · error · FlowableIllegalStateException

Can only trigger an event listener plan item that is in the

Error message

Can only trigger an event listener plan item that is in the AVAILABLE state

What it means

TriggerPlanItemInstanceOperation.run executes the trigger only when the plan item instance has a PlanItem definition and is in the AVAILABLE state (for event listeners) or ACTIVE state (for others). Triggering an event listener that is not AVAILABLE is an illegal transition, so a FlowableIllegalStateException is thrown.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/TriggerPlanItemInstanceOperation.java:45

 */
public class TriggerPlanItemInstanceOperation extends AbstractPlanItemInstanceOperation {
    
    public TriggerPlanItemInstanceOperation(CommandContext commandContext, PlanItemInstanceEntity planItemInstanceEntity) {
        super(commandContext, planItemInstanceEntity);
    }
    
    @Override
    public void run() {
        if (PlanItemInstanceState.ACTIVE.equals(planItemInstanceEntity.getState())
                || (planItemInstanceEntity.getPlanItem() != null 
                && planItemInstanceEntity.getPlanItem().getPlanItemDefinition() instanceof EventListener
                && PlanItemInstanceState.AVAILABLE.equals(planItemInstanceEntity.getState()))){
            
            executeTrigger();

        } else {
            if (planItemInstanceEntity.getPlanItem() != null && planItemInstanceEntity.getPlanItem().getPlanItemDefinition() instanceof EventListener){
                throw new FlowableIllegalStateException("Can only trigger an event listener plan item that is in the AVAILABLE state");
            } else {
                throw new FlowableIllegalStateException("Can only trigger a plan item that is in the ACTIVE state");
            }

        }
    }

    protected void executeTrigger() {
        Object behaviorObject = planItemInstanceEntity.getPlanItem().getBehavior();
        if (!(behaviorObject instanceof CmmnTriggerableActivityBehavior)) {
            throw new FlowableException("Cannot trigger a plan item which activity behavior does not implement the " 
                    + CmmnTriggerableActivityBehavior.class + " interface in " + planItemInstanceEntity);
        }
        CmmnTriggerableActivityBehavior behavior = (CmmnTriggerableActivityBehavior) planItemInstanceEntity.getPlanItem().getBehavior();
        if (behavior instanceof CoreCmmnTriggerableActivityBehavior) {
            ((CoreCmmnTriggerableActivityBehavior) behavior).trigger(commandContext, planItemInstanceEntity);
        } else {
            behavior.trigger(planItemInstanceEntity);

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Check the plan item instance state equals 'available' before triggering.
  2. Make client-side triggers idempotent (disable the button after first call).
  3. Catch FlowableIllegalStateException and treat repeat triggers as no-ops.
  4. Query fresh state within the same command to avoid races.

Example fix

// before
cmmnRuntimeService.triggerPlanItemInstance(planItemInstanceId);
// after
PlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery()
    .planItemInstanceId(planItemInstanceId).singleResult();
if (pii != null && "available".equals(pii.getState())) {
    cmmnRuntimeService.triggerPlanItemInstance(planItemInstanceId);
}
Defensive patterns

Strategy: validation

Validate before calling

// java
PlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery()
    .planItemInstanceId(planItemInstanceId).singleResult();
if (pii == null || !"available".equals(pii.getState())) return; // cannot trigger

Type guard

boolean isTriggerable(PlanItemInstance pii) {
    return pii != null && PlanItemInstanceState.AVAILABLE.equals(pii.getState());
}

Try / catch

try {
    cmmnRuntimeService.triggerPlanItemInstance(id);
} catch (FlowableIllegalStateException e) {
    if (e.getMessage().contains("Can only trigger")) {
        // already triggered or wrong state: idempotent no-op
    }
}

Prevention

When it happens

Trigger: Calling cmmnRuntimeService.triggerPlanItemInstance (or complete of a user event listener) when the event listener instance is already triggered/completed/terminated, or is in a state other than AVAILABLE.

Common situations: Double-click / duplicate submit completing the same user event listener; retry logic re-triggering after a first successful trigger; triggering a listener on a suspended or completed case.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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