flowable/flowable-engine · error · FlowableIllegalStateException

Can only disable a plan item instance which is in state ENAB

Error message

Can only disable a plan item instance which is in state ENABLED

What it means

FlowableIllegalStateException thrown by DisablePlanItemInstanceCmd.internalExecute when the target plan item instance is not in the ENABLED state. Only enabled plan items (e.g. human task stage entries waiting for user action) can be disabled. This is a state-machine guard to prevent illegal lifecycle transitions.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/DisablePlanItemInstanceCmd.java:45

public class DisablePlanItemInstanceCmd extends AbstractNeedsPlanItemInstanceCmd {

    public DisablePlanItemInstanceCmd(String planItemInstanceId) {
        super(planItemInstanceId);
    }

    public DisablePlanItemInstanceCmd(String planItemInstanceId, Map<String, Object> variables,
            Map<String, Object> formVariables, String formOutcome, FormInfo formInfo,
            Map<String, Object> localVariables,
            Map<String, Object> transientVariables) {
        
        super(planItemInstanceId, variables, formVariables, formOutcome, formInfo, 
                localVariables, transientVariables);
    }

    @Override
    protected void internalExecute(CommandContext commandContext, PlanItemInstanceEntity planItemInstanceEntity) {
        if (!PlanItemInstanceState.ENABLED.equals(planItemInstanceEntity.getState())) {
            throw new FlowableIllegalStateException("Can only disable a plan item instance which is in state ENABLED");
        }
        CommandContextUtil.getAgenda(commandContext).planDisablePlanItemInstanceOperation(planItemInstanceEntity);
    }
    
}

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Query the plan item state first and only call disable when it equals PlanItemInstanceState.ENABLED
  2. Catch FlowableIllegalStateException and treat it as a no-op or user-facing 'already handled' message
  3. Refresh UI state after every state-changing call to avoid stale actions

Example fix

// before
planItemInstanceService.disablePlanItemInstance(planItemInstanceId);
// after
PlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery()
        .planItemInstanceId(planItemInstanceId).singleResult();
if (pii != null && PlanItemInstanceState.ENABLED.equals(pii.getState())) {
    planItemInstanceService.disablePlanItemInstance(planItemInstanceId);
}
Defensive patterns

Strategy: validation

Validate before calling

PlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery().planItemInstanceId(id).singleResult(); if (pii == null || !PlanItemInstanceState.ENABLED.equals(pii.getState())) return;

Type guard

boolean canDisable = pii != null && PlanItemInstanceState.ENABLED.equals(pii.getState());

Try / catch

try { planItemInstanceService.disablePlanItemInstance(id); } catch (FlowableIllegalStateException e) { log.info("plan item {} not in ENABLED state", id); }

Prevention

When it happens

Trigger: Calling planItemInstanceService.disablePlanItemInstance(id) on a plan item already disabled, completed, active, or unavailable.

Common situations: Double-clicking a 'disable' button in a task UI so the second call hits an already-DISABLED item; concurrent user actions; stale plan item id shown in UI after the state changed; scripting against a case whose plan item auto-completed.

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/09e814f4980e7463. Report an issue: GitHub.