flowable/flowable-engine · error · FlowableIllegalArgumentException

deadLetterJobIds are null

Error message

deadLetterJobIds are null

What it means

Thrown by BulkMoveDeadLetterJobsToHistoryJobsCmd.execute() when deadLetterJobIds is null. This command bulk-moves dead letter jobs to history (removing runtime dead letter rows); it rejects a null id collection before running the query, treating it as a programming error rather than an empty batch.

Solutions

  1. Initialize the id list to an empty collection so the call is a safe no-op
  2. Null-check before invoking the bulk history move
  3. Fix the producer that should return an empty list instead of null

Example fix

// before
List<String> ids = fetchDeadLetterJobIds(); // may return null
jobService.moveDeadLetterJobsToHistoryJobs(ids);
// after
List<String> ids = fetchDeadLetterJobIds();
jobService.moveDeadLetterJobsToHistoryJobs(ids != null ? ids : Collections.emptyList());
Defensive patterns

Strategy: validation

Validate before calling

List<String> ids = fetched != null ? fetched : Collections.emptyList();

Type guard

boolean isUsableIdList(List<String> ids) { return ids != null; }

Try / catch

try {
    jobService.moveDeadLetterJobsToHistoryJobs(deadLetterJobIds);
} catch (FlowableIllegalArgumentException e) {
    LOGGER.error("Bulk history move rejected: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Calling the history-move bulk API (moveDeadLetterJobsToHistoryJobs-style management call) with a null list; deserialization producing a null ids array; a variable initialized as null and only assigned conditionally.

Common situations: Nightly cleanup job whose id list source returned null; API/REST layer not setting the ids property; migration code assuming a default empty list.

Related errors


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

Appendix: source

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

public class BulkMoveDeadLetterJobsToHistoryJobsCmd implements Command<Void> {

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

    protected JobServiceConfiguration jobServiceConfiguration;

    protected Collection<String> deadLetterJobIds;
    protected int retries;

    public BulkMoveDeadLetterJobsToHistoryJobsCmd(Collection<String> deadLetterJobIds, int retries, JobServiceConfiguration jobServiceConfiguration) {
        this.deadLetterJobIds = deadLetterJobIds;
        this.retries = retries;
        this.jobServiceConfiguration = jobServiceConfiguration;
    }

    @Override
    public Void execute(CommandContext commandContext) {
        if (deadLetterJobIds == null) {
            throw new FlowableIllegalArgumentException("deadLetterJobIds are null");
        }
        DeadLetterJobQueryImpl query = new DeadLetterJobQueryImpl(commandContext, jobServiceConfiguration);
        query.jobIds(deadLetterJobIds);
        List<Job> deadLetterJobs = jobServiceConfiguration.getDeadLetterJobEntityManager().findJobsByQueryCriteria(query);
        for (Job job : deadLetterJobs) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Moving deadletter job to history job table {}", job.getId());
            }
            jobServiceConfiguration.getJobManager().moveDeadLetterJobToHistoryJob((DeadLetterJobEntity) job, retries);
        }
        return null;
    }

}

View on GitHub (pinned to d6d39ce1c6)