{"record":{"id":"3e49173dbe6fbcba","repo":"flowable/flowable-engine","slug":"can-only-trigger-an-event-listener-plan-item-that","errorCode":null,"errorMessage":"Can only trigger an event listener plan item that is in the AVAILABLE state","messagePattern":"Can only trigger an event listener plan item that is in the AVAILABLE state","errorType":"exception","errorClass":"FlowableIllegalStateException","httpStatus":null,"severity":"error","filePath":"modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/TriggerPlanItemInstanceOperation.java","lineNumber":45,"sourceCode":" */\npublic class TriggerPlanItemInstanceOperation extends AbstractPlanItemInstanceOperation {\n    \n    public TriggerPlanItemInstanceOperation(CommandContext commandContext, PlanItemInstanceEntity planItemInstanceEntity) {\n        super(commandContext, planItemInstanceEntity);\n    }\n    \n    @Override\n    public void run() {\n        if (PlanItemInstanceState.ACTIVE.equals(planItemInstanceEntity.getState())\n                || (planItemInstanceEntity.getPlanItem() != null \n                && planItemInstanceEntity.getPlanItem().getPlanItemDefinition() instanceof EventListener\n                && PlanItemInstanceState.AVAILABLE.equals(planItemInstanceEntity.getState()))){\n            \n            executeTrigger();\n\n        } else {\n            if (planItemInstanceEntity.getPlanItem() != null && planItemInstanceEntity.getPlanItem().getPlanItemDefinition() instanceof EventListener){\n                throw new FlowableIllegalStateException(\"Can only trigger an event listener plan item that is in the AVAILABLE state\");\n            } else {\n                throw new FlowableIllegalStateException(\"Can only trigger a plan item that is in the ACTIVE state\");\n            }\n\n        }\n    }\n\n    protected void executeTrigger() {\n        Object behaviorObject = planItemInstanceEntity.getPlanItem().getBehavior();\n        if (!(behaviorObject instanceof CmmnTriggerableActivityBehavior)) {\n            throw new FlowableException(\"Cannot trigger a plan item which activity behavior does not implement the \" \n                    + CmmnTriggerableActivityBehavior.class + \" interface in \" + planItemInstanceEntity);\n        }\n        CmmnTriggerableActivityBehavior behavior = (CmmnTriggerableActivityBehavior) planItemInstanceEntity.getPlanItem().getBehavior();\n        if (behavior instanceof CoreCmmnTriggerableActivityBehavior) {\n            ((CoreCmmnTriggerableActivityBehavior) behavior).trigger(commandContext, planItemInstanceEntity);\n        } else {\n            behavior.trigger(planItemInstanceEntity);","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/TriggerPlanItemInstanceOperation.java#L27-L63","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the plan item instance state equals 'available' before triggering.","Make client-side triggers idempotent (disable the button after first call).","Catch FlowableIllegalStateException and treat repeat triggers as no-ops.","Query fresh state within the same command to avoid races."],"exampleFix":"// before\ncmmnRuntimeService.triggerPlanItemInstance(planItemInstanceId);\n// after\nPlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery()\n    .planItemInstanceId(planItemInstanceId).singleResult();\nif (pii != null && \"available\".equals(pii.getState())) {\n    cmmnRuntimeService.triggerPlanItemInstance(planItemInstanceId);\n}","handlingStrategy":"validation","validationCode":"// java\nPlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery()\n    .planItemInstanceId(planItemInstanceId).singleResult();\nif (pii == null || !\"available\".equals(pii.getState())) return; // cannot trigger","typeGuard":"boolean isTriggerable(PlanItemInstance pii) {\n    return pii != null && PlanItemInstanceState.AVAILABLE.equals(pii.getState());\n}","tryCatchPattern":"try {\n    cmmnRuntimeService.triggerPlanItemInstance(id);\n} catch (FlowableIllegalStateException e) {\n    if (e.getMessage().contains(\"Can only trigger\")) {\n        // already triggered or wrong state: idempotent no-op\n    }\n}","preventionTips":["Disable trigger/complete controls after first submission.","Re-read plan item state inside the same transaction as the trigger.","Treat duplicate triggers as idempotent no-ops server-side."],"tags":["cmmn","event-listener","state-transition"],"backgroundTag":"invalid-state-transition","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}