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

  1. Skip the call when the collection is null or empty: if (jobs == null || jobs.isEmpty()) return;
  2. Guard in the acquisition layer so empty batches never reach the bulk move
  3. 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

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


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);
    }

    @Override

View on GitHub (pinned to d6d39ce1c6)