flowable/flowable-engine · error · FlowableException
Programmatic error: task listener added to an element that i
Error message
Programmatic error: task listener added to an element that is not a human task, but a ${currentCmmnElement.getClass()} What it means
Thrown by ExtensionElementsXMLConverter.readTaskListener when a <taskListener> extension element is encountered inside an element that is not a HumanTask. Task listeners are only valid on human tasks in CMMN, so the converter rejects them anywhere else. The message includes the actual element class to identify the misplaced listener.
Source
Thrown at modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/ExtensionElementsXMLConverter.java:372
if (StringUtils.isNotEmpty(targetType)) {
parameter.setTargetType(targetType);
}
if (StringUtils.isNotEmpty(isTransient)) {
parameter.setTransient(Boolean.parseBoolean(isTransient));
}
return parameter;
}
protected void readTaskListener(XMLStreamReader xtr, ConversionHelper conversionHelper) throws Exception {
BaseElement currentCmmnElement = conversionHelper.getCurrentCmmnElement(); // needs to be captured before setting the flowable listeners as this will change the current element
FlowableListener flowableListener = ListenerXmlConverterUtil.convertToListener(xtr);
if (flowableListener != null) {
if (currentCmmnElement instanceof HumanTask humanTask) {
humanTask.getTaskListeners().add(flowableListener);
} else {
throw new FlowableException("Programmatic error: task listener added to an element that is not a human task, but a " + currentCmmnElement.getClass());
}
}
conversionHelper.setCurrentCmmnElement(flowableListener);
}
protected void readLifecycleListener(XMLStreamReader xtr, ConversionHelper conversionHelper) throws Exception {
BaseElement currentCmmnElement = conversionHelper.getCurrentCmmnElement(); // needs to be captured before setting the flowable listeners as this will change the current element
FlowableListener flowableListener = ListenerXmlConverterUtil.convertToListener(xtr);
if (flowableListener != null) {
if (currentCmmnElement instanceof HasLifecycleListeners lifecycleListenersElement) {
lifecycleListenersElement.getLifecycleListeners().add(flowableListener);
} else {
throw new FlowableException("Programmatic error: lifecycle listener added to an element that is not a plan item definition, but a " + currentCmmnElement.getClass());
}
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Move the taskListener inside a <humanTask>'s extensionElements, or switch the host element to a human task if a listener is required.
- Remove the taskListener if the host element is not a human task.
- Use a flowable:caseEventListener/planItem lifecycle element instead for non-human-task callbacks where supported.
Example fix
<!-- before -->
<serviceTask id="st1">
<extensionElements>
<flowable:taskListener event="end" class="com.acme.MyListener"/>
</extensionElements>
</serviceTask>
<!-- after -->
<humanTask id="ht1">
<extensionElements>
<flowable:taskListener event="end" class="com.acme.MyListener"/>
</extensionElements>
</humanTask> Defensive patterns
Strategy: validation
Validate before calling
// Before conversion: every flowable:taskListener must be inside a humanTask // XPath: //flowable:taskListener[not(ancestor::humanTask)] should return empty
Type guard
boolean canHostTaskListener(BaseElement el) {
return el instanceof HumanTask;
} Try / catch
try {
cmmnModel = converter.convertToCmmnModel(stream, encoding);
} catch (FlowableException e) {
if (e.getMessage().startsWith("Programmatic error: task listener added")) {
// the host element class is in the message; move the listener to a HumanTask
}
throw e;
} Prevention
- In CMMN, attach task listeners only to humanTask elements (unlike BPMN).
- Lint XML so taskListener ancestors are always humanTask.
- Use case event listeners or plan-item lifecycle handling for non-human-task callbacks.
When it happens
Trigger: A <flowable:taskListener> child of <extensionElements> whose current CMMN element is anything other than a HumanTask (e.g. a case task, service task, stage, or case element), typically from hand-edited XML or tool-generated output.
Common situations: Copying BPMN-style taskListener usage into CMMN where only HumanTask supports it; bulk XML transformations that attach listeners to the wrong elements.
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
- ${delegateInstance.getClass().getName()} doesn't implement $
- Cannot create 'script' task listener. Missing ScriptInfo.
- Delegate expression ${expression} did not resolve to an impl
- The field 'script' should be set on the TaskListener
- The field 'language' should be set on the TaskListener
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/b7c168c756aee0cb.
Report an issue: GitHub.