xuxueli/xxl-job · error · RuntimeException

xxl-job method-jobhandler destroyMethod invalid, for[{}#{}]

Error message

xxl-job method-jobhandler destroyMethod invalid, for[{}#{}] .

What it means

Thrown by registryJobHandler when @XxlJob(destroy="...") names a method not found on the bean's class (NoSuchMethodException rewrapped). The destroy lifecycle hook must be a declared no-arg method on the same class.

Source

Thrown at xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java:376

        // init and destroy
        Method initMethod = null;
        Method destroyMethod = null;

        if (StringTool.isNotBlank(xxlJob.init())) {
            try {
                initMethod = clazz.getDeclaredMethod(xxlJob.init());
                initMethod.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] .");
            }
        }
        if (StringTool.isNotBlank(xxlJob.destroy())) {
            try {
                destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());
                destroyMethod.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
            }
        }

        // registry jobhandler
        registryJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));

    }


    // ---------------------- job thread repository ----------------------

    private final ConcurrentMap<Integer, JobThread> jobThreadRepository = new ConcurrentHashMap<>();
    public JobThread registJobThread(int jobId, IJobHandler handler, String removeOldReason){
        JobThread newJobThread = new JobThread(jobId, handler);
        newJobThread.start();
        logger.info(">>>>>>>>>>> xxl-job register JobThread success, jobId:{}, handler:{}", new Object[]{jobId, handler});

        JobThread oldJobThread = jobThreadRepository.put(jobId, newJobThread);	// putIfAbsent | oh my god, map's put method return the old value!!!

View on GitHub (pinned to e74c784f68)

Solutions

  1. Declare a no-arg method on the same class matching @XxlJob(destroy=...).
  2. Verify spelling and visibility of the destroy method.
  3. Drop destroy="..." if no teardown is required.

Example fix

// before
@XxlJob(value="x", destroy="cleanup")
public ReturnT<String> execute(String p){...}
// cleanup() missing
// after
@XxlJob(value="x", destroy="cleanup")
public ReturnT<String> execute(String p){...}
public void cleanup(){ /* release */ }
Defensive patterns

Strategy: validation

Validate before calling

if (StringTool.isNotBlank(xxlJob.destroy())) {
    Method dm = clazz.getDeclaredMethod(xxlJob.destroy());
    if (dm.getParameterCount() != 0) throw new IllegalStateException("destroy must be no-arg");
}

Try / catch

try {
    xxlJobExecutor.start();
} catch (RuntimeException e) {
    log.error("handler destroy method invalid: {}", e.getMessage());
}

Prevention

When it happens

Trigger: A handler annotated @XxlJob(value="x", destroy="tearDown") where tearDown is absent, has parameters, sits on a superclass, or was renamed.

Common situations: Renamed the cleanup method without updating the annotation; declared it with arguments; method exists only on a parent class invisible to getDeclaredMethod.

Related errors


AI-assisted analysis of xuxueli/xxl-job@e74c784f68 (2026-08-14). Data as JSON: /api/errors/2b1865187bae5bd1. Report an issue: GitHub.