flowable/flowable-engine · error · FlowableException
is locked with a different worker id
Error message
${jobEntity} is locked with a different worker id What it means
UnacquireExternalWorkerJobCmd.execute throws this FlowableException when the lock owner (LOCK_OWNER_ column) of the external worker job does not equal the workerId passed to the command. The message stringifies the job entity. This prevents one worker from releasing a job that another worker currently owns.
Solutions
- Pass the exact worker id that acquired the job (JobEntity.getLockOwner()) to unacquire
- Give each worker instance a unique, persistent worker id in configuration
- Catch FlowableException and skip release if another worker owns the lock
- Keep task processing within the lock duration to avoid re-acquisition races
Example fix
// before jobService.unacquireExternalWorkerJob(job.getId(), "worker-2"); // job locked by worker-1 // after jobService.unacquireExternalWorkerJob(job.getId(), job.getLockOwner());
Defensive patterns
Strategy: try-catch
Validate before calling
ExternalWorkerJobEntity job = externalWorkerJobService.createExternalWorkerJobQuery().jobId(jobId).singleResult();
if (job != null && workerId.equals(job.getLockOwner())) {
externalWorkerJobService.unacquireExternalWorkerJob(jobId, workerId);
} Try / catch
try {
externalWorkerJobService.unacquireExternalWorkerJob(jobId, workerId);
} catch (FlowableException e) {
logger.info("Job {} locked by another worker; skipping unacquire", jobId);
} Prevention
- Unacquire with the same worker id that acquired the job
- Give each worker instance a unique, stable worker id
- Handle lock-expiry races by re-checking ownership before release
When it happens
Trigger: Calling unacquireExternalWorkerJob(jobId, otherWorkerId) where the job was acquired by a different worker id; reusing the same worker id configured differently across instances; the job was re-acquired by another worker after lock expiry while the first worker still tries to release it.
Common situations: Multiple worker instances sharing a database but configured with the same/default worker id then one restarts with a new id; load-balanced handlers releasing each other's jobs; lock expiry races under long-running task handling.
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
- at least one of userId or groups must be provided
- Cannot combine onlyBpmn() with onlyCmmn() in the same query
- Cannot combine onlyCmmn() with onlyBpmn() in the same query
- Cannot combine onlyExternalWorkers() with onlyMessages() in…
- Cannot combine onlyMessages() with onlyExternalWorkers() in…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/ef8a7053b2e6011d.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/cmd/UnacquireExternalWorkerJobCmd.java:54
@Override
public Void execute(CommandContext commandContext) {
if (StringUtils.isEmpty(jobId)) {
throw new FlowableIllegalArgumentException("job id must not be empty");
}
if (StringUtils.isEmpty(workerId)) {
throw new FlowableIllegalArgumentException("worker id must not be empty");
}
ExternalWorkerJobEntityManager externalWorkerJobEntityManager = jobServiceConfiguration.getExternalWorkerJobEntityManager();
ExternalWorkerJobEntity jobEntity = externalWorkerJobEntityManager.findById(jobId);
if (jobEntity == null) {
throw new FlowableException("Could not find job for id " + jobId);
}
if (!jobEntity.getLockOwner().equals(workerId)) {
throw new FlowableException(jobEntity + " is locked with a different worker id");
}
jobEntity.setLockExpirationTime(null);
jobEntity.setLockOwner(null);
externalWorkerJobEntityManager.update(jobEntity);
if (jobEntity.isExclusive()) {
new UnlockExclusiveJobCmd(jobEntity, jobServiceConfiguration).execute(commandContext);
}
return null;
}
}
View on GitHub (pinned to d6d39ce1c6)