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 isView on GitHub (pinned to 820761864e)
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
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
- 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
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
- Additional servlets `${name}` does NOT provide an additional
- Unsupported additional servlet type ${servletType}
- Failed to load the additional servlet for name `${servletNam
- Class ${entryFilterClass} does not implement entry filter in
- Additional servlet instance of type ${className} doesn't imp
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/f145ff7343fabe1f.
Report an issue: GitHub.