jeecgboot/JeecgBoot · error · IllegalArgumentException

非法的任务类:${classname},必须实现 org.quartz.Job 接口

Error message

非法的任务类:${classname},必须实现 org.quartz.Job 接口

What it means

The second guard in getClass(): after the whitelist passes and Class.forName succeeds, the loaded class must be assignable to org.quartz.Job. This prevents scheduling arbitrary classes (e.g. a service bean or a class implementing a custom interface) that the Quartz scheduler cannot execute.

Source

Thrown at jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/quartz/service/impl/QuartzJobServiceImpl.java:190

			log.error(e.getMessage(), e);
			throw new JeecgBootException("删除定时任务失败");
		}
	}

	/**
	 * 安全加载Job类:仅允许 org.jeecg. 包下的类,且必须实现 org.quartz.Job 接口
	 */
	private static Job getClass(String classname) throws Exception {
		// 包名白名单校验,防止任意类实例化导致RCE
		if (classname == null || !classname.startsWith("org.jeecg.")) {
			throw new IllegalArgumentException("非法的任务类名:" + classname + ",仅允许 org.jeecg 包下的Job类");
		}
		//update-begin---author:scott ---date:20260416  for:【PR#9538】Class.forName使用上下文类加载器,增强部署兼容性-----------
		Class<?> clazz = Class.forName(classname, true, Thread.currentThread().getContextClassLoader());
		//update-end---author:scott ---date:20260416  for:【PR#9538】Class.forName使用上下文类加载器,增强部署兼容性-----------
		// 校验是否实现了 org.quartz.Job 接口
		if (!Job.class.isAssignableFrom(clazz)) {
			throw new IllegalArgumentException("非法的任务类:" + classname + ",必须实现 org.quartz.Job 接口");
		}
		return (Job) clazz.getDeclaredConstructor().newInstance();
	}

}

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. Make the class implement org.quartz.Job (override execute(JobExecutionContext)) or extend a Job base such as QuartzJobBean.
  2. Point sys_quartz_job.job_class_name at the class that already implements Job.

Example fix

// before
public class ReportTask {
    public void run() { ... }
}
// after
public class ReportTask implements org.quartz.Job {
    @Override public void execute(JobExecutionContext ctx) { run(); }
    public void run() { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the class implements Job before scheduling.
Class<?> c = Class.forName(jobClassName);
if (!org.quartz.Job.class.isAssignableFrom(c)) {
    throw new IllegalArgumentException(jobClassName + " 必须实现 org.quartz.Job");
}

Type guard

public static boolean implementsJob(String name) {
    try {
        return org.quartz.Job.class.isAssignableFrom(
            Class.forName(name, false, Thread.currentThread().getContextClassLoader()));
    } catch (Throwable t) { return false; }
}

Try / catch

try {
    quartzJobService.schedulerAdd(job);
} catch (JeecgBootException e) {
    if (e.getMessage() != null && e.getMessage().contains("必须实现 org.quartz.Job")) {
        return Result.error("所选类未实现 Job 接口, 无法调度");
    }
    throw e;
}

Prevention

When it happens

Trigger: A jobClassName that resolves to an org.jeecg.* class which does NOT implement org.quartz.Job — e.g. a plain @Service bean, an entity, or a class implementing only java.lang.Runnable.

Common situations: A developer wrote the job logic as a method on an existing service rather than a dedicated Job class; the class was refactored and the `implements Job` clause dropped; pointing the scheduler at a DTO/entity by mistake.

Related errors


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