flowable/flowable-engine · error · FlowableException

{task} is already deleted

Error message

{task} is already deleted

What it means

State check in ActivateTaskCmd.execute: the task was found, but its deletion flag shows it has already been deleted, so (re)activating it is not allowed. The task row still exists (historically) but is no longer a live runtime task.

Source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/ActivateTaskCmd.java:55

        this.userId = userId;
    }

    @Override
    public Void execute(CommandContext commandContext) {
        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
        
        if (taskId == null) {
            throw new FlowableIllegalArgumentException("taskId is null");
        }

        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(task + " is not suspended, so can't be activated");
        }
        
        Clock clock = processEngineConfiguration.getClock();
        Date updateTime = clock.getCurrentTime();
        task.setSuspendedTime(null);
        task.setSuspendedBy(null);
        if (task.getInProgressStartTime() != null) {
            task.setState(Task.IN_PROGRESS);
        } else if (task.getClaimTime() != null) {
            task.setState(Task.CLAIMED);
        } else {
            task.setState(Task.CREATED);
        }
        task.setSuspensionState(SuspensionState.ACTIVE.getStateCode());

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Query the task again before activation and skip if it no longer exists or is deleted.
  2. Handle the exception gracefully for idempotent batch processing (treat 'already deleted' as success/skip).
  3. Remove stale taskId entries from your scheduling store when the process instance is deleted.
  4. Serialize suspension/activation operations for the same process instance to avoid races.

Example fix

// before
taskService.activateTask(taskId); // may hit deleted task
// after
Task t = taskService.createTaskQuery().taskId(taskId).singleResult();
if (t != null && !t.isDeleted() && t.isSuspended()) {
    taskService.activateTask(taskId);
}
Defensive patterns

Strategy: try-catch

Validate before calling

Task t = taskService.createTaskQuery().taskId(taskId).singleResult();
if (t == null || t.isDeleted() || !t.isSuspended()) {
    return;
}

Try / catch

try {
    taskService.activateTask(taskId);
} catch (FlowableException e) {
    if (e.getMessage() != null && e.getMessage().contains("already deleted")) {
        // treat as no-op in idempotent batch
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: taskService.activateTask(id) on a task whose process instance was deleted or whose task was removed concurrently between lookup and activation.

Common situations: Race condition where another thread/user deletes the process instance while a batch job suspends/activates tasks; retry logic re-running activation on already-deleted tasks.

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