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

  1. Verify the exact FQN in the sys_quartz_job row exists in the deployed jar (jar tf <artifact> | grep <ClassName>).
  2. Ensure the class is under the org.jeecg.* whitelist, implements org.quartz.Job, and has a public no-arg constructor.
  3. Confirm the module containing the job is on the classpath of jeecg-system-start (the artifact you actually run).
  4. 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

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


AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14). Data as JSON: /api/errors/d1b5838186bf0fb6. Report an issue: GitHub.