flowable/flowable-engine · error · FlowableException
deleting historic case instances with related data requires…
Error message
deleting historic case instances with related data requires CommandExecutor
What it means
HistoricCaseInstanceQueryImpl.delete() (bulk delete of historic case instances with all related data) requires a CommandExecutor to run its three delete commands in new transactions. If the query was constructed without a CommandExecutor — e.g. built manually rather than obtained from the engine's CmmnHistoryService — a FlowableException is thrown because the delete cannot be dispatched.
Solutions
- Obtain the query from the engine: cmmnEngineConfig.buildCmmnEngine().getCmmnHistoryService().createHistoricCaseInstanceQuery(), which injects a CommandExecutor.
- If you must construct the query manually, pass the engine's CommandExecutor: new HistoricCaseInstanceQueryImpl(commandExecutor).
- In tests, build the query through a real or mocked CmmnHistoryService rather than the raw impl constructor.
- Consider the non-bulk historyService.deleteHistoricCaseInstance(caseInstanceId) for single deletes.
Example fix
// before
HistoricCaseInstanceQueryImpl query = new HistoricCaseInstanceQueryImpl();
query.finishedBefore(date).delete();
// after
HistoricCaseInstanceQuery query = cmmnHistoryService.createHistoricCaseInstanceQuery()
.finishedBefore(date);
query.delete(); // CommandExecutor is wired by the service
Defensive patterns
Strategy: try-catch
Validate before calling
if (query instanceof HistoricCaseInstanceQueryImpl) {
// ensure it was created via the history service so commandExecutor != null
HistoricCaseInstanceQuery q = cmmnHistoryService.createHistoricCaseInstanceQuery();
}
query.delete(); Type guard
boolean isDeletable(HistoricCaseInstanceQuery q) { return q instanceof HistoricCaseInstanceQueryImpl; } // only service-created instances carry a CommandExecutor Try / catch
try {
query.delete();
} catch (FlowableException e) {
if (e.getMessage().contains("requires CommandExecutor")) {
throw new IllegalStateException("Create the query via CmmnHistoryService, not the raw constructor", e);
}
throw e;
} Prevention
- Always create queries through the public service API (cmmnHistoryService.createHistoricCaseInstanceQuery()).
- Avoid instantiating *QueryImpl classes directly outside tests.
- For bulk deletes in tests, spin up an in-memory CMMN engine rather than mocking the query.
- Note delete() runs three commands in REQUIRES_NEW transactions — ensure the engine configuration is complete.
When it happens
Trigger: Calling delete() on a HistoricCaseInstanceQueryImpl created via new HistoricCaseInstanceQueryImpl() (commandExecutor == null) instead of via cmmnEngine.getCmmnHistoryService().createHistoricCaseInstanceQuery().
Common situations: Unit tests instantiating the query class directly; deserialization or builder utilities that construct the query object without wiring the engine; custom code reaching into impl classes instead of going through the public service API.
Understand the failure class
Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.
Related errors
- Cannot complete CMMN job. There is no CMMN engine available
- Cannot query external jobs. There is no BPMN or CMMN engine…
- Cannot unacquire CMMN job. There is no CMMN engine available
- Case should be validated, but no case validator is…
- caseInstanceIds are null
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/156bc1464904df80.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/HistoricCaseInstanceQueryImpl.java:1023
@Override
public void delete() {
if (commandExecutor != null) {
commandExecutor.execute(new DeleteHistoricCaseInstancesCmd(this));
} else {
new DeleteHistoricCaseInstancesCmd(this).execute(Context.getCommandContext());
}
}
@Override
@Deprecated
public void deleteWithRelatedData() {
if (commandExecutor != null) {
CommandConfig config = new CommandConfig().transactionRequiresNew();
commandExecutor.execute(config, new DeleteHistoricCaseInstancesCmd(this));
commandExecutor.execute(config, new DeleteTaskAndPlanItemInstanceDataOfRemovedHistoricCaseInstancesCmd());
commandExecutor.execute(config, new DeleteRelatedDataOfRemovedHistoricCaseInstancesCmd());
} else {
throw new FlowableException("deleting historic case instances with related data requires CommandExecutor");
}
}
@Override
public String deleteInParallelUsingBatch(int batchSize, String batchName) {
return commandExecutor.execute(new DeleteHistoricCaseInstancesUsingBatchesCmd(this, batchSize, batchName, false));
}
@Override
public String deleteSequentiallyUsingBatch(int batchSize, String batchName) {
return commandExecutor.execute(new DeleteHistoricCaseInstancesUsingBatchesCmd(this, batchSize, batchName, true));
}
@Override
public HistoricCaseInstanceQuery includeCaseVariables() {
this.includeCaseVariables = true;
return this;
}View on GitHub (pinned to d6d39ce1c6)