{"record":{"id":"754d9c4dd1334f28","repo":"apache/pulsar","slug":"additional-servlets-name-does-not-provide-an","errorCode":null,"errorMessage":"Additional servlets `${name}` does NOT provide an additional servlets implementation","messagePattern":"Additional servlets `(.+?)` does NOT provide an additional servlets implementation","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":140,"sourceCode":"    /**\n     * Load the additional servlets according to the additional servlet definition.\n     *\n     * @param metadata the additional servlet definition.\n     */\n    @SuppressWarnings(\"unchecked\")\n    public AdditionalServletWithClassLoader load(\n            AdditionalServletMetadata metadata, String narExtractionDirectory) throws IOException {\n\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","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/web/plugin/servlet/AdditionalServletUtils.java#L122-L158","documentation":"When the broker loads an additional servlet NAR package (via AdditionalServletUtils.load), it reads `META-INF/services/additional_servlet.yml` from the archive. That definition must specify an `additionalServletClass`. If the class field is blank/missing, load() throws this IOException because there is no implementation class to instantiate.","triggerScenarios":"Deploying a .nar whose additional_servlet.yml lacks the `additionalServletClass` key or has it empty; a hand-built NAR packaged with a minimal/incorrect descriptor; a renamed or re-packaged servlet jar losing the metadata file.","commonSituations":"Building a custom servlet NAR and forgetting the descriptor field; using a NAR generated for a different plugin type (e.g. an entrypoint/function NAR) whose yml schema differs; upstream packaging regression after a Pulsar version change.","solutions":["Add/fix `additionalServletClass: <fully.qualified.ClassName>` in the NAR's META-INF/services/additional_servlet.yml and rebuild the NAR","Verify with `unzip -p your.nar META-INF/services/additional_servlet.yml` that the field is present and non-blank","Replace the NAR with an official/release-built servlet NAR to rule out a corrupted archive"],"exampleFix":"# before (additional_servlet.yml)\nname: my-servlet\ndescription: my servlet\n# after\nname: my-servlet\ndescription: my servlet\nadditionalServletClass: com.example.MyAdditionalServlet","handlingStrategy":"validation","validationCode":"try (JarFile nar = new JarFile(narPath)) {\n    String yml = nar.getInputStream(nar.getEntry(\"META-INF/services/additional_servlet.yml\"))\n        .readAllBytes() != null ? new String(nar.getInputStream(\n            nar.getEntry(\"META-INF/services/additional_servlet.yml\")).readAllBytes()) : null;\n    if (yml == null || !yml.contains(\"additionalServletClass:\"))\n        throw new IllegalStateException(\"NAR descriptor missing additionalServletClass\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    servlet = AdditionalServletUtils.load(metadata, narExtractionDir);\n} catch (IOException e) {\n    log.error(\"Failed to load servlet NAR {}: {}\", metadata.getArchivePath(), e.getMessage());\n}","preventionTips":["Always set additionalServletClass in additional_servlet.yml","Inspect the NAR descriptor (unzip -p) before deploying","Build NARs with the official Pulsar plugin archetype/template"],"tags":["plugin","nar","servlet","configuration","pulsar"],"backgroundTag":"missing-plugin-descriptor-field","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}