apache/shenyu · error · IllegalArgumentException

shutdownHook cannot be NULL

Error message

shutdownHook cannot be NULL

What it means

ShutdownHookManager.addShutdownHook registers a Runnable to run at JVM/client shutdown. It validates arguments first: a null hook is rejected with IllegalArgumentException because HookEntry wrapping and later execution assume a non-null Runnable.

Solutions

  1. Guard before registering: only call addShutdownHook when the Runnable is non-null.
  2. Fix the upstream factory/bean so it returns a no-op hook instead of null.
  3. Log-and-skip null hooks in wrapper code if registration is best-effort.

Example fix

// before
manager.addShutdownHook(buildHook()); // may return null
// after
Runnable hook = buildHook();
if (hook != null) {
    manager.addShutdownHook(hook);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (hook == null) {
    return; // skip registration of absent cleanup
}

Type guard

void safeAddHook(ShutdownHookManager mgr, Runnable hook) {
    Objects.requireNonNullElse(hook, Runnable {}); // or skip when null
    if (hook != null) mgr.addShutdownHook(hook);
}

Try / catch

try {
    manager.addShutdownHook(hook);
} catch (IllegalArgumentException e) {
    LOGGER.warn("skipped null shutdown hook: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Calling ShutdownHookManager.getInstance().addShutdownHook(null), typically when a hook variable comes from an unset config/optional bean or a failed initialization returns null.

Common situations: Conditional resource cleanup code where the resource was never created (so the Runnable is null) and is registered unconditionally; refactoring that moves hook creation behind a nullable factory.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/e8e8836651a48bff. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/shutdown/ShutdownHookManager.java:102

        synchronized (MGR.hooks) {
            list = new ArrayList<>(MGR.hooks);
        }
        list.sort((o1, o2) -> o2.priority - o1.priority);
        List<Runnable> ordered = new ArrayList<>();
        list.forEach(entry -> ordered.add(entry.hook));
        return ordered;
    }

    /**
     * Adds a shutdownHook with default priority zero, the higher the priority
     * the earlier will run. ShutdownHooks with same priority run
     * in a non-deterministic order.
     *
     * @param shutdownHook shutdownHook <code>Runnable</code>
     */
    public void addShutdownHook(final Runnable shutdownHook) {
        if (Objects.isNull(shutdownHook)) {
            throw new IllegalArgumentException("shutdownHook cannot be NULL");
        }
        if (shutdownInProgress.get()) {
            throw new IllegalStateException("Shutdown in progress, cannot add a shutdownHook");
        }
        hooks.add(new HookEntry(shutdownHook, 0));
    }

    /**
     * Adds a shutdownHook with a priority, the higher the priority
     * the earlier will run. ShutdownHooks with same priority run
     * in a non-deterministic order.
     *
     * @param shutdownHook shutdownHook <code>Runnable</code>
     * @param priority     priority of the shutdownHook.
     */
    public void addShutdownHook(final Runnable shutdownHook, final int priority) {
        if (Objects.isNull(shutdownHook)) {
            throw new IllegalArgumentException("shutdownHook cannot be NULL");

View on GitHub (pinned to 567142e072)