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
- Purge stale Quartz job rows (QRTZ_JOB_DETAILS/QRTZ_TRIGGERS) referencing the old async task class so jobs are recreated from current code
- Redeploy/revert the class rename so the stored FQCN resolves on the classpath
- 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)
- 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
- Don't rename Uni-returning @Scheduled methods while using a persistent job store
- Purge QRTZ tables across deployments that change scheduled method signatures
- Pin datasource/store-type per environment to avoid leftover rows
- Test scheduled jobs after every refactor in dev mode with a clean DB
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
- Cannot load task class: {taskClass}
- Cannot load skip predicate class: {taskClass}
- Thread pool class not found: ${threadPoolClass}
- Unable to load handled exception type ${i.getProvidedType()}
- Unable to load type: ${name}
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/70b1bb391e752c1a.
Report an issue: GitHub.