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

  1. Fetch and null-check the DeadLetterJobEntity (e.g. via managementService) before calling the move
  2. Return a clear 'job not found' message to the user when the lookup returns null
  3. 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

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


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)