flowable/flowable-engine · error · FlowableTaskAlreadyClaimedException

Task '

Error message

Task '

What it means

While claiming a task, ClaimTaskCmd detects the task already has an assignee different from the claiming user and throws FlowableTaskAlreadyClaimedException (message begins with "Task '"). This enforces single-owner semantics: only the current assignee may re-claim (which is a no-op) and others are rejected.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/ClaimTaskCmd.java:51

    public ClaimTaskCmd(String taskId, String userId) {
        super(taskId);
        this.userId = userId;
    }

    @Override
    protected Void execute(CommandContext commandContext, TaskEntity task) {
        CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext);
        if (userId != null) {
            Clock clock = cmmnEngineConfiguration.getClock();
            task.setClaimTime(clock.getCurrentTime());
            task.setClaimedBy(userId);
            task.setState(Task.CLAIMED);

            if (task.getAssignee() != null) {
                if (!task.getAssignee().equals(userId)) {
                    // When the task is already claimed by another user, throw
                    // exception. Otherwise, ignore this, post-conditions of method already met.
                    throw new FlowableTaskAlreadyClaimedException(task.getId(), task.getAssignee());
                }
                cmmnEngineConfiguration.getCmmnHistoryManager().recordTaskInfoChange(task, clock.getCurrentTime());
                
            } else {
                TaskHelper.changeTaskAssignee(task, userId, cmmnEngineConfiguration);
                
                if (cmmnEngineConfiguration.getHumanTaskStateInterceptor() != null) {
                    cmmnEngineConfiguration.getHumanTaskStateInterceptor().handleClaim(task, userId);
                }
            }
            
        } else {
            if (task.getAssignee() != null) {
                // Task claim time should be null
                task.setClaimTime(null);
                task.setClaimedBy(null);
                
                if (task.getInProgressStartTime() != null) {

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Catch FlowableTaskAlreadyClaimedException and treat it as "already claimed by someone else" in the UI/business flow.
  2. Check the assignee first: Task task = cmmnTaskService.createTaskQuery().taskId(id).singleResult(); skip claim if task.getAssignee() != null.
  3. If the previous claim is stale, have the current assignee unclaim (setAssignee(null)) or an admin reassign before claiming.
  4. If the same user should be idempotently allowed, note that claiming with the same userId is silently ignored — only differing users throw.

Example fix

// before
cmmnTaskService.claim(taskId, userId);

// after
try {
    cmmnTaskService.claim(taskId, userId);
} catch (FlowableTaskAlreadyClaimedException e) {
    logger.info("Task " + taskId + " already claimed by another user");
}
Defensive patterns

Strategy: try-catch

Validate before calling

Task t = cmmnTaskService.createTaskQuery().taskId(taskId).singleResult();
boolean claimable = t != null && t.getAssignee() == null;
if (!claimable) { /* skip or notify */ }

Try / catch

try { cmmnTaskService.claim(taskId, userId); } catch (FlowableTaskAlreadyClaimedException e) { notifyAlreadyClaimed(e.getTaskId(), e.getAssignee()); }

Prevention

When it happens

Trigger: Calling cmmnTaskService.claim(taskId, userId) on a task whose task.getAssignee() is non-null and != userId, i.e. a second user claiming an already-claimed CMMN standalone task.

Common situations: Two agents/users clicking "claim" concurrently; retrying a claim from a different service account after the first succeeded; UI state that did not refresh assignee status.

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