flowable/flowable-engine · error · ActivitiIllegalArgumentException

job is null

Error message

job is null

What it means

Thrown by UnlockExclusiveJobCmd.execute() as ActivitiIllegalArgumentException when the command is constructed with a null job reference. The command exists to release the exclusive-lock held by an exclusive job, and without a job object it has nothing to unlock, so it fails fast.

Solutions

  1. Ensure the job is loaded from the DB (managementService.createJobQuery().jobId(id).singleResult()) before constructing the unlock command; skip unlocking if it no longer exists.
  2. If this surfaces from the default job executor, upgrade the engine — this indicates an internal race fixed in later versions.
  3. In custom code, guard the argument: only create UnlockExclusiveJobCmd when the JobEntity is non-null.
  4. Check for concurrent job deletion (timeout handlers deleting a job while it executes) and serialize those operations.

Example fix

// before
commandExecutor.execute(new UnlockExclusiveJobCmd(job)); // job may be null
// after
if (job != null) {
    commandExecutor.execute(new UnlockExclusiveJobCmd(job));
}
Defensive patterns

Strategy: type-guard

Validate before calling

Job job = managementService.createJobQuery().jobId(jobId).singleResult();
if (job == null) {
    return; // already deleted; nothing to unlock
}

Type guard

boolean isUnlockable(Job job) {
    return job != null && job.getId() != null;
}

Try / catch

try {
    commandExecutor.execute(new UnlockExclusiveJobCmd(job));
} catch (ActivitiIllegalArgumentException e) {
    if ("job is null".equals(e.getMessage())) {
        log.warn("Skipped unlock: job reference was null");
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Internally constructed UnlockExclusiveJobCmd(null) — typically from job executor/acquisition code paths where the job entity failed to load or was already removed before the unlock command is dispatched; direct command use passing a null JobEntity.

Common situations: Race conditions where the job was deleted between acquisition and unlock; custom job executor extensions or tests invoking the command directly; misconfigured job manager wiring passing an unset job variable.

Related errors


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

Appendix: source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/cmd/UnlockExclusiveJobCmd.java:45

 * @author Joram Barrez
 */
public class UnlockExclusiveJobCmd implements Command<Object>, Serializable {

    private static final long serialVersionUID = 1L;

    private static final Logger LOGGER = LoggerFactory.getLogger(UnlockExclusiveJobCmd.class);

    protected JobEntity job;

    public UnlockExclusiveJobCmd(JobEntity job) {
        this.job = job;
    }

    @Override
    public Object execute(CommandContext commandContext) {

        if (job == null) {
            throw new ActivitiIllegalArgumentException("job is null");
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Unlocking exclusive job {}", job.getId());
        }

        if (job.isExclusive()) {
            if (job.getProcessInstanceId() != null) {
                ExecutionEntity execution = commandContext.getExecutionEntityManager().findExecutionById(job.getProcessInstanceId());
                if (execution != null) {
                    commandContext.getExecutionEntityManager().clearProcessInstanceLockTime(execution.getId());
                }
            }
        }

        return null;
    }
}

View on GitHub (pinned to d6d39ce1c6)