flowable/flowable-engine · error · FlowableIllegalArgumentException
Null job provided
Error message
Null job provided
What it means
moveDeadLetterJobToExecutableJob resurrects a DeadLetterJobEntity back to the executable job table with a reset retry count. A null dead-letter job cannot be moved, so the method throws FlowableIllegalArgumentException as a fail-fast precondition.
Solutions
- Fetch and null-check the DeadLetterJobEntity (e.g. via managementService) before calling the move
- Return a clear 'job not found' message to the user when the lookup returns null
- Catch FlowableIllegalArgumentException and map it to a not-found handling path
Example fix
// before
jobManager.moveDeadLetterJobToExecutableJob(deadLetterJob, 3);
// after
if (deadLetterJob != null) {
jobManager.moveDeadLetterJobToExecutableJob(deadLetterJob, 3);
} Defensive patterns
Strategy: validation
Validate before calling
if (deadLetterJobEntity == null) { throw new JobNotFoundException("Dead letter job not found"); } Type guard
boolean canMove(DeadLetterJobEntity d) { return d != null && d.getId() != null; } Try / catch
try { jobManager.moveDeadLetterJobToExecutableJob(job, retries); } catch (FlowableIllegalArgumentException e) { throw new JobNotFoundException("Dead letter job missing: " + e.getMessage()); } Prevention
- Verify the dead-letter job exists before moving
- Handle concurrent deletion of dead-letter jobs
- Null-check admin-console lookups
When it happens
Trigger: Passing null to JobManager.moveDeadLetterJobToExecutableJob(deadLetterJobEntity, retries), typically forwarding the result of a dead-letter-job lookup that found nothing.
Common situations: Admin/retry consoles that look up a dead-letter job by id and call the move without checking for a missing record; jobs concurrently deleted by another node.
Related errors
- Cannot move the job to deadletter: the job is not a timer…
- Empty external worker job can not be scheduled
- Empty timer job can not be scheduled
- activity tenant id is null
- activityId is null
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/fb89e21b0dea38c0.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java:265
} else {
throw new FlowableIllegalArgumentException("Cannot move the job to deadletter: the job is not a timer, async job or external worker job");
}
return deadLetterJob;
}
protected void sendMoveToDeadletterEvent(JobInfo job) {
FlowableEventDispatcher eventDispatcher = jobServiceConfiguration.getEventDispatcher();
if (eventDispatcher != null && eventDispatcher.isEnabled()) {
eventDispatcher.dispatchEvent(FlowableJobEventBuilder.createEntityEvent(
FlowableEngineEventType.JOB_MOVED_TO_DEADLETTER, job), jobServiceConfiguration.getEngineName());
}
}
@Override
public Job moveDeadLetterJobToExecutableJob(DeadLetterJobEntity deadLetterJobEntity, int retries) {
if (deadLetterJobEntity == null) {
throw new FlowableIllegalArgumentException("Null job provided");
}
if (HistoryJobEntity.HISTORY_JOB_TYPE.equals(deadLetterJobEntity.getJobType())) {
throw new FlowableIllegalArgumentException("Cannot move a history job to an executable job");
}
if (Job.JOB_TYPE_EXTERNAL_WORKER.equals(deadLetterJobEntity.getJobType())) {
ExternalWorkerJobEntity externalWorkerJob = createExternalWorkerJobFromOtherJob(deadLetterJobEntity);
externalWorkerJob.setRetries(retries);
boolean insertSuccessful = jobServiceConfiguration.getExternalWorkerJobEntityManager().insertExternalWorkerJobEntity(externalWorkerJob);
if (insertSuccessful) {
jobServiceConfiguration.getDeadLetterJobEntityManager().delete(deadLetterJobEntity);
return externalWorkerJob;
}
} else {
JobEntity executableJob = createExecutableJobFromOtherJob(deadLetterJobEntity);
executableJob.setRetries(retries);
boolean insertSuccessful = jobServiceConfiguration.getJobEntityManager().insertJobEntity(executableJob);View on GitHub (pinned to d6d39ce1c6)