{"record":{"id":"d8c4ee9dd7c5b175","repo":"flowable/flowable-engine","slug":"end-event-with-canceleventdefinition-only-supporte","errorCode":null,"errorMessage":"end event with cancelEventDefinition only supported inside transaction subprocess (id={})","messagePattern":"end event with cancelEventDefinition only supported inside transaction subprocess \\(id=(.+?)\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/parser/handler/EndEventParseHandler.java","lineNumber":66,"sourceCode":"\n        // Error end event\n        if (eventDefinition instanceof org.flowable.bpmn.model.ErrorEventDefinition) {\n            org.flowable.bpmn.model.ErrorEventDefinition errorDefinition = (org.flowable.bpmn.model.ErrorEventDefinition) eventDefinition;\n            if (bpmnParse.getBpmnModel().containsErrorRef(errorDefinition.getErrorCode())) {\n                String errorCode = bpmnParse.getBpmnModel().getErrors().get(errorDefinition.getErrorCode());\n                if (StringUtils.isEmpty(errorCode)) {\n                    LOGGER.warn(\"errorCode is required for an error event {}\", endEvent.getId());\n                }\n                endEventActivity.setProperty(\"type\", \"errorEndEvent\");\n                errorDefinition.setErrorCode(errorCode);\n            }\n            endEventActivity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createErrorEndEventActivityBehavior(endEvent, errorDefinition));\n\n            // Cancel end event\n        } else if (eventDefinition instanceof CancelEventDefinition) {\n            ScopeImpl scope = bpmnParse.getCurrentScope();\n            if (scope.getProperty(\"type\") == null || !scope.getProperty(\"type\").equals(\"transaction\")) {\n                LOGGER.warn(\"end event with cancelEventDefinition only supported inside transaction subprocess (id={})\", endEvent.getId());\n            } else {\n                endEventActivity.setProperty(\"type\", \"cancelEndEvent\");\n                endEventActivity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createCancelEndEventActivityBehavior(endEvent));\n            }\n\n            // Terminate end event\n        } else if (eventDefinition instanceof TerminateEventDefinition) {\n            endEventActivity.setAsync(endEvent.isAsynchronous());\n            endEventActivity.setExclusive(!endEvent.isNotExclusive());\n            endEventActivity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createTerminateEndEventActivityBehavior(endEvent));\n\n            // None end event\n        } else if (eventDefinition == null) {\n            endEventActivity.setAsync(endEvent.isAsynchronous());\n            endEventActivity.setExclusive(!endEvent.isNotExclusive());\n            endEventActivity.setActivityBehavior(bpmnParse.getActivityBehaviorFactory().createNoneEndEventActivityBehavior(endEvent));\n        }\n    }","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/parser/handler/EndEventParseHandler.java#L48-L84","documentation":"A cancel end event (cancelEventDefinition) is only legal inside an embedded transaction subprocess per the BPMN spec. When the parser finds one whose enclosing scope's 'type' property is not 'transaction', it logs this warning and skips wiring the CancelEndEventActivityBehavior, so the end event will not trigger cancellation at runtime.","triggerScenarios":"executeParse of an EndEvent carrying a CancelEventDefinition while bpmnParse.getCurrentScope().getProperty(\"type\") is null or not \"transaction\" — i.e. the cancel end event sits in a plain subprocess, the top-level process, or a non-transactional container.","commonSituations":"Copying a cancel end event out of a transaction subprocess during refactoring; a modeler allowing cancel events anywhere; process templates missing the transactionSubprocess wrapper.","solutions":["Move the cancel end event into an embedded transaction subprocess (transaction subprocess element)","Replace the cancel end event with a plain or error end event if cancellation semantics are not needed","Add a boundary compensate/cancel event on the transaction subprocess if that was the intent"],"exampleFix":"// before\n<process><endEvent id=\"end\"><cancelEventDefinition/></endEvent></process>\n// after\n<process><subProcess triggeredByEvent=\"false\"><transaction id=\"tx\"><endEvent id=\"end\"><cancelEventDefinition/></endEvent></transaction></subProcess></process>","handlingStrategy":"validation","validationCode":"// ensure cancel end events sit inside a transaction subprocess\nbpmnModel.getMainProcess().getFlowElementsOfType(EndEvent.class).forEach(e -> {\n    if (hasCancelDefinition(e) && !isInsideTransactionSubprocess(bpmnModel, e)) {\n        throw new IllegalArgumentException(\"cancel end event \" + e.getId() + \" not in transaction subprocess\");\n    }\n});","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only use cancel end events inside <transaction> subprocess elements","Model transactional work as transaction subprocesses with boundary compensate handlers","Validate the process with BpmnParse/BpmnModel validation before deployment"],"tags":["bpmn","parsing","cancel-event","transaction-subprocess"],"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-18T11:17:12.947Z"}