flowable/flowable-engine · error · FlowableIllegalArgumentException
deadLetterJobIds are null
Error message
deadLetterJobIds are null
What it means
Thrown by BulkMoveDeadLetterJobsCmd.execute() when the deadLetterJobIds collection is null. Flowable distinguishes null (invalid input) from empty (valid but no-op) collections; a null collection cannot back the jobIds query filter, so the command rejects it immediately.
Solutions
- Pass a non-null collection, even an empty one, to the bulk move API
- Guard the call site: if (ids != null) managementService.moveDeadLetterJobs(ids)
- Null-coalesce at the call: moveDeadLetterJobs(ids == null ? Collections.emptyList() : ids)
Example fix
// before
jobService.moveDeadLetterJobs(failedJobIds); // may be null
// after
if (failedJobIds != null) {
jobService.moveDeadLetterJobs(failedJobIds);
} Defensive patterns
Strategy: validation
Validate before calling
if (deadLetterJobIds == null) {
throw new IllegalArgumentException("deadLetterJobIds must be non-null (use an empty list for no-op)");
} Type guard
boolean hasIds(List<String> ids) { return ids != null; } Try / catch
try {
jobService.moveDeadLetterJobs(deadLetterJobIds);
} catch (FlowableIllegalArgumentException e) {
LOGGER.error("Bulk move rejected: {}", e.getMessage());
} Prevention
- Initialize id collections to empty lists, never null
- Null-check collections returned from queries before passing them on
- Wrap the bulk move call in a validation helper
When it happens
Trigger: managementService.moveDeadLetterJobs(...) (bulk move) called with a null id list; building the id list from a stream/collection that was null; passing an optional collection without a null check.
Common situations: Collecting failed job ids from a query that returned null instead of a list; API callers omitting the ids field which binds to null; refactor merging two id sources where one is null.
Related errors
- deadLetterJobIds are null
- jobId is null
- A channel key detection value is required for the channel…
- A group or a user is required to create an identity link.
- A group or a user is required to create an identity link.
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/ccc48abdf4bbdc66.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/cmd/BulkMoveDeadLetterJobsCmd.java:50
public class BulkMoveDeadLetterJobsCmd implements Command<Void> {
private static final Logger LOGGER = LoggerFactory.getLogger(BulkMoveDeadLetterJobsCmd.class);
protected JobServiceConfiguration jobServiceConfiguration;
protected Collection<String> deadLetterJobIds;
protected int retries;
public BulkMoveDeadLetterJobsCmd(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 (HistoryJobEntity.HISTORY_JOB_TYPE.equals(job.getJobType())) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Moving deadletter job to history job table {}", job.getId());
}
jobServiceConfiguration.getJobManager().moveDeadLetterJobToHistoryJob((DeadLetterJobEntity) job, retries);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Moving deadletter job to executable job table {}", job.getId());
}
jobServiceConfiguration.getJobManager().moveDeadLetterJobToExecutableJob((DeadLetterJobEntity) job, retries);
}View on GitHub (pinned to d6d39ce1c6)