apache/pulsar · error · IOException
Class ${factoryClass} does not implement CustomCommandFactor
Error message
Class ${factoryClass} does not implement CustomCommandFactory interface What it means
CustomCommandFactoryProvider.load reflectively instantiates the factory class named in a custom-command definition and requires it to implement the CustomCommandFactory interface. If the loaded object is not an instance of that interface, an IOException is thrown so the CLI refuses to wire up an incompatible factory class.
Source
Thrown at pulsar-client-tools/src/main/java/org/apache/pulsar/admin/cli/utils/CustomCommandFactoryProvider.java:164
String narExtractionDirectory)
throws IOException {
final File narFile = metadata.getArchivePath().toAbsolutePath().normalize().toFile();
NarClassLoader ncl = NarClassLoaderBuilder.builder()
.narFile(narFile)
.parentClassLoader(CustomCommandFactory.class.getClassLoader())
.extractionDirectory(narExtractionDirectory)
.build();
CustomCommandFactoryDefinition def = getCustomCommandFactoryDefinition(ncl);
if (StringUtils.isBlank(def.getFactoryClass())) {
throw new IOException("Command Factory `" + def.getName() + "` does NOT provide a Command Factory"
+ " implementation");
}
try {
Class commandFactoryClass = ncl.loadClass(def.getFactoryClass());
Object factory = commandFactoryClass.getDeclaredConstructor().newInstance();
if (!(factory instanceof CustomCommandFactory)) {
throw new IOException("Class " + def.getFactoryClass()
+ " does not implement CustomCommandFactory interface");
}
return (CustomCommandFactory) factory;
} catch (Exception e) {
if (e instanceof IOException) {
throw (IOException) e;
}
log.error().exception(e).attr("factoryClass", def.getFactoryClass())
.log("Failed to load class");
throw new IOException(e);
}
}
}
View on GitHub (pinned to 820761864e)
Solutions
- Make the factory class implement CustomCommandFactory and provide a public no-arg constructor
- Verify the exact interface package (org.apache.pulsar.admin.cli CustomCommandFactory) matches the one the provider checks — no duplicate classes on the classpath
- Rebuild/redeploy the plugin jar and confirm the class file in the deployed artifact actually implements the interface (javap -classpath)
- If the provider is loaded in an isolated classloader (ncl), ensure the interface is loaded by a parent-visible classloader so instanceof succeeds
Example fix
// before
public class MyCommands { public List<CmdBase> commands() { ... } }
// after
public class MyCommands implements CustomCommandFactory {
public MyCommands() {}
@Override public List<CmdBase> getCommands() { ... }
} Defensive patterns
Strategy: type-guard
Validate before calling
Class<?> c = Class.forName(factoryClassName, true, loader);
if (!CustomCommandFactory.class.isAssignableFrom(c))
throw new IllegalArgumentException(factoryClassName + " does not implement CustomCommandFactory");
if (c.getConstructor() == null) /* needs public no-arg ctor */; Type guard
static boolean isValidFactory(Class<?> c) {
return CustomCommandFactory.class.isAssignableFrom(c)
&& java.lang.reflect.Modifier.isPublic(c.getModifiers());
} Try / catch
try {
CustomCommandFactory f = CustomCommandFactoryProvider.load(def);
} catch (IOException e) {
if (e.getMessage() != null && e.getMessage().contains("does not implement CustomCommandFactory")) {
LOG.error("Plugin {} is not a CustomCommandFactory; fix the class or remove the definition", def.getFactoryClass(), e);
} else { throw e; }
} Prevention
- Always declare 'implements CustomCommandFactory' on plugin classes and add a compile-time unit test asserting it
- Keep only one copy of the CustomCommandFactory interface on the classpath (avoid shaded duplicates)
- Run javap or a build-time check on shipped plugin jars to confirm the interface is implemented
- Add a startup smoke test that loads every registered factory definition before production
When it happens
Trigger: A custom-command definition file (def.getFactoryClass()) names a class that exists and instantiates via its no-arg constructor, but the class does not implement org.apache.pulsar.admin.cli.utils CustomCommandFactory (or implements a similarly-named interface from a different package/version).
Common situations: Hand-written plugin classes missing the 'implements CustomCommandFactory' clause; classpath picking up an old jar whose factory interface differs; copy-pasted example class renamed/refactored away from the interface; fat-jar shading that split the interface into two packages.
Related errors
- Failed to resolve type for Source class %s
- The '${offloaderName}' offloader does not provide an offload
- Class ${factoryClass} does not implement interface ${interfa
- Need to specify a configuration file for broker
- No configuration file for Bookie
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/8688fca36241970b.
Report an issue: GitHub.