quarkusio/quarkus · error · IllegalStateException

Cannot load async task class: {taskClass}

Error message

Cannot load async task class: {taskClass}

What it means

Analogous to the sync task class, QuartzSchedulerImpl stores the async task class (Function<ScheduledExecution, Uni<Void>>) FQCN in the JobDataMap and loads it via TCCL when the job fires. If loading fails with ClassNotFoundException it throws 'Cannot load async task class: <name>'. Note the message uses the wrong variable (taskClassStr instead of asyncTaskClassStr), so the printed name may be misleading — inspect the actual JobDataMap entry.

Source

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

        @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);
            }
            this.runOnVirtualThread = Boolean
                    .parseBoolean(jobDetail.getJobDataMap().getString(EXECUTION_METADATA_RUN_ON_VIRTUAL_THREAD));
            this.nonconcurrent = Boolean.parseBoolean(jobDetail.getJobDataMap().getString(EXECUTION_METADATA_NONCONCURRENT));
        }

        @Override
        public Consumer<ScheduledExecution> task() {
            return taskClass != null ? SchedulerUtils.instantiateBeanOrClass(taskClass) : null;

View on GitHub (pinned to e1c734241f)

Solutions

  1. Purge stale Quartz job rows (QRTZ_JOB_DETAILS/QRTZ_TRIGGERS) referencing the old async task class so jobs are recreated from current code
  2. Redeploy/revert the class rename so the stored FQCN resolves on the classpath
  3. Inspect the EXECUTION_METADATA_ASYNC_TASK_CLASS entry in the JobDataMap to see the exact missing class (the error text may show the wrong name due to a message bug)
  4. Verify the class is not excluded from native-image or a different classloader in your runtime setup

Example fix

// before: stale row holds com.acme.OldAsyncTask after rename
// after: clear the stale persisted job and let Quarkus re-register
// SQL: DELETE FROM QRTZ_JOB_DETAILS WHERE JOB_NAME = 'com.acme.OldAsyncTask';
// then restart the application so @Scheduled methods are re-scheduled
Defensive patterns

Strategy: validation

Validate before calling

// Check the async task FQCN stored in the job data map resolves
String async = jobDetail.getJobDataMap().getString("io.quarkus.quartz.async_task_class");
if (async != null) {
    try { Class.forName(async, false, Thread.currentThread().getContextClassLoader()); }
    catch (ClassNotFoundException e) { /* delete stale job row and re-register */ }
}

Try / catch

try {
    executeJob();
} catch (IllegalStateException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Cannot load async task class")) {
        LOG.error("Stored async task class missing; purging stale Quartz rows recommended");
    }
    throw e;
}

Prevention

When it happens

Trigger: A @Scheduled method returning Uni (non-blocking) was persisted to a Quartz job store; on later execution the stored EXECUTION_METADATA_ASYNC_TASK_CLASS FQCN cannot be loaded through the current TCCL.

Common situations: Async scheduled method renamed/refactored/removed after jobs were persisted in a JDBC store; native-image or dev-mode classloader lacking the previously stored class; stale Quartz DB rows from an older deployment version.

Related errors


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