jeecgboot/JeecgBoot · error · JeecgBootException
后台找不到该类名:${jobClassName}
Error message
后台找不到该类名:${jobClassName} What it means
Thrown from the generic catch(Exception) block in QuartzJobServiceImpl.schedulerAdd when loading or instantiating a Quartz job class fails with a checked exception (e.g. ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException) that is NOT a SchedulerException and NOT a RuntimeException. It signals that the target job class could not be located or instantiated by Thread.currentThread().getContextClassLoader(). Note: the IllegalArgumentException guards from getClass() (errors 162/163) are RuntimeExceptions, so they are caught by the earlier catch(RuntimeException) block and do NOT reach this message.
Source
Thrown at jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/quartz/service/impl/QuartzJobServiceImpl.java:157
// 启动调度器
scheduler.start();
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(id).usingJobData("parameter", parameter).build();
// 表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(id).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
throw new JeecgBootException("创建定时任务失败", e);
} catch (RuntimeException e) {
throw new JeecgBootException(e.getMessage(), e);
}catch (Exception e) {
throw new JeecgBootException("后台找不到该类名:" + jobClassName, e);
}
}
/**
* 删除定时任务
*
* @param id
*/
private void schedulerDelete(String id) {
try {
scheduler.pauseTrigger(TriggerKey.triggerKey(id));
scheduler.unscheduleJob(TriggerKey.triggerKey(id));
scheduler.deleteJob(JobKey.jobKey(id));
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new JeecgBootException("删除定时任务失败");
}
}View on GitHub (pinned to 96fb33f5ec)
Solutions
- Verify the exact FQN in the sys_quartz_job row exists in the deployed jar (jar tf <artifact> | grep <ClassName>).
- Ensure the class is under the org.jeecg.* whitelist, implements org.quartz.Job, and has a public no-arg constructor.
- Confirm the module containing the job is on the classpath of jeecg-system-start (the artifact you actually run).
- After a rename/refactor, update existing sys_quartz_job rows to the new FQN.
Example fix
// before: job class has only a parametrized constructor
public class MyJob implements Job {
public MyJob(String cfg) { ... }
}
// after: add an explicit no-arg constructor so newInstance() succeeds
public class MyJob implements Job {
public MyJob() { this("default"); }
public MyJob(String cfg) { ... }
} Defensive patterns
Strategy: validation
Validate before calling
// Validate the job class is loadable, whitelisted, Job-implementing, and instantiable BEFORE scheduling.
private static void assertJobClassSchedulable(String jobClassName) throws ClassNotFoundException {
if (jobClassName == null || !jobClassName.startsWith("org.jeecg.")) {
throw new IllegalArgumentException("jobClassName 必须以 org.jeecg. 开头");
}
Class<?> c = Class.forName(jobClassName, false,
Thread.currentThread().getContextClassLoader());
if (!org.quartz.Job.class.isAssignableFrom(c)) {
throw new IllegalArgumentException(jobClassName + " 未实现 org.quartz.Job");
}
try { c.getDeclaredConstructor(); }
catch (NoSuchMethodException ex) {
throw new IllegalArgumentException(jobClassName + " 缺少无参构造器");
}
}
// call before schedulerAdd:
assertJobClassSchedulable(jobClassName); Type guard
public static boolean isSchedulableJobClass(String name) {
if (name == null || !name.startsWith("org.jeecg.")) return false;
try {
Class<?> c = Class.forName(name, false, Thread.currentThread().getContextClassLoader());
return org.quartz.Job.class.isAssignableFrom(c)
&& java.lang.reflect.Modifier.isPublic(c.getModifiers())
&& !java.lang.reflect.Modifier.isAbstract(c.getModifiers());
} catch (Throwable t) { return false; }
} Try / catch
try {
quartzJobService.schedulerAdd(...);
} catch (JeecgBootException e) {
// e.getMessage() contains the jobClassName; check classpath/constructor
log.error("调度任务创建失败, jobClassName={}", jobClassName, e);
return Result.error("任务类加载失败, 请检查类名与部署包: " + jobClassName);
} Prevention
- Store job class names as constants or an enum rather than free text to avoid typos.
- Add a startup health check that validates every sys_quartz_job.job_class_name is loadable in the current artifact.
- After any rename/refactor, grep sys_quartz_job for stale FQNs and update them.
- Keep job classes in the same module that is packaged into the runnable artifact.
When it happens
Trigger: POSTing a new scheduled job (e.g. /sys/quartzJob/add) with a jobClassName that (a) does not exist on the runtime classpath, (b) is outside the reach of the context classloader, (c) lacks a public no-arg constructor (getDeclaredConstructor().newInstance() throws), or (d) is abstract/an interface that passed the whitelist but cannot be instantiated.
Common situations: Typo in the class FQN stored in sys_quartz_job; the job class lives in a module not packaged into the deployed jeecg-system-start artifact; after a refactor the FQN changed but the DB row still references the old name; running in microservices/cloud mode where the class is in a different service; fat-jar deployment where the context classloader differs from the system loader.
Related errors
- 非法的任务类名:${classname},仅允许 org.jeecg 包下的Job类
- 创建定时任务失败
- 删除定时任务失败
- 非法的任务类:${classname},必须实现 org.quartz.Job 接口
- ${e.getMessage()}
AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14).
Data as JSON: /api/errors/d1b5838186bf0fb6.
Report an issue: GitHub.