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

  1. Make the factory class implement CustomCommandFactory and provide a public no-arg constructor
  2. Verify the exact interface package (org.apache.pulsar.admin.cli CustomCommandFactory) matches the one the provider checks — no duplicate classes on the classpath
  3. Rebuild/redeploy the plugin jar and confirm the class file in the deployed artifact actually implements the interface (javap -classpath)
  4. 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

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


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