flowable/flowable-engine · error · FlowableException
Process instance '" + processInstanceId + "' is already clai
Error message
Process instance '" + processInstanceId + "' is already claimed."
What it means
Flowable throws this from ProcessInstanceClaimCmd.execute() when a user attempts to claim a process instance that already has an ASSIGNEE identity link. Claiming is exclusive: only one assignee can own a process instance at a time, so the command first loads all identity links for the instance and rejects the claim if an ASSIGNEE link already exists.
Source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/ProcessInstanceClaimCmd.java:70
@Override
public Void execute(CommandContext commandContext) {
ExecutionEntityManager executionManager = CommandContextUtil.getExecutionEntityManager(commandContext);
ExecutionEntity processInstance = executionManager.findById(processInstanceId);
if (processInstance == null) {
throw new FlowableObjectNotFoundException("No process instance found for id = '" + processInstanceId + "'.", ProcessInstance.class);
} else if (!processInstance.isProcessInstanceType()) {
throw new FlowableIllegalArgumentException("A process instance id is required, but the provided id " + "'" + processInstanceId + "' " + "points to a child execution of process instance " + "'"
+ processInstance.getProcessInstanceId() + "'. " + "Please invoke the " + getClass().getSimpleName() + " with a root execution id.");
}
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
if (userId != null) {
List<IdentityLinkEntity> identityLinks = processEngineConfiguration.getIdentityLinkServiceConfiguration()
.getIdentityLinkService().findIdentityLinksByProcessInstanceId(processInstanceId);
for (IdentityLinkEntity identityLink : identityLinks) {
if (IdentityLinkType.ASSIGNEE.equals(identityLink.getType())) {
throw new FlowableException("Process instance '" + processInstanceId + "' is already claimed.");
}
}
IdentityLinkUtil.createProcessInstanceIdentityLink(processInstance, userId, null, IdentityLinkType.ASSIGNEE);
executionManager.updateProcessInstanceClaimTime(processInstance,
processEngineConfiguration.getClock().getCurrentTime(), userId);
if (processEngineConfiguration.getProcessInstanceStateInterceptor() != null) {
processEngineConfiguration.getProcessInstanceStateInterceptor().handleClaim(processInstance, userId);
}
} else {
IdentityLinkUtil.deleteProcessInstanceIdentityLinks(processInstance, null, null, IdentityLinkType.ASSIGNEE);
executionManager.updateProcessInstanceClaimTime(processInstance, null, null);
if (processEngineConfiguration.getProcessInstanceStateInterceptor() != null) {View on GitHub (pinned to d6d39ce1c6)
Solutions
- Check existing claim state before claiming: query identity links via IdentityLinkService.findIdentityLinksByProcessInstanceId and skip or route to the current assignee.
- Release/unclaim the existing assignee first (delete the ASSIGNEE identity link) then re-claim with the new user.
- Catch FlowableException in the claim path and treat it as a concurrency signal (reload state, notify caller the instance is taken).
- Use optimistic UI refresh / retry with backoff when multiple users claim concurrently.
Example fix
// before
managementService.claimProcessInstance(processInstanceId, userId);
// after
List<IdentityLink> links = managementService.getIdentityLinkService()
.findIdentityLinksByProcessInstanceId(processInstanceId);
boolean claimed = links.stream()
.anyMatch(l -> IdentityLinkType.ASSIGNEE.equals(l.getType()));
if (!claimed) {
managementService.claimProcessInstance(processInstanceId, userId);
} else {
// instance already claimed by another user - handle business case
} Defensive patterns
Strategy: try-catch
Validate before calling
boolean isClaimed(String processInstanceId) {
return runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.variableValueEquals("claimState", "claimed")
.count() > 0; // or inspect identity links via IdentityLinkService
} Try / catch
try {
managementService.claimProcessInstance(processInstanceId, userId);
} catch (FlowableException e) {
if (e.getMessage() != null && e.getMessage().contains("is already claimed")) {
// reload state, notify user instance is taken by another assignee
} else {
throw e;
}
} Prevention
- Query identity links for an existing ASSIGNEE before claiming
- Release the previous claim before re-assigning
- Serialize claims per instance (queue/lock) in high-concurrency UIs
- Refresh claim state in the UI before submit
When it happens
Trigger: Calling managementService.claimProcessInstance(processInstanceId, userId) (via ProcessInstanceClaimCmd) while another user is already recorded as ASSIGNEE for that process instance.
Common situations: Two agents racing to claim the same customer case from a work queue; a stale UI that does not refresh claim state; re-claiming an instance after a previous claim was never released.
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 start process instance with business key ${key}
- Job <jobId> parent is not process instance
- No processDefinitionId, processDefinitionKey nor messageName
- No processDefinitionId, processDefinitionKey provided
- Could not find a scope execution for compensation boundary e
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/df2bedd914a56651.
Report an issue: GitHub.