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
- Catch FlowableException and treat the deleted task as a cancelled work item — notify the user instead of retrying.
- Re-query the task before submission to confirm it is still active: taskService.createTaskQuery().taskId(id).singleResult().
- Avoid deleting process instances while users hold open task forms, or handle the resulting exception in the UI.
- 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
- Handle task cancellation events so open user forms are invalidated.
- Avoid terminating process instances while users hold open task forms.
- Re-read task state right before mutation.
- Notify users when their task was cancelled by process termination.
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
- Could not find a job with id
- Could not find job for id
- execution + " does not have a subscription to a message…
- A channel key detection value is required for inbound…
- A channel key detection value is required for the channel…
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)