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

  1. Pass the same workerId that was used when acquiring/locking the job into the command.
  2. Ensure each worker instance sets a unique, non-empty workerId at startup and carries it through all job commands.
  3. 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

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


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/2453ae26d0f5f642. Report an issue: GitHub.