flowable/flowable-engine · error · FlowableIllegalArgumentException

job id must not be empty

Error message

job id must not be empty

What it means

UnacquireExternalWorkerJobCmd.execute throws this FlowableIllegalArgumentException when jobId is null or empty. The command releases a single externally acquired job identified by its id; without a valid job id the release cannot proceed. This is the first check in execute, before the worker id check.

Solutions

  1. Always propagate the JobEntity.getId() from the acquired job into the handler and use it in unacquire
  2. Null/empty-check jobId at the handler boundary before calling unacquire
  3. Ensure the acquired job object is retained (not GC'd/reassigned) through the handler lifecycle

Example fix

// before
externalWorkerJobAcquireService.unacquireExternalWorkerJob(null, workerId);
// after
if (job != null && job.getId() != null) {
    externalWorkerJobAcquireService.unacquireExternalWorkerJob(job.getId(), workerId);
}
Defensive patterns

Strategy: validation

Validate before calling

if (job == null || job.getId() == null || job.getId().isEmpty()) {
    throw new IllegalStateException("Cannot unacquire: job id missing");
}

Prevention

When it happens

Trigger: Calling unacquireExternalWorkerJob(jobId, workerId) with an empty jobId, e.g. the id variable was lost during job handling, or a result handler ran after the job entity was no longer in scope and its id defaulted to null.

Common situations: External worker task handlers storing/parsing the job id incorrectly; passing null after a failed JSON payload extraction; framework code invoking unacquire on shutdown with an unset job id.

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/662d3784e1c624d2. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/cmd/UnacquireExternalWorkerJobCmd.java:39

import org.flowable.job.service.impl.persistence.entity.ExternalWorkerJobEntity;
import org.flowable.job.service.impl.persistence.entity.ExternalWorkerJobEntityManager;

public class UnacquireExternalWorkerJobCmd implements Command<Void> {

    protected final String jobId;
    protected final String workerId;
    protected final JobServiceConfiguration jobServiceConfiguration;

    public UnacquireExternalWorkerJobCmd(String jobId, String workerId, JobServiceConfiguration jobServiceConfiguration) {
        this.jobId = jobId;
        this.workerId = workerId;
        this.jobServiceConfiguration = jobServiceConfiguration;
    }

    @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);

View on GitHub (pinned to d6d39ce1c6)