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 requires the historic task to be finished: if getEndTime() is null the task is still open and the engine throws FlowableException. History deletion is reserved for completed tasks.

Source

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

    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.getHistoryManager(commandContext).recordHistoricTaskDeleted(historicTaskInstance);
        
        return null;
    }

}

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Complete the task first (taskService.complete(taskId)) before deleting its history
  2. Check historicTaskInstance.getEndTime() != null before deleting
  3. Use taskService-based deletion for active tasks instead of history APIs

Example fix

// before
historyService.deleteHistoricTask(taskId); // task not finished
// after
HistoricTaskInstance hti = historyService.createHistoricTaskInstanceQuery()
    .taskId(taskId).singleResult();
if (hti != null && hti.getEndTime() != null) {
    historyService.deleteHistoricTask(taskId);
}
Defensive patterns

Strategy: validation

Validate before calling

HistoricTaskInstance hti = historyService.createHistoricTaskInstanceQuery()
    .taskId(taskId).singleResult();
if (hti != null && hti.getEndTime() == null) {
    throw new IllegalStateException("Task " + taskId + " is not finished; complete it first");
}

Type guard

boolean isTaskFinished = id -> {
    HistoricTaskInstance h = historyService.createHistoricTaskInstanceQuery()
        .taskId(id).singleResult();
    return h != null && h.getEndTime() != null;
};

Try / catch

try {
    historyService.deleteHistoricTask(taskId);
} catch (FlowableException e) {
    if (e.getMessage().contains("endTime")) {
        logger.warn("Cannot delete history of unfinished task " + taskId);
    }
}

Prevention

When it happens

Trigger: historyService.deleteHistoricTask(taskId) on an active/uncompleted task, or on a task whose history row exists but has not received an endTime (still claimed/in-progress).

Common situations: Cleanup jobs that skip the completion check, deleting a task's history while its process is still running, or confusion between taskService (runtime) and historyService (history) deletion APIs.

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/984a3a02ac872029. Report an issue: GitHub.