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
- 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.
- If this surfaces from the default job executor, upgrade the engine — this indicates an internal race fixed in later versions.
- In custom code, guard the argument: only create UnlockExclusiveJobCmd when the JobEntity is non-null.
- 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
- Check job existence before constructing unlock commands
- Avoid manual UnlockExclusiveJobCmd usage unless implementing custom job execution
- Keep the engine version consistent across modules to avoid internal races
- Log job lifecycle (acquire/delete/unlock) when debugging job executor races
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
- Cannot delete job when the job is being executed. Try again…
- job is null
- activatedBefore is null
- activity tenant id is null
- activity tenant id is null
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)