flowable/flowable-engine · error · FlowableIllegalArgumentException

The case element needs to be a plan item, but is a

Error message

The case element needs to be a plan item, but is a 

What it means

Thrown when the resolved case element exists but is not a PlanItem instance (e.g. it is a Stage, Milestone, or CasePageTask-level element). Dynamic plan item injection only supports creating new plan items; the elementId must point at a <planItem> in the case model.

Solutions

  1. Verify the element is a plan item: if (element instanceof PlanItem) before injecting, otherwise pick the correct planItem id from the model.
  2. Use cmmnModel's getPlanItems()/stage plan items to enumerate only plan item ids.
  3. If you meant to trigger a milestone, use the plan item runtime service triggerPlanItemInstance API instead.

Example fix

// before
CaseElement el = caseModel.getAllCaseElements().get(elementId);
planItemInstanceBuilder.elementId(elementId); // throws if el is a Stage/Milestone
// after
CaseElement el = caseModel.getAllCaseElements().get(elementId);
if (el instanceof PlanItem) {
    planItemInstanceBuilder.elementId(elementId);
} else {
    throw new IllegalStateException(elementId + " is not a plan item");
}
Defensive patterns

Strategy: validation

Validate before calling

Case caseModel = cmmnRepositoryService.getCmmnModel(caseDefinitionId).getCases().get(0);
CaseElement el = caseModel.getAllCaseElements().get(elementId);
if (!(el instanceof PlanItem)) {
    throw new IllegalArgumentException("elementId must reference a planItem, got " + el.getClass().getSimpleName());
}
planItemInstanceBuilder.elementId(elementId);

Type guard

boolean isInjectablePlanItem(CaseElement el) {
    return el instanceof PlanItem;
}

Prevention

When it happens

Trigger: Calling planItemInstanceBuilder.elementId(id) where id resolves to a case element that is not a plan item, such as passing a stage id or milestone id from allCaseElements().

Common situations: Iterating allCaseElements() and picking the first matching id without checking its type; confusing the plan item's id with the underlying planItemDefinition/task id; passing a milestone id intending to trigger it (which requires a different API).

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

    public CreateInjectedPlanItemInstanceCmd(InjectedPlanItemInstanceBuilderImpl planItemInstanceBuilder) {
        this.planItemInstanceBuilder = planItemInstanceBuilder;
    }

    @Override
    public PlanItemInstance execute(CommandContext commandContext) {

        Case caseModel = CaseDefinitionUtil.getCase(planItemInstanceBuilder.getCaseDefinitionId());
        if (caseModel == null) {
            throw new FlowableIllegalArgumentException("Could not find case model with case definition id " + planItemInstanceBuilder.getCaseDefinitionId());
        }

        CaseElement caseElement = caseModel.getAllCaseElements().get(planItemInstanceBuilder.getElementId());
        if (caseElement == null) {
            throw new FlowableIllegalArgumentException("Could not find case element with id " + planItemInstanceBuilder.getElementId());
        }
        if (!(caseElement instanceof PlanItem)) {
            throw new FlowableIllegalArgumentException("The case element needs to be a plan item, but is a " + caseElement.getClass().getName());
        }

        String runningCaseDefinitionId;
        String caseInstanceId;
        String tenantId;

        PlanItemInstance stagePlanItemInstance = null;
        if (planItemInstanceBuilder.injectInStage()) {
            stagePlanItemInstance = getStageInstanceEntity(commandContext);
            caseInstanceId = stagePlanItemInstance.getCaseInstanceId();
            tenantId = stagePlanItemInstance.getTenantId();
            runningCaseDefinitionId = stagePlanItemInstance.getCaseDefinitionId();
        } else if (planItemInstanceBuilder.injectInCase()) {
            CaseInstance caseInstance = getCaseInstanceEntity(commandContext);
            caseInstanceId = caseInstance.getId();
            tenantId = caseInstance.getTenantId();
            runningCaseDefinitionId = caseInstance.getCaseDefinitionId();
        } else {

View on GitHub (pinned to d6d39ce1c6)