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
- 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
- Restore/redeploy the class or revert the rename/move so the stored FQCN resolves again
- Inspect the stored task class name in the JobDataMap and compare it with classes actually on the classpath
- 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 renaming/moving @Scheduled classes, purge Quartz DB tables or bump store config
- Prefer memory store in environments rebuilt from code each deploy
- Keep the class FQCN stable once jobs are persisted in a JDBC store
- Verify class presence in native-image if running native
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
- Cannot load async 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/63df5dde402765a8.
Report an issue: GitHub.