{"record":{"id":"f145ff7343fabe1f","repo":"apache/pulsar","slug":"class-classname-does-not-implement-additional-s","errorCode":null,"errorMessage":"Class ${className} does not implement additional servlet interface","messagePattern":"Class (.+?) does not implement additional servlet interface","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"pulsar-broker-common/src/main/java/org/apache/pulsar/broker/web/plugin/servlet/AdditionalServletUtils.java","lineNumber":148,"sourceCode":"\n        final File narFile = metadata.getArchivePath().toAbsolutePath().normalize().toFile();\n        NarClassLoader ncl = NarClassLoaderBuilder.builder()\n                .narFile(narFile)\n                .parentClassLoader(AdditionalServlet.class.getClassLoader())\n                .extractionDirectory(narExtractionDirectory)\n                .build();\n\n        AdditionalServletDefinition def = getAdditionalServletDefinition(ncl);\n        if (StringUtils.isBlank(def.getAdditionalServletClass())) {\n            throw new IOException(\"Additional servlets `\" + def.getName() + \"` does NOT provide an \"\n                    + \"additional servlets implementation\");\n        }\n\n        try {\n            Class additionalServletClass = ncl.loadClass(def.getAdditionalServletClass());\n            Object additionalServlet = additionalServletClass.getDeclaredConstructor().newInstance();\n            if (!(additionalServlet instanceof AdditionalServlet)) {\n                throw new IOException(\"Class \" + def.getAdditionalServletClass()\n                        + \" does not implement additional servlet interface\");\n            }\n            AdditionalServlet servlet = (AdditionalServlet) additionalServlet;\n            return new AdditionalServletWithClassLoader(servlet, ncl);\n        } catch (Throwable t) {\n            rethrowIOException(t);\n            return null;\n        }\n    }\n\n    /**\n     * Adapts the servlet instance of an additional servlet to {@code jakarta.servlet.Servlet}, the servlet API\n     * of the single Jetty environment the broker and the proxy run.\n     *\n     * <p>Servlets declaring {@link AdditionalServletType#JAKARTA_SERVLET} are returned as they are. Servlets\n     * declaring {@link AdditionalServletType#JAVAX_SERVLET} implement the legacy {@code javax.servlet.Servlet}\n     * interface and are adapted with the Apache Felix {@link ServletWrapper}. Registering both flavours in the\n     * same environment is what lets every additional servlet go through the broker/proxy filter chain, which is","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/web/plugin/servlet/AdditionalServletUtils.java#L130-L166","documentation":"AdditionalServletUtils.load instantiates the class named in additional_servlet.yml and checks it implements org.apache.pulsar.broker.web.plugin.servlet.AdditionalServlet. If the loaded object is not an AdditionalServlet instance, load() throws this IOException — the descriptor points at a class of the wrong type, so the NAR cannot be used as a servlet plugin.","triggerScenarios":"additional_servlet.yml names a class that implements some other Pulsar plugin interface (e.g. an AuthenticationProvider or a javax/jakarta Servlet directly) instead of AdditionalServlet; a class whose servlet instance field is set but the plugin class itself isn't an AdditionalServlet.","commonSituations":"Migrating a plugin across Pulsar versions where the required base interface changed; copy-pasting a descriptor from another plugin type; building the NAR with the wrong main class; classloader isolation loading a stale/duplicate copy of the interface so instanceof fails.","solutions":["Make the configured class implement org.apache.pulsar.broker.web.plugin.servlet.AdditionalServlet and rebuild the NAR","Correct `additionalServletClass` in additional_servlet.yml to point at the actual AdditionalServlet implementation","Check for classloader conflicts: ensure the NAR does not bundle a duplicate copy of the AdditionalServlet interface (let it come from the parent classloader)","Confirm the plugin code matches your Pulsar version's plugin API"],"exampleFix":"// before\npublic class MyServlet extends HttpServlet { ... }\n// after\npublic class MyServlet extends HttpServlet implements org.apache.pulsar.broker.web.plugin.servlet.AdditionalServlet {\n  @Override public Object getServletInstance() { return this; }\n  ...\n}","handlingStrategy":"type-guard","validationCode":"Class<?> c = narClassLoader.loadClass(descriptorClass);\nif (!org.apache.pulsar.broker.web.plugin.servlet.AdditionalServlet.class.isAssignableFrom(c))\n    throw new IllegalStateException(descriptorClass + \" is not an AdditionalServlet\");","typeGuard":"boolean isValidServletPlugin(Object o) {\n    return o instanceof org.apache.pulsar.broker.web.plugin.servlet.AdditionalServlet;\n}","tryCatchPattern":"try {\n    servlet = AdditionalServletUtils.load(metadata, dir);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"does not implement additional servlet interface\")) {\n        log.error(\"Plugin class has wrong type in {}: {}\", metadata.getArchivePath(), e.getMessage());\n    }\n}","preventionTips":["Make the plugin class implement AdditionalServlet, not just Servlet","Don't bundle a duplicate copy of the plugin API interfaces in the NAR","Verify the descriptor names the plugin class, not an inner servlet/helper class","Test NAR loading against the exact Pulsar version in production"],"tags":["plugin","nar","servlet","type-mismatch","pulsar"],"backgroundTag":"class-does-not-implement-interface","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}