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

  1. Pass the exact worker id that acquired the job (JobEntity.getLockOwner()) to unacquire
  2. Give each worker instance a unique, persistent worker id in configuration
  3. Catch FlowableException and skip release if another worker owns the lock
  4. 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

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


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)