quarkusio/quarkus · error · IllegalStateException

Cannot modify a job that was already scheduled

Error message

Cannot modify a job that was already scheduled

What it means

JobDefinition is a fluent, mutable builder whose setters (setCron, setInterval, setDelayed, setConcurrentExecution, setSkipPredicate, setOverdueGracePeriod) are only legal until the job is scheduled. checkScheduled() throws this IllegalStateException once the job has been registered with the scheduler and someone attempts further mutation.

Source

Thrown at extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/AbstractJobDefinition.java:152

    @Override
    public THIS setAsyncTask(Function<ScheduledExecution, Uni<Void>> asyncTask) {
        checkScheduled();
        if (task != null) {
            throw new IllegalStateException("Sync task was already set");
        }
        this.asyncTask = Objects.requireNonNull(asyncTask);
        return self();
    }

    @Override
    public THIS setAsyncTask(Class<? extends Function<ScheduledExecution, Uni<Void>>> asyncTaskClass) {
        this.asyncTaskClass = Objects.requireNonNull(asyncTaskClass);
        return setAsyncTask(SchedulerUtils.instantiateBeanOrClass(asyncTaskClass));
    }

    protected void checkScheduled() {
        if (scheduled) {
            throw new IllegalStateException("Cannot modify a job that was already scheduled");
        }
    }

    @SuppressWarnings("unchecked")
    protected THIS self() {
        return (THIS) this;
    }

}

View on GitHub (pinned to e1c734241f)

Solutions

  1. To change a scheduled job's config, unschedule and create a new JobDefinition via scheduler.newJob(...) with the updated settings, or use the scheduler's schedule/update flow
  2. Treat JobDefinition as write-once: finish all setters before submitting
  3. Do not store and mutate JobDefinition references after scheduling
  4. For dynamic schedules, use a scheduling implementation that supports job updates (e.g. Quartz-based) and its own APIs

Example fix

// before
jobDef.setCron("0 0 12 * * ?"); // job already scheduled -> throws
// after
scheduler.unscheduleJob("j");
scheduler.newJob("j").setCron("0 0 12 * * ?").setTask(task).schedule();
Defensive patterns

Strategy: validation

Validate before calling

if (definition.isScheduled()) { // track via your own wrapper
    throw new IllegalStateException("create a new JobDefinition to change config");
}
definition.setCron(newCron);

Type guard

boolean modifiable(JobDefinition d) {
    return !scheduledJobs.contains(d); // maintain registry of scheduled definitions
}

Try / catch

try {
    definition.setCron(newCron);
} catch (IllegalStateException e) {
    scheduler.unscheduleJob(id);
    scheduler.newJob(id).setCron(newCron).setTask(task).schedule();
}

Prevention

When it happens

Trigger: Calling any setter on a JobDefinition after the definition was scheduled (the scheduled flag is set), e.g. keeping a reference and later calling setCron("0 0 * * * ?") to 'update' the job.

Common situations: Trying to dynamically reschedule a job through the old definition object instead of the scheduler's update API; caching JobDefinition instances in a service and mutating them at runtime; tests reconfiguring an already-submitted job.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/47abf36273d9aafe. Report an issue: GitHub.