{"record":{"id":"0080d70781dac959","repo":"quarkusio/quarkus","slug":"extension-passed-an-invalid-shutdown-handler","errorCode":null,"errorMessage":"Extension passed an invalid shutdown handler","messagePattern":"Extension passed an invalid shutdown handler","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"core/runtime/src/main/java/io/quarkus/runtime/StartupContext.java","lineNumber":37,"sourceCode":"\n    // Holds values for returned proxies\n    // These values are usually returned from recorder methods but can be also set explicitly\n    // For example, the raw command line args and ShutdownContext are set when the StartupContext is created\n    private final Map<String, Object> values = new HashMap<>();\n\n    private final Deque<Runnable> shutdownTasks = new ConcurrentLinkedDeque<>();\n    private final Deque<Runnable> lastShutdownTasks = new ConcurrentLinkedDeque<>();\n    private String[] commandLineArgs;\n    private String currentBuildStepName;\n\n    public StartupContext() {\n        ShutdownContext shutdownContext = new ShutdownContext() {\n            @Override\n            public void addShutdownTask(Runnable runnable) {\n                if (runnable != null) {\n                    shutdownTasks.addFirst(runnable);\n                } else {\n                    throw new IllegalArgumentException(\"Extension passed an invalid shutdown handler\");\n                }\n            }\n\n            @Override\n            public void addLastShutdownTask(Runnable runnable) {\n                if (runnable != null) {\n                    lastShutdownTasks.addFirst(runnable);\n                } else {\n                    throw new IllegalArgumentException(\"Extension passed an invalid last shutdown handler\");\n                }\n            }\n        };\n        values.put(ShutdownContext.class.getName(), shutdownContext);\n        values.put(RAW_COMMAND_LINE_ARGS, new Supplier<String[]>() {\n            @Override\n            public String[] get() {\n                if (commandLineArgs == null) {\n                    throw new RuntimeException(\"Command line arguments not available during static init\");","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/core/runtime/src/main/java/io/quarkus/runtime/StartupContext.java#L19-L55","documentation":"StartupContext's ShutdownContext.addShutdownTask throws IllegalArgumentException when a null Runnable is passed, because a null shutdown task is always an extension bug — there is nothing to run at shutdown.","triggerScenarios":"Calling shutdownContext.addShutdownTask(null), typically when a lazily-computed Runnable evaluates to null (e.g. an optional resource, a config-conditional task).","commonSituations":"Extension recorders wiring shutdown tasks whose supplier returns null when a feature is disabled; refactoring that made a task conditional without guarding the call.","solutions":["Guard the call site: only invoke addShutdownTask when the Runnable is non-null.","Fix the task supplier so it returns a no-op Runnable instead of null.","Check which extension passes the null task by inspecting the startup/shutdown stack trace."],"exampleFix":"// before\nshutdownContext.addShutdownTask(maybeTask());\n// after\nRunnable t = maybeTask();\nif (t != null) { shutdownContext.addShutdownTask(t); }","handlingStrategy":"validation","validationCode":"if (runnable != null) { shutdownContext.addShutdownTask(runnable); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Null-check tasks before registering","Return no-op Runnables from optional task factories instead of null","Audit recorders for conditional shutdown task wiring"],"tags":["quarkus","shutdown","null-argument","extension"],"backgroundTag":"null-argument","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}