flowable/flowable-engine · error · FlowableIllegalStateException

Can only trigger a plan item that is in the ACTIVE state

Error message

Can only trigger a plan item that is in the ACTIVE state

What it means

ProcessTaskActivityBehavior.trigger() is invoked to complete/notify a process task plan item, but the plan item is not in the ACTIVE state, so triggering it is illegal. Flowable throws FlowableIllegalStateException to protect the state machine of the case execution.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java:163

                processInstanceService.deleteProcessInstance(processInstanceId);
                FaultPropagation.propagateFault(businessError, commandContext, planItemInstanceEntity);
                return;
            }

        } else {
            processInstanceService.startProcessInstance(processDefinitionId, processInstanceId, planItemInstanceEntity.getStageInstanceId(),
                    planItemInstanceEntity.getTenantId(), inParametersMap, businessKey, variableFormVariables, variableFormInfo, variableFormOutcome);
        }

        if (!blocking) {
            CommandContextUtil.getAgenda(commandContext).planCompletePlanItemInstanceOperation(planItemInstanceEntity);
        }
    }

    @Override
    public void trigger(CommandContext commandContext, PlanItemInstanceEntity planItemInstance) {
        if (!PlanItemInstanceState.ACTIVE.equals(planItemInstance.getState())) {
            throw new FlowableIllegalStateException("Can only trigger a plan item that is in the ACTIVE state");
        }
        if (planItemInstance.getReferenceId() == null) {
            throw new FlowableIllegalStateException("Cannot trigger process task plan item instance : no reference id set");
        }
        if (!ReferenceTypes.PLAN_ITEM_CHILD_PROCESS.equals(planItemInstance.getReferenceType())) {
            throw new FlowableException("Cannot trigger process task plan item instance : reference type '"
                    + planItemInstance.getReferenceType() + "' not supported for " + planItemInstance);
        }

        // Need to be set before planning the complete operation
        CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext);
        CaseInstanceEntity caseInstance = cmmnEngineConfiguration.getCaseInstanceEntityManager().findById(planItemInstance.getCaseInstanceId());
        handleOutParameters(planItemInstance, caseInstance, cmmnEngineConfiguration.getProcessInstanceService());

        // Triggering the plan item (as opposed to a regular complete) terminates the process instance
        CommandContextUtil.getAgenda(commandContext).planCompletePlanItemInstanceOperation(planItemInstance);
        deleteProcessInstance(commandContext, planItemInstance);
    }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Check planItemInstance.getState() equals PlanItemInstanceState.ACTIVE before calling trigger/completion APIs
  2. Guard the child-process completion callback so it is idempotent and skips plan items already completed/terminated
  3. Use CmmnRuntimeService.createPlanItemInstanceQuery() to fetch the current state from the DB rather than a stale in-memory object
  4. If termination races are expected, catch FlowableIllegalStateException around the trigger call and treat it as already-finalized

Example fix

// before
runtimeService.completePlanItemInstance(planItemId); // may throw if not ACTIVE
// after
PlanItemInstance pii = runtimeService.createPlanItemInstanceQuery().planItemInstanceId(planItemId).singleResult();
if (pii != null && PlanItemInstanceState.ACTIVE.equals(pii.getState())) {
    runtimeService.completePlanItemInstance(planItemId);
}
Defensive patterns

Strategy: type-guard

Validate before calling

PlanItemInstance pii = runtimeService.createPlanItemInstanceQuery()
    .planItemInstanceId(planItemId).singleResult();
boolean triggerable = pii != null && "active".equals(pii.getState());

Type guard

function isActive(pii) { return pii != null && 'active' === pii.getState(); }

Try / catch

try {
    runtimeService.completePlanItemInstance(planItemId);
} catch (FlowableIllegalStateException e) {
    // plan item already finalized (completed/terminated); treat as idempotent no-op
}

Prevention

When it happens

Trigger: Calling CmmnRuntimeService/PlanItemInstance trigger (e.g. completePlanItemInstance or a child-process completion callback) on a plan item whose state is AVAILABLE, ENABLED, COMPLETED, TERMINATED, etc.

Common situations: Race condition where the child process finished after the case was already terminated or the plan item completed; double-completion from an event/callback listener; manually triggering an async process task that has not yet become active.

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/7511fa1bbcf55bed. Report an issue: GitHub.