flowable/flowable-engine · error · FlowableForbiddenException

Cannot delete a task that is part of a case instance.

Error message

Cannot delete a task that is part of a case instance.

What it means

The Flowable CMMN REST API forbids deleting a standalone task via the REST endpoint when the task belongs to a running case instance. Task lifecycle in CMMN is owned by the case engine, so direct deletion would corrupt case state. The endpoint throws FlowableForbiddenException (HTTP 403) instead of performing the delete.

Source

Thrown at modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/task/TaskResource.java:160

    @ApiOperation(value = "Delete a task", tags = { "Tasks" }, code = 204)
    @ApiImplicitParams({
            @ApiImplicitParam(name = "cascadeHistory", dataType = "string", value = "Whether or not to delete the HistoricTask instance when deleting the task (if applicable). If not provided, this value defaults to false.", paramType = "query"),
            @ApiImplicitParam(name = "deleteReason", dataType = "string", value = "Reason why the task is deleted. This value is ignored when cascadeHistory is true.", paramType = "query")
    })
    @ApiResponses(value = {
            @ApiResponse(code = 204, message = "Indicates the task was found and has been deleted. Response-body is intentionally empty."),
            @ApiResponse(code = 403, message = "Indicates the requested task cannot be deleted because it’s part of a workflow."),
            @ApiResponse(code = 404, message = "Indicates the requested task was not found.")
    })
    @DeleteMapping(value = "/cmmn-runtime/tasks/{taskId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteTask(@ApiParam(name = "taskId") @PathVariable String taskId, @ApiParam(hidden = true) @RequestParam(value = "cascadeHistory", required = false) Boolean cascadeHistory,
            @ApiParam(hidden = true) @RequestParam(value = "deleteReason", required = false) String deleteReason) {

        Task taskToDelete = getTaskFromRequestWithoutAccessCheck(taskId);
        if (taskToDelete.getScopeId() != null && ScopeTypes.CMMN.equals(taskToDelete.getScopeType())) {
            // Can't delete a task that is part of a case instance
            throw new FlowableForbiddenException("Cannot delete a task that is part of a case instance.");
        } else if (taskToDelete.getExecutionId() != null) {
            throw new FlowableForbiddenException("Cannot delete a task that is part of a process instance.");
        }
        
        if (restApiInterceptor != null) {
            restApiInterceptor.deleteTask(taskToDelete);
        }

        if (cascadeHistory != null) {
            // Ignore delete-reason since the task-history (where the reason is
            // recorded) will be deleted anyway
            taskService.deleteTask(taskToDelete.getId(), cascadeHistory);
        } else {
            // Delete with delete-reason
            taskService.deleteTask(taskToDelete.getId(), deleteReason);
        }
    }
    

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Complete, resolve, delegate, or terminate the task through the case engine instead of deleting it (POST /cmmn-runtime/tasks/{taskId} with action complete/terminate).
  2. Terminate the parent case instance, which removes its tasks, then clean up leftovers.
  3. If the task is truly orphaned, delete the case instance (DELETE /cmmn-runtime/case-instances/{caseInstanceId}) which cascades.
  4. Filter out tasks with scopeType=cmmn before issuing delete calls in batch jobs.

Example fix

// before
curl -X DELETE /flowable-rest/cmmn-runtime/tasks/123
// throws: Cannot delete a task that is part of a case instance.

// after
curl -X POST /flowable-rest/cmmn-runtime/tasks/123 -H 'Content-Type: application/json' -d '{"action":"complete"}'
Defensive patterns

Strategy: try-catch

Validate before calling

const task = await api.get(`/cmmn-runtime/tasks/${id}`);
if (task.scopeType === 'cmmn' && task.scopeId) throw new Error('task belongs to a case instance; use case actions instead');

Type guard

function isCaseTask(t) { return !!t.scopeId && t.scopeType === 'cmmn'; }

Try / catch

try { await api.delete(`/cmmn-runtime/tasks/${id}`); } catch (e) { if (e.response && e.response.status === 403) { /* complete or terminate the case task instead */ } else { throw e; } }

Prevention

When it happens

Trigger: Calling DELETE /cmmn-runtime/tasks/{taskId} where the task has a non-null scopeId with scopeType 'cmmn' (i.e. it was created as part of a case instance).

Common situations: Admin cleanup scripts that delete stale tasks; deleting a task created by a human task / case task stage in a case definition; scripts written for standalone tasks reused against case tasks.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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