flowable/flowable-engine · error · FlowableException

task does not have an endTime, cannot delete

Error message

task does not have an endTime, cannot delete 

What it means

DeleteHistoricTaskInstanceCmd throws this FlowableException when the historic task exists but getEndTime() is null, i.e. the task never completed. Only finished tasks may be deleted through this command, because deleting a live task's history row would corrupt runtime/history consistency.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/DeleteHistoricTaskInstanceCmd.java:54

    public DeleteHistoricTaskInstanceCmd(String taskId) {
        this.taskId = taskId;
    }

    @Override
    public Object execute(CommandContext commandContext) {

        if (taskId == null) {
            throw new FlowableIllegalArgumentException("taskId is null");
        }

        // Check if task is completed
        HistoricTaskInstanceEntity historicTaskInstance = CommandContextUtil.getHistoricTaskService().getHistoricTask(taskId);

        if (historicTaskInstance == null) {
            throw new FlowableObjectNotFoundException("No historic task instance found with id: " + taskId, HistoricTaskInstance.class);
        }
        if (historicTaskInstance.getEndTime() == null) {
            throw new FlowableException("task does not have an endTime, cannot delete " + historicTaskInstance);
        }

        CommandContextUtil.getCmmnHistoryManager(commandContext).recordHistoricTaskDeleted(historicTaskInstance);
        
        return null;
    }

}

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Only call deleteHistoricTaskInstance for tasks whose endTime is set (query .finished() or check getEndTime() first).
  2. If the intent is to remove an active task, cancel/complete the plan item through the runtime service instead of deleting its history.
  3. Filter cleanup queries with createHistoricTaskInstanceQuery().finished() so only deletable rows are processed.

Example fix

// before
historyService.deleteHistoricTaskInstance(taskId);
// after
HistoricTaskInstance hti = historyService.createHistoricTaskInstanceQuery().taskId(taskId).finished().singleResult();
if (hti != null) {
    historyService.deleteHistoricTaskInstance(taskId);
}
Defensive patterns

Strategy: validation

Validate before calling

HistoricTaskInstance hti = historyService.createHistoricTaskInstanceQuery().taskId(taskId).finished().singleResult();
if (hti != null) historyService.deleteHistoricTaskInstance(taskId);

Try / catch

try { historyService.deleteHistoricTaskInstance(taskId); } catch (FlowableException e) { log.warn("Task {} not finished; skipping history delete", taskId); }

Prevention

When it happens

Trigger: Calling CmmnHistoryService.deleteHistoricTaskInstance(taskId) for a task that was created but not yet finished (no endTime), e.g. deleting the history of an in-progress CMMN task.

Common situations: Cleanup scripts that filter historic tasks only by existence and not by finished state; API calls that pass a runtime task id where a completed-task id was expected.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/181877a71bcf815d. Report an issue: GitHub.