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

  1. Obtain the query from the engine: cmmnEngineConfig.buildCmmnEngine().getCmmnHistoryService().createHistoricCaseInstanceQuery(), which injects a CommandExecutor.
  2. If you must construct the query manually, pass the engine's CommandExecutor: new HistoricCaseInstanceQueryImpl(commandExecutor).
  3. In tests, build the query through a real or mocked CmmnHistoryService rather than the raw impl constructor.
  4. 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

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


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)