{"record":{"id":"c59f4a64ac331bbd","repo":"flowable/flowable-engine","slug":"can-only-delete-a-child-entity-for-a-plan-item-wit-c59f4a","errorCode":null,"errorMessage":"Can only delete a child entity for a plan item with reference type ","messagePattern":"Can only delete a child entity for a plan item with reference type ","errorType":"exception","errorClass":"FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java","lineNumber":216,"sourceCode":"\n    @Override\n    public void deleteChildEntity(CommandContext commandContext, DelegatePlanItemInstance delegatePlanItemInstance, boolean cascade) {\n        if (ReferenceTypes.PLAN_ITEM_CHILD_PROCESS.equals(delegatePlanItemInstance.getReferenceType())) {\n            // This is not the regular termination through the agenda, but the historic plan item state still needs to be correct.\n            // The state needs to be set before deleting the process instance, because the process instance deletion triggers\n            // a ChildProcessInstanceStateChangeCallback which re-enters the CMMN engine to terminate the plan item.\n            // With the state already set to TERMINATED, this callback becomes a no-op and avoids re-triggering repetition rules.\n            PlanItemInstanceEntity planItemInstanceEntity = (PlanItemInstanceEntity) delegatePlanItemInstance;\n            if (!PlanItemInstanceState.TERMINATED.equals(planItemInstanceEntity.getState())) {\n                planItemInstanceEntity.setState(PlanItemInstanceState.TERMINATED);\n                planItemInstanceEntity.setEndedTime(CommandContextUtil.getCmmnEngineConfiguration(commandContext).getClock().getCurrentTime());\n                planItemInstanceEntity.setTerminatedTime(planItemInstanceEntity.getEndedTime());\n                CommandContextUtil.getCmmnHistoryManager(commandContext).recordPlanItemInstanceTerminated(planItemInstanceEntity);\n            }\n\n            deleteProcessInstance(commandContext, delegatePlanItemInstance);\n        } else {\n            throw new FlowableException(\"Can only delete a child entity for a plan item with reference type \" + ReferenceTypes.PLAN_ITEM_CHILD_PROCESS + \" for \" + delegatePlanItemInstance);\n        }\n    }\n\n    protected void handleOutParameters(DelegatePlanItemInstance planItemInstance,\n                                       CaseInstanceEntity caseInstance,\n                                       ProcessInstanceService processInstanceService) {\n\n        if (outParameters == null) {\n            return;\n        }\n\n        for (IOParameter outParameter : outParameters) {\n\n            String variableName = null;\n            if (StringUtils.isNotEmpty(outParameter.getTarget())) {\n                variableName = outParameter.getTarget();\n\n            } else if (StringUtils.isNotEmpty(outParameter.getTargetExpression())) {","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java#L198-L234","documentation":"deleteChildEntity cleans up the child entity (child process instance) of a plan item when the plan item is terminated or completed. If the plan item's referenceType is not PLAN_ITEM_CHILD_PROCESS, there is no child process to delete, and Flowable throws a FlowableException naming the expected reference type and plan item.","triggerScenarios":"Case exit/termination path calls deleteChildEntity for a plan item whose referenceType is a child case or is unset — i.e. the delete routine was invoked for a non-process-task child relationship.","commonSituations":"Terminating a case containing mixed task types where generic cleanup code calls the process-task delete path; referenceType never set because the child process failed to start; custom termination logic invoking internals directly.","solutions":["Only invoke deleteChildEntity for plan items with ReferenceTypes.PLAN_ITEM_CHILD_PROCESS; branch on referenceType before cleanup","Ensure the plan item actually started its child process (reference set) before termination cleanup expects deletion","Use the public CMMN runtime/termination APIs instead of calling behavior internals directly","If a mixed model is expected, implement per-type child cleanup (case vs process) in the terminating code"],"exampleFix":"// before\ndeleteChildEntity(commandContext, planItemInstance); // throws for non-process children\n// after\nif (ReferenceTypes.PLAN_ITEM_CHILD_PROCESS.equals(planItemInstance.getReferenceType())) {\n    deleteChildEntity(commandContext, planItemInstance);\n} else {\n    // other cleanup path (e.g. child case termination)\n}","handlingStrategy":"type-guard","validationCode":"boolean deletable = planItemInstance.getReferenceType() != null\n    && planItemInstance.getReferenceType().endsWith(\"childProcess\");","typeGuard":"function canDeleteChild(pii) { return 'planItemChildProcess'.equalsIgnoreCase(pii.getReferenceType()); }","tryCatchPattern":"try {\n    terminatePlanItem(planItem);\n} catch (FlowableException e) {\n    if (e.getMessage().startsWith(\"Can only delete a child entity\")) {\n        // fall back to type-appropriate cleanup\n    }\n}","preventionTips":["Use public termination APIs instead of behavior internals","Confirm the child process actually started before cleanup","Implement per-type cleanup for mixed case models","Check referenceType before every child-cleanup call"],"tags":["cmmn","termination","reference-type","cleanup"],"backgroundTag":"unsupported-operation","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}