flowable/flowable-engine · error · FlowableException
Cannot find a PlanItemDefinitionExporter for
Error message
Cannot find a PlanItemDefinitionExporter for '{planItemDefinitionClass}' What it means
During CMMN model export, each PlanItemDefinition requires a registered exporter (AbstractPlanItemDefinitionExport) keyed by the definition's canonical class name. If no exporter matches the plan item definition's class, Flowable throws this FlowableException.
Solutions
- Register an exporter for the custom class via the exporter registration method (registerPlanItemDefinitionExporter / put in planItemDefinitionExporters).
- Ensure your custom PlanItemDefinition subclasses a supported built-in type (e.g. Task, UserEventListener).
- Align Flowable dependency versions (converter vs engine) so all built-in exporters are present.
- If you don't need the element exported, remove it from the CmmnModel before conversion.
Example fix
// before
model.addPlanItemDefinition(new MyCustomTask()); // no exporter registered
// after
FlowableCmmnXmlConverter.registerPlanItemDefinitionExporter("com.acme.MyCustomTask", new MyCustomTaskExport()); Defensive patterns
Strategy: validation
Validate before calling
public static boolean hasExporter(PlanItemDefinition pid) {
return AbstractPlanItemDefinitionExportRegistry != null
&& org.flowable.cmmn.converter.export.PlanItemDefinitionExport.class != null; // check via determineExporter reflection
}
// simpler: verify class is a known built-in type
public static boolean isSupported(PlanItemDefinition pid) {
return pid instanceof org.flowable.cmmn.model.Task
|| pid instanceof org.flowable.cmmn.model.EventListener
|| pid instanceof org.flowable.cmmn.model.Stage
|| pid instanceof org.flowable.cmmn.model.Milestone;
} Try / catch
try {
CmmnXMLConverter.convertToXMLDocument(model);
} catch (FlowableException e) {
if (e.getMessage().startsWith("Cannot find a PlanItemDefinitionExporter")) {
logger.error("Register an exporter for the custom plan item definition before export", e);
}
throw e;
} Prevention
- Always register a custom exporter when adding custom PlanItemDefinition subclasses
- Keep flowable-engine and flowable-cmmn-converter versions aligned
- Prefer subclassing supported built-in definition types
When it happens
Trigger: Calling PlanItemDefinitionExport.writePlanItemDefinition (or converting a model to XML) with a custom/unregistered PlanItemDefinition subclass that was never registered via the register mechanism, or with a definition type not supported by the installed converters.
Common situations: Custom plan item definitions added via extension API without registering a matching exporter; classpath mixing Flowable versions so built-in exporters are missing; deserialized models with new definition types exported by an older converter.
Understand the failure class
Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.
Related errors
- Cannot override built-in eventListenerType
- A dynamically created plan item can only be injected into a…
- A dynamically created plan item can only be injected into a…
- A 'maxInstanceCount' on a repetition rule with value '0' is…
- a suspended
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/903f49f8306d69f0.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/PlanItemDefinitionExport.java:60
addPlanItemDefinitionExport(new ExternalWorkerServiceTaskExport());
addPlanItemDefinitionExport(new MilestoneExport());
addPlanItemDefinitionExport(new GenericEventListenerExport());
addPlanItemDefinitionExport(new SignalEventListenerExport());
addPlanItemDefinitionExport(new IntentEventListenerExport());
addPlanItemDefinitionExport(new ReactivationEventListenerExport());
addPlanItemDefinitionExport(new TimerEventListenerExport());
addPlanItemDefinitionExport(new UserEventListenerExport());
addPlanItemDefinitionExport(new VariableEventListenerExport());
}
public static void addPlanItemDefinitionExport(AbstractPlanItemDefinitionExport exporter) {
planItemDefinitionExporters.put(exporter.getExportablePlanItemDefinitionClass().getCanonicalName(), exporter);
}
public static void writePlanItemDefinition(CmmnModel model, PlanItemDefinition planItemDefinition, XMLStreamWriter xtw, CmmnXmlConverterOptions options) throws Exception {
AbstractPlanItemDefinitionExport exporter = determineExporter(planItemDefinition);
if (exporter == null) {
throw new FlowableException("Cannot find a PlanItemDefinitionExporter for '" + planItemDefinition.getClass().getCanonicalName() + "'");
}
exporter.writePlanItemDefinition(model, planItemDefinition, xtw, options);
}
protected static AbstractPlanItemDefinitionExport determineExporter(PlanItemDefinition planItemDefinition) {
AbstractPlanItemDefinitionExport exporter = null;
Class currentPlanItemDefinitionClass = planItemDefinition.getClass();
while (exporter == null && !currentPlanItemDefinitionClass.equals(PlanItemDefinition.class)) {
String exporterType = currentPlanItemDefinitionClass.getCanonicalName();
exporter = planItemDefinitionExporters.get(exporterType);
currentPlanItemDefinitionClass = currentPlanItemDefinitionClass.getSuperclass(); // loop will stop once PlanItemDefinition is reached, so only child hierarchies will be checked
}
return exporter;
}View on GitHub (pinned to d6d39ce1c6)