flowable/flowable-engine · error · FlowableIllegalArgumentException

externalJobId must not be empty

Error message

externalJobId must not be empty

What it means

This FlowableIllegalArgumentException is thrown by resolveJob() in AbstractExternalWorkerJobCmd when the externalJobId passed to an external-worker job command (acquire/complete/terminate/fail) is null or empty. Flowable validates command inputs up front before touching the database, so the command fails fast rather than issuing a lookup with a blank identifier. It indicates the calling code never set the external job id on the builder/command.

Source

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

    @Override
    public Void execute(CommandContext commandContext) {
        ExternalWorkerJobEntity externalWorkerJob = resolveJob();
        runJobLogic(externalWorkerJob, commandContext);
        if (externalWorkerJob.isExclusive()) {
            // Part of the same transaction to avoid a race condition with the
            // potentially new jobs (wrt process instance locking) that are created
            // during the execution of the original job
            new UnlockExclusiveJobCmd(externalWorkerJob, jobServiceConfiguration).execute(commandContext);
        }
        return null;
    }

    protected abstract void runJobLogic(ExternalWorkerJobEntity externalWorkerJob, CommandContext commandContext);

    protected ExternalWorkerJobEntity resolveJob() {
        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");
        }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Set the externalJobId on the ExternalWorkerJobCompletionBuilder before executing the command, e.g. externalWorkerJobCompletionBuilder.externalJobId(jobId)
  2. Check where the id originates (message payload, process variable) and log/validate it before calling the Flowable API
  3. If the id is genuinely unknown, re-acquire the job via ExternalWorkerJobAcquireBuilder so a valid id and lock are available

Example fix

// before
externalWorkerJobCompletionBuilder
    .workerId(workerId)
    .complete();
// after
externalWorkerJobCompletionBuilder
    .externalJobId(externalJobId)
    .workerId(workerId)
    .complete();
Defensive patterns

Strategy: validation

Validate before calling

if (externalJobId == null || externalJobId.isEmpty()) {
    throw new IllegalArgumentException("externalJobId must be set before completing the job");
}

Type guard

boolean hasJobId(String id) { return id != null && !id.trim().isEmpty(); }

Try / catch

try {
    completionBuilder.externalJobId(externalJobId).workerId(workerId).complete();
} catch (FlowableIllegalArgumentException e) {
    LOGGER.error("Invalid external worker job command: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Calling ExternalWorkerJobCompletionBuilder-based commands (e.g. completeExternalWorkerJob, terminateExternalWorkerJob, failExternalWorkerJob) without setting externalJobId; passing an empty String from an unmarshalled payload; constructing the command directly with externalJobId == null.

Common situations: Deserializing a worker callback message where the job id field was dropped or empty; copy-pasting completion code that sets workerId but forgets job id; a variable holding the job id resolved to empty string from an unset process variable.

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