apache/pulsar · error · IOException

Class ${className} does not implement additional servlet int

Error message

Class ${className} does not implement additional servlet interface

What it means

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.

Source

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

        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;
        }
    }

    /**
     * Adapts the servlet instance of an additional servlet to {@code jakarta.servlet.Servlet}, the servlet API
     * of the single Jetty environment the broker and the proxy run.
     *
     * <p>Servlets declaring {@link AdditionalServletType#JAKARTA_SERVLET} are returned as they are. Servlets
     * declaring {@link AdditionalServletType#JAVAX_SERVLET} implement the legacy {@code javax.servlet.Servlet}
     * interface and are adapted with the Apache Felix {@link ServletWrapper}. Registering both flavours in the
     * same environment is what lets every additional servlet go through the broker/proxy filter chain, which is

View on GitHub (pinned to 820761864e)

Solutions

  1. Make the configured class implement org.apache.pulsar.broker.web.plugin.servlet.AdditionalServlet and rebuild the NAR
  2. Correct `additionalServletClass` in additional_servlet.yml to point at the actual AdditionalServlet implementation
  3. Check for classloader conflicts: ensure the NAR does not bundle a duplicate copy of the AdditionalServlet interface (let it come from the parent classloader)
  4. Confirm the plugin code matches your Pulsar version's plugin API

Example fix

// before
public class MyServlet extends HttpServlet { ... }
// after
public class MyServlet extends HttpServlet implements org.apache.pulsar.broker.web.plugin.servlet.AdditionalServlet {
  @Override public Object getServletInstance() { return this; }
  ...
}
Defensive patterns

Strategy: type-guard

Validate before calling

Class<?> c = narClassLoader.loadClass(descriptorClass);
if (!org.apache.pulsar.broker.web.plugin.servlet.AdditionalServlet.class.isAssignableFrom(c))
    throw new IllegalStateException(descriptorClass + " is not an AdditionalServlet");

Type guard

boolean isValidServletPlugin(Object o) {
    return o instanceof org.apache.pulsar.broker.web.plugin.servlet.AdditionalServlet;
}

Try / catch

try {
    servlet = AdditionalServletUtils.load(metadata, dir);
} catch (IOException e) {
    if (e.getMessage().contains("does not implement additional servlet interface")) {
        log.error("Plugin class has wrong type in {}: {}", metadata.getArchivePath(), e.getMessage());
    }
}

Prevention

When it happens

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

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

Related errors


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