apache/pulsar · error · IOException

Additional servlets `${name}` does NOT provide an additional

Error message

Additional servlets `${name}` does NOT provide an additional servlets implementation

What it means

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.

Source

Thrown at pulsar-broker-common/src/main/java/org/apache/pulsar/broker/web/plugin/servlet/AdditionalServletUtils.java:140

    /**
     * Load the additional servlets according to the additional servlet definition.
     *
     * @param metadata the additional servlet definition.
     */
    @SuppressWarnings("unchecked")
    public AdditionalServletWithClassLoader load(
            AdditionalServletMetadata metadata, String narExtractionDirectory) throws IOException {

        final File narFile = metadata.getArchivePath().toAbsolutePath().normalize().toFile();
        NarClassLoader ncl = NarClassLoaderBuilder.builder()
                .narFile(narFile)
                .parentClassLoader(AdditionalServlet.class.getClassLoader())
                .extractionDirectory(narExtractionDirectory)
                .build();

        AdditionalServletDefinition def = getAdditionalServletDefinition(ncl);
        if (StringUtils.isBlank(def.getAdditionalServletClass())) {
            throw new IOException("Additional servlets `" + def.getName() + "` does NOT provide an "
                    + "additional servlets implementation");
        }

        try {
            Class additionalServletClass = ncl.loadClass(def.getAdditionalServletClass());
            Object additionalServlet = additionalServletClass.getDeclaredConstructor().newInstance();
            if (!(additionalServlet instanceof AdditionalServlet)) {
                throw new IOException("Class " + def.getAdditionalServletClass()
                        + " does not implement additional servlet interface");
            }
            AdditionalServlet servlet = (AdditionalServlet) additionalServlet;
            return new AdditionalServletWithClassLoader(servlet, ncl);
        } catch (Throwable t) {
            rethrowIOException(t);
            return null;
        }
    }

View on GitHub (pinned to 820761864e)

Solutions

  1. Add/fix `additionalServletClass: <fully.qualified.ClassName>` in the NAR's META-INF/services/additional_servlet.yml and rebuild the NAR
  2. Verify with `unzip -p your.nar META-INF/services/additional_servlet.yml` that the field is present and non-blank
  3. Replace the NAR with an official/release-built servlet NAR to rule out a corrupted archive

Example fix

# before (additional_servlet.yml)
name: my-servlet
description: my servlet
# after
name: my-servlet
description: my servlet
additionalServletClass: com.example.MyAdditionalServlet
Defensive patterns

Strategy: validation

Validate before calling

try (JarFile nar = new JarFile(narPath)) {
    String yml = nar.getInputStream(nar.getEntry("META-INF/services/additional_servlet.yml"))
        .readAllBytes() != null ? new String(nar.getInputStream(
            nar.getEntry("META-INF/services/additional_servlet.yml")).readAllBytes()) : null;
    if (yml == null || !yml.contains("additionalServletClass:"))
        throw new IllegalStateException("NAR descriptor missing additionalServletClass");
}

Try / catch

try {
    servlet = AdditionalServletUtils.load(metadata, narExtractionDir);
} catch (IOException e) {
    log.error("Failed to load servlet NAR {}: {}", metadata.getArchivePath(), e.getMessage());
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/754d9c4dd1334f28. Report an issue: GitHub.