flowable/flowable-engine · error · FlowableIllegalArgumentException
workerId must not be empty
Error message
workerId must not be empty
What it means
The same resolveJob validation path also requires a workerId. The workerId identifies which worker holds the lock on the acquired job; without it lock ownership cannot be verified, so a FlowableIllegalArgumentException is thrown.
Source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/AbstractExternalWorkerJobCmd.java:79
}
protected abstract void runJobLogic(ExternalWorkerJobEntity externalWorkerJob, CommandContext commandContext);
protected void moveExternalWorkerJobToExecutableJob(ExternalWorkerJobEntity externalWorkerJob, CommandContext commandContext) {
jobServiceConfiguration.getJobManager().moveExternalWorkerJobToExecutableJob(externalWorkerJob);
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
processEngineConfiguration.getIdentityLinkServiceConfiguration().getIdentityLinkService()
.deleteIdentityLinksByScopeIdAndType(externalWorkerJob.getCorrelationId(), ScopeTypes.EXTERNAL_WORKER);
}
protected ExternalWorkerJobEntity resolveJob(CommandContext commandContext) {
if (StringUtils.isEmpty(externalJobId)) {
throw new FlowableIllegalArgumentException("externalJobId must not be empty");
}
if (StringUtils.isEmpty(workerId)) {
throw new FlowableIllegalArgumentException("workerId must not be empty");
}
ExternalWorkerJobEntityManager externalWorkerJobEntityManager = jobServiceConfiguration.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
- Pass the same workerId that was used when acquiring/locking the job into the command.
- Ensure each worker instance sets a unique, non-empty workerId at startup and carries it through all job commands.
- Add a client-side check that workerId is non-blank before calling the job service.
Example fix
// before externalWorkerJobService.complete(jobId, workerId, variables); // workerId empty // after String workerId = config.getWorkerId(); // e.g. worker-42 set at startup externalWorkerJobService.complete(jobId, workerId, variables);
Defensive patterns
Strategy: validation
Validate before calling
if (workerId == null || workerId.isBlank()) {
throw new IllegalArgumentException("workerId required");
} Prevention
- Assign a unique non-empty workerId at worker startup
- Propagate workerId via a single config object used in all job commands
- Assert workerId presence in integration tests
When it happens
Trigger: Invoking complete/fail/release/unlock commands with workerId null or empty, even when externalJobId is valid.
Common situations: Worker identity not propagated into the command (config lost the workerId); multiple workers sharing a client that hardcodes or drops the worker id; refactoring changed the workerId variable's initialization.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- externalJobId must not be empty
- variableName is null
- caseInstanceId is null
- The case definition id is mandatory, but '' has been provide
- The target tenant id must not be null.
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/2453ae26d0f5f642.
Report an issue: GitHub.