flowable/flowable-engine · error · FlowableException
Empty timer jobs collection can not be scheduled
Error message
Empty timer jobs collection can not be scheduled
What it means
DefaultJobManager.bulkMoveTimerJobsToExecutableJobs moves a batch of timer jobs to the executable job table in one operation. A null or empty list means there is nothing to move, and the method throws this FlowableException as a precondition rather than performing a no-op.
Solutions
- Skip the call when the collection is null or empty: if (jobs == null || jobs.isEmpty()) return;
- Guard in the acquisition layer so empty batches never reach the bulk move
- If you control the query, add due-date criteria so only non-empty batches are produced
Example fix
// before
jobManager.bulkMoveTimerJobsToExecutableJobs(dueTimerJobs);
// after
if (dueTimerJobs != null && !dueTimerJobs.isEmpty()) {
jobManager.bulkMoveTimerJobsToExecutableJobs(dueTimerJobs);
} Defensive patterns
Strategy: validation
Validate before calling
if (timerJobEntities == null || timerJobEntities.isEmpty()) { return; } Type guard
boolean isNonEmptyBatch(List<TimerJobEntity> l) { return l != null && !l.isEmpty(); } Try / catch
try { jobManager.bulkMoveTimerJobsToExecutableJobs(jobs); } catch (FlowableException e) { log.warn("Empty batch passed to bulk move: {}", e.getMessage()); } Prevention
- Skip empty batches before calling bulk APIs
- Check acquisition query results for empty results
- Centralize batch-move logic in one guarded helper
When it happens
Trigger: Calling bulkMoveTimerJobsToExecutableJobs(null) or with an empty List, e.g. forwarding the result of a timer-job query that matched nothing.
Common situations: Batch schedulers that query due timer jobs and always call the bulk move even when zero rows were due; tests or utilities exercising the API with empty inputs.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- at least one of userId or groups must be provided
- callbackIds is null or empty
- callbackIds is null or empty
- Can only move a history job to a history job
- Candidate group list is empty
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/a1a38996e0d5306a.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java:146
if (timerJob == null) {
throw new FlowableException("Empty timer job can not be scheduled");
}
JobEntity executableJob = createExecutableJobFromOtherJob(timerJob);
boolean insertSuccessful = jobServiceConfiguration.getJobEntityManager().insertJobEntity(executableJob);
if (insertSuccessful) {
jobServiceConfiguration.getTimerJobEntityManager().delete(timerJob);
triggerExecutorIfNeeded(executableJob);
return executableJob;
}
return null;
}
@Override
public void bulkMoveTimerJobsToExecutableJobs(List<TimerJobEntity> timerJobEntities) {
if (timerJobEntities == null || timerJobEntities.isEmpty()) {
throw new FlowableException("Empty timer jobs collection can not be scheduled");
}
// Only hint when there is enough capacity remaining in the job queue
boolean remainingCapacitySufficient = isAsyncExecutorRemainingCapacitySufficient(timerJobEntities.size());
for (TimerJobEntity timerJobEntity : timerJobEntities) {
JobEntity executableJob = createExecutableJobFromOtherJob(timerJobEntity, remainingCapacitySufficient);
boolean insertSuccessful = jobServiceConfiguration.getJobEntityManager().insertJobEntity(executableJob);
if (insertSuccessful && remainingCapacitySufficient) {
triggerExecutorIfNeeded(executableJob);
}
}
jobServiceConfiguration.getTimerJobEntityManager().bulkDeleteTimerJobsWithoutRevisionCheck(timerJobEntities);
}
@OverrideView on GitHub (pinned to d6d39ce1c6)