flowable/flowable-engine · error · FlowableIllegalArgumentException

deadletterJobId is null

Error message

deadletterJobId is null

What it means

MoveDeadLetterJobToHistoryJobCmd.execute() validates deadletterJobId and throws FlowableIllegalArgumentException when it is null. The command moves a dead-letter job to the history job table, which requires the id of the dead-letter job to move.

Solutions

  1. Null-check the dead-letter job id before calling moveDeadLetterJobToHistoryJob.
  2. Re-query the dead-letter job list before each move in loops, since successful moves invalidate stale ids.
  3. Correct the caller/wrapper that forwards null instead of failing earlier.

Example fix

// before
for (Job job : jobs) {
    managementService.moveDeadLetterJobToHistoryJob(job.getId());
}
// after
for (Job job : jobs) {
    if (job != null && job.getId() != null) {
        managementService.moveDeadLetterJobToHistoryJob(job.getId());
    }
}
Defensive patterns

Strategy: validation

Validate before calling

if (deadLetterJobId == null || deadLetterJobId.isEmpty()) throw new IllegalArgumentException("deadLetterJobId required");

Type guard

boolean canMove = deadLetterJob != null && deadLetterJob.getId() != null;

Try / catch

try { managementService.moveDeadLetterJobToHistoryJob(id); } catch (FlowableIllegalArgumentException e) { log.warn("Skipped move: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Calling ManagementService.moveDeadLetterJobToHistoryJob(deadLetterJobId) with a null id, or constructing/executing the command directly without the id.

Common situations: Iterating dead-letter jobs where a prior operation already moved/deleted the job and the stale reference is null; API wrappers forwarding null from optional parameters; copy-paste mistakes where the wrong variable is passed.

Related errors


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

Appendix: source

Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/cmd/MoveDeadLetterJobToHistoryJobCmd.java:50

    private static final long serialVersionUID = 1L;

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

    protected String deadletterJobId;
    protected int retries;
    protected JobServiceConfiguration jobServiceConfiguration;

    public MoveDeadLetterJobToHistoryJobCmd(String deadletterJobId, int retries, JobServiceConfiguration jobServiceConfiguration) {
        this.deadletterJobId = deadletterJobId;
        this.retries = retries;
        this.jobServiceConfiguration = jobServiceConfiguration;
    }

    @Override
    public HistoryJob execute(CommandContext commandContext) {

        if (deadletterJobId == null) {
            throw new FlowableIllegalArgumentException("deadletterJobId is null");
        }

        DeadLetterJobEntity deadLetterJobEntity = jobServiceConfiguration.getDeadLetterJobEntityManager().findById(deadletterJobId);
        if (deadLetterJobEntity == null) {
            throw new JobNotFoundException(deadletterJobId);
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Moving deadletter job to history job table {}", deadLetterJobEntity.getId());
        }

        return jobServiceConfiguration.getJobManager().moveDeadLetterJobToHistoryJob(deadLetterJobEntity, retries);
    }

    public String getDeadletterJobId() {
        return deadletterJobId;
    }

View on GitHub (pinned to d6d39ce1c6)