flowable/flowable-engine · error · FlowableIllegalArgumentException
${workerId} does not hold a lock on the requested job
Error message
${workerId} does not hold a lock on the requested job What it means
This FlowableIllegalArgumentException is thrown by AbstractExternalWorkerJobCmd.resolveJob when the job exists but its lock owner does not equal the workerId supplied to the command. The engine uses lockOwner to track which external worker acquired the job, so only the acquiring worker id may complete, terminate or otherwise act on the job. It protects against two workers mutating the same job.
Source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/AbstractExternalWorkerJobCmd.java:91
if (StringUtils.isEmpty(externalJobId)) {
throw new FlowableIllegalArgumentException("externalJobId must not be empty");
}
if (StringUtils.isEmpty(workerId)) {
throw new FlowableIllegalArgumentException("workerId must not be empty");
}
CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext);
ExternalWorkerJobEntityManager externalWorkerJobEntityManager = cmmnEngineConfiguration.getJobServiceConfiguration().getExternalWorkerJobEntityManager();
ExternalWorkerJobEntity job = externalWorkerJobEntityManager.findById(externalJobId);
if (job == null) {
throw new FlowableObjectNotFoundException("No External Worker job found for id: " + externalJobId, ExternalWorkerJobEntity.class);
}
if (!Objects.equals(workerId, job.getLockOwner())) {
throw new FlowableIllegalArgumentException(workerId + " does not hold a lock on the requested job");
}
return job;
}
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Use the same workerId value that was passed to acquireExternalWorkerJobs when acting on the job
- Check job.getLockOwner() before executing the command and skip/log when it does not match your workerId
- If the job is locked by a dead worker, wait for lock expiration or have an admin release the lock, then re-acquire
- Standardize workerId configuration across all worker instances handling a given job queue
Example fix
// before
managementService.executeCommand(new ExternalWorkerJobCompleteCmd<>(jobId, "worker-b", variables));
// after
ExternalWorkerJob job = cmmnTaskService.createExternalWorkerJobQueryBuilder().externalWorkerJobId(jobId).singleResult();
if (job != null && "worker-b".equals(job.getLockOwner())) {
managementService.executeCommand(new ExternalWorkerJobCompleteCmd<>(jobId, "worker-b", variables));
} else {
logger.info("Job {} not owned by this worker (lockOwner={})", jobId, job == null ? null : job.getLockOwner());
} Defensive patterns
Strategy: validation
Validate before calling
ExternalWorkerJob job = cmmnTaskService.createExternalWorkerJobQueryBuilder().externalWorkerJobId(jobId).singleResult();
if (job != null && !workerId.equals(job.getLockOwner())) throw new IllegalStateException("Job locked by " + job.getLockOwner()); Try / catch
try { managementService.executeCommand(new ExternalWorkerJobCompleteCmd<>(jobId, workerId, vars)); }
catch (FlowableIllegalArgumentException e) { log.warn("Lock ownership mismatch on job {}: {}", jobId, e.getMessage()); } Prevention
- Keep one immutable workerId per worker instance in config
- Always pass the same workerId used at acquire time to complete/terminate
- Check job.getLockOwner() before acting on acquired ids
- Log lockOwner mismatches to detect fleet configuration drift
When it happens
Trigger: Calling complete/terminate/bpmn-error style external worker commands with a workerId different from the one used during acquire; worker restarted or reconfigured with a new worker id while holding ids of jobs locked under the old id; hardcoded/mismatched workerId in test code.
Common situations: Load-balanced worker fleet where each instance uses a unique workerId but shares acquired job ids via a queue; config change (e.g. workerId derived from hostname) after failover; copy-pasted test code using wrong workerId constant.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- No External Worker job found for id: ${externalJobId}
- Setting variable is not supported for read only delegate exe
- Can only trigger a plan item that is in the ACTIVE state
- Case instance id is null
- Cannot find case instance for id ${caseInstanceId}
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/af6160f3dd7f58cd.
Report an issue: GitHub.