{"record":{"id":"127ee67e3f64fe52","repo":"flowable/flowable-engine","slug":"workerid-does-not-hold-a-lock-on-the-requested-j","errorCode":null,"errorMessage":"{workerId} does not hold a lock on the requested job","messagePattern":"(.+?) does not hold a lock on the requested job","errorType":"validation","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/AbstractExternalWorkerJobCmd.java","lineNumber":91,"sourceCode":"    protected ExternalWorkerJobEntity resolveJob(CommandContext commandContext) {\n        if (StringUtils.isEmpty(externalJobId)) {\n            throw new FlowableIllegalArgumentException(\"externalJobId must not be empty\");\n        }\n\n        if (StringUtils.isEmpty(workerId)) {\n            throw new FlowableIllegalArgumentException(\"workerId must not be empty\");\n        }\n\n        ExternalWorkerJobEntityManager externalWorkerJobEntityManager = jobServiceConfiguration.getExternalWorkerJobEntityManager();\n\n        ExternalWorkerJobEntity job = externalWorkerJobEntityManager.findById(externalJobId);\n\n        if (job == null) {\n            throw new FlowableObjectNotFoundException(\"No External Worker job found for id: \" + externalJobId, ExternalWorkerJobEntity.class);\n        }\n\n        if (!Objects.equals(workerId, job.getLockOwner())) {\n            throw new FlowableIllegalArgumentException(workerId + \" does not hold a lock on the requested job\");\n        }\n\n        return job;\n    }\n}\n","sourceCodeStart":73,"sourceCodeEnd":97,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/AbstractExternalWorkerJobCmd.java#L73-L97","documentation":"resolveJob compares the supplied workerId with the job's lockOwner and throws FlowableIllegalArgumentException when they differ. Only the worker that currently holds the lock is allowed to complete, fail, or release the job, preventing one worker from manipulating another worker's job.","triggerScenarios":"Calling complete/fail/release with a workerId different from the one used at acquisition time (job.getLockOwner()), e.g. after the lock expired and was re-acquired by another worker, or with a duplicate/default workerId.","commonSituations":"Multiple workers sharing a hardcoded workerId then colliding on lock ownership; job lock expired and re-acquired by a different instance; a restart generated a new workerId while the client reused an old job reference; load-balanced consumers using per-request ids.","solutions":["Use the exact workerId that acquired the job for all subsequent complete/fail/release calls on it.","Give each worker instance a stable unique workerId (e.g. hostname+PID persisted across restarts).","If the lock was lost, treat the job as no longer owned and re-acquire; optionally catch the exception and refresh the job state."],"exampleFix":"// before\nservice.complete(job.getId(), \"shared-worker\", variables); // lock held by worker-7\n// after\nservice.complete(job.getId(), job.getLockOwner(), variables); // pass acquiring workerId","handlingStrategy":"try-catch","validationCode":"ExternalWorkerJob job = jobService.createExternalWorkerJobQuery().externalJobId(jobId).singleResult();\nif (job != null && !Objects.equals(workerId, job.getLockOwner())) {\n    // lock lost: re-acquire instead of completing\n}","typeGuard":null,"tryCatchPattern":"try {\n    jobService.complete(jobId, workerId, variables);\n} catch (FlowableIllegalArgumentException e) {\n    if (e.getMessage().contains(\"does not hold a lock\")) {\n        // lock expired or lost: re-acquire the job\n    }\n}","preventionTips":["Use a stable unique workerId per worker instance","Complete jobs within the acquisition lock duration","Handle lock-lost as a normal concurrency outcome, not a bug"],"tags":["flowable","external-worker","lock-ownership","concurrency"],"backgroundTag":"invalid-argument-value","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}