quarkusio/quarkus · error · IllegalStateException

Cannot load task class: {taskClass}

Error message

Cannot load task class: {taskClass}

What it means

Quarkus stores the fully-qualified name of the task class (the Consumer<ScheduledExecution> implementing a @Scheduled method's body) in the Quartz JobDataMap so persistent job stores can survive restarts. When the job fires, the scheduler loads the class via the thread context classloader; a ClassNotFoundException is converted to 'Cannot load task class: <name>'.

Source

Thrown at extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java:1292

    static class SerializedExecutionMetadata implements ExecutionMetadata {

        private final Class<? extends Consumer<ScheduledExecution>> taskClass;
        private final Class<? extends Function<ScheduledExecution, Uni<Void>>> asyncTaskClass;
        private final boolean runOnVirtualThread;
        private final Class<? extends SkipPredicate> skipPredicateClass;
        private final boolean nonconcurrent;

        @SuppressWarnings("unchecked")
        public SerializedExecutionMetadata(JobDetail jobDetail) {
            ClassLoader tccl = Thread.currentThread().getContextClassLoader();
            String taskClassStr = jobDetail.getJobDataMap().getString(EXECUTION_METADATA_TASK_CLASS);
            try {
                this.taskClass = taskClassStr != null
                        ? (Class<? extends Consumer<ScheduledExecution>>) tccl.loadClass(taskClassStr)
                        : null;
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException("Cannot load task class: " + taskClassStr);
            }
            String asyncTaskClassStr = jobDetail.getJobDataMap().getString(EXECUTION_METADATA_ASYNC_TASK_CLASS);
            try {
                this.asyncTaskClass = asyncTaskClassStr != null
                        ? (Class<? extends Function<ScheduledExecution, Uni<Void>>>) tccl.loadClass(asyncTaskClassStr)
                        : null;
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException("Cannot load async task class: " + taskClassStr);
            }

            String skipPredicateClassStr = jobDetail.getJobDataMap().getString(EXECUTION_METADATA_SKIP_PREDICATE_CLASS);
            try {
                this.skipPredicateClass = skipPredicateClassStr != null
                        ? (Class<? extends SkipPredicate>) tccl.loadClass(skipPredicateClassStr)
                        : null;
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException("Cannot load skip predicate class: " + taskClassStr);
            }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Delete stale job rows for the old class from the Quartz database tables (QRTZ_JOB_DETAILS, QRTZ_TRIGGERS) or change quarkus.quartz.store-type so jobs are rebuilt from code
  2. Restore/redeploy the class or revert the rename/move so the stored FQCN resolves again
  3. Inspect the stored task class name in the JobDataMap and compare it with classes actually on the classpath
  4. Clear the Quartz tables entirely if the store should be rebuilt from @Scheduled annotations on next start

Example fix

// before (class renamed but DB still holds old FQCN)
package com.acme.sched; public class OldJob implements Consumer<ScheduledExecution> {...}
// after
package com.acme.sched; public class RenamedJob implements Consumer<ScheduledExecution> {...}
// plus: DELETE FROM QRTZ_JOB_DETAILS WHERE JOB_CLASS_NAME LIKE '%OldJob%';
Defensive patterns

Strategy: validation

Validate before calling

// Before starting against a persistent store, verify stored job classes resolve
String stored = jobDetail.getJobDataMap().getString("io.quarkus.quartz.task_class");
if (stored != null) {
    try { Class.forName(stored, false, Thread.currentThread().getContextClassLoader()); }
    catch (ClassNotFoundException e) { /* purge stale QRTZ rows before start */ }
}

Try / catch

try {
    job.execute(executionContext);
} catch (IllegalStateException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Cannot load task class")) {
        // rebuild job store from code: purge QRTZ_JOB_DETAILS rows and restart
    }
    throw e;
}

Prevention

When it happens

Trigger: A job persisted in a JDBC/other persistent Quartz store references EXECUTION_METADATA_TASK_CLASS that cannot be loaded through TCCL at execution time.

Common situations: The scheduled method/class was renamed, moved packages, or deleted while old rows remain in the Quartz DB tables (QRTZ_JOB_DETAIL); running with a different classloader setup (dev mode restarts, native image) than the one that stored the job; partially deployed artifacts missing the class.

Related errors


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