{"record":{"id":"156bc1464904df80","repo":"flowable/flowable-engine","slug":"deleting-historic-case-instances-with-related-data","errorCode":null,"errorMessage":"deleting historic case instances with related data requires CommandExecutor","messagePattern":"deleting historic case instances with related data requires CommandExecutor","errorType":"exception","errorClass":"FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/HistoricCaseInstanceQueryImpl.java","lineNumber":1023,"sourceCode":"    @Override\n    public void delete() {\n        if (commandExecutor != null) {\n            commandExecutor.execute(new DeleteHistoricCaseInstancesCmd(this));\n        } else {\n            new DeleteHistoricCaseInstancesCmd(this).execute(Context.getCommandContext());\n        }\n    }\n\n    @Override\n    @Deprecated\n    public void deleteWithRelatedData() {\n        if (commandExecutor != null) {\n            CommandConfig config = new CommandConfig().transactionRequiresNew();\n            commandExecutor.execute(config, new DeleteHistoricCaseInstancesCmd(this));\n            commandExecutor.execute(config, new DeleteTaskAndPlanItemInstanceDataOfRemovedHistoricCaseInstancesCmd());\n            commandExecutor.execute(config, new DeleteRelatedDataOfRemovedHistoricCaseInstancesCmd());\n        } else {\n            throw new FlowableException(\"deleting historic case instances with related data requires CommandExecutor\");\n        }\n    }\n\n    @Override\n    public String deleteInParallelUsingBatch(int batchSize, String batchName) {\n        return commandExecutor.execute(new DeleteHistoricCaseInstancesUsingBatchesCmd(this, batchSize, batchName, false));\n    }\n\n    @Override\n    public String deleteSequentiallyUsingBatch(int batchSize, String batchName) {\n        return commandExecutor.execute(new DeleteHistoricCaseInstancesUsingBatchesCmd(this, batchSize, batchName, true));\n    }\n\n    @Override\n    public HistoricCaseInstanceQuery includeCaseVariables() {\n        this.includeCaseVariables = true;\n        return this;\n    }","sourceCodeStart":1005,"sourceCodeEnd":1041,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/HistoricCaseInstanceQueryImpl.java#L1005-L1041","documentation":"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.","triggerScenarios":"Calling delete() on a HistoricCaseInstanceQueryImpl created via new HistoricCaseInstanceQueryImpl() (commandExecutor == null) instead of via cmmnEngine.getCmmnHistoryService().createHistoricCaseInstanceQuery().","commonSituations":"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.","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."],"exampleFix":"// before\nHistoricCaseInstanceQueryImpl query = new HistoricCaseInstanceQueryImpl();\nquery.finishedBefore(date).delete();\n// after\nHistoricCaseInstanceQuery query = cmmnHistoryService.createHistoricCaseInstanceQuery()\n    .finishedBefore(date);\nquery.delete(); // CommandExecutor is wired by the service\n","handlingStrategy":"try-catch","validationCode":"if (query instanceof HistoricCaseInstanceQueryImpl) {\n    // ensure it was created via the history service so commandExecutor != null\n    HistoricCaseInstanceQuery q = cmmnHistoryService.createHistoricCaseInstanceQuery();\n}\nquery.delete();","typeGuard":"boolean isDeletable(HistoricCaseInstanceQuery q) { return q instanceof HistoricCaseInstanceQueryImpl; } // only service-created instances carry a CommandExecutor","tryCatchPattern":"try {\n    query.delete();\n} catch (FlowableException e) {\n    if (e.getMessage().contains(\"requires CommandExecutor\")) {\n        throw new IllegalStateException(\"Create the query via CmmnHistoryService, not the raw constructor\", e);\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["configuration","command-executor","cmmn","bulk-delete"],"backgroundTag":"missing-required-config","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"}