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
- Set the externalJobId on the ExternalWorkerJobCompletionBuilder before executing the command, e.g. externalWorkerJobCompletionBuilder.externalJobId(jobId)
- Check where the id originates (message payload, process variable) and log/validate it before calling the Flowable API
- 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
- Always set externalJobId on the builder before complete/fail/terminate
- Validate ids at the message-queue/consumer boundary before invoking Flowable
- Log the raw payload when an id is missing to trace where it is dropped
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
- workerId must not be empty
- Error retrieving app engine info
- Could not find an app definition with id '<appDefinitionId>
- No action found in request body.
- No deployment id available
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/bdb79567871f05ed.
Report an issue: GitHub.