flowable/flowable-engine · error · FlowableException

task + " is already deleted"

Error message

task + " is already deleted"

What it means

Flowable throws this FlowableException when a NeedsActiveTaskCmd targets a task whose isDeleted flag is set. Deleted tasks remain momentarily visible before cleanup, and any claim/complete/resolve attempt on them is rejected.

Solutions

  1. Catch FlowableException and treat the deleted task as a cancelled work item — notify the user instead of retrying.
  2. Re-query the task before submission to confirm it is still active: taskService.createTaskQuery().taskId(id).singleResult().
  3. Avoid deleting process instances while users hold open task forms, or handle the resulting exception in the UI.
  4. Wrap task mutations in OptimisticLocking/FlowableException handling that re-reads current task state.

Example fix

// before
taskService.complete(taskId); // may throw: Task ... is already deleted
// after
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task != null && !task.isDeleted()) {
    taskService.complete(taskId);
} else {
    // treat as cancelled work item
}
Defensive patterns

Strategy: try-catch

Validate before calling

Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null || task.isDeleted()) { throw new IllegalStateException("Task no longer active"); }

Try / catch

try {
    taskService.complete(taskId);
} catch (FlowableException e) {
    if (e.getMessage().endsWith("is already deleted")) {
        notifyUserTaskCancelled(taskId);
    } else { throw e; }
}

Prevention

When it happens

Trigger: Calling TaskService.claim/complete/setAssignee on a task that was deleted (e.g. its execution ended or the task was removed) but whose row still carries the deleted flag.

Common situations: A parallel branch of the process cancelled (terminated) the task while a user was acting on it; race between task deletion (process end/termination) and user submission; cascading delete via process instance deletion while forms were open.

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/733e8ce19e8bf80f. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/NeedsActiveTaskCmd.java:57

        this.taskId = taskId;
    }

    @Override
    public T execute(CommandContext commandContext) {

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

        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
        TaskEntity task = processEngineConfiguration.getTaskServiceConfiguration().getTaskService().getTask(taskId);

        if (task == null) {
            throw new FlowableObjectNotFoundException("Cannot find task with id " + taskId, Task.class);
        }

        if (task.isDeleted()) {
            throw new FlowableException(task + " is already deleted");
        }

        if (task.isSuspended()) {
            throw new FlowableException(getSuspendedTaskExceptionPrefix() + " a suspended " + task);
        }

        return execute(commandContext, task);
    }

    /**
     * Subclasses must implement in this method their normal command logic. The provided task is ensured to be active.
     */
    protected abstract T execute(CommandContext commandContext, TaskEntity task);

    /**
     * Subclasses can override this method to provide a customized exception message that will be thrown when the task is suspended.
     */
    protected String getSuspendedTaskExceptionPrefix() {

View on GitHub (pinned to d6d39ce1c6)