{"record":{"id":"8688fca36241970b","repo":"apache/pulsar","slug":"class-factoryclass-does-not-implement-customcom","errorCode":null,"errorMessage":"Class ${factoryClass} does not implement CustomCommandFactory interface","messagePattern":"Class (.+?) does not implement CustomCommandFactory interface","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"pulsar-client-tools/src/main/java/org/apache/pulsar/admin/cli/utils/CustomCommandFactoryProvider.java","lineNumber":164,"sourceCode":"                                                   String narExtractionDirectory)\n            throws IOException {\n        final File narFile = metadata.getArchivePath().toAbsolutePath().normalize().toFile();\n        NarClassLoader ncl = NarClassLoaderBuilder.builder()\n                .narFile(narFile)\n                .parentClassLoader(CustomCommandFactory.class.getClassLoader())\n                .extractionDirectory(narExtractionDirectory)\n                .build();\n        CustomCommandFactoryDefinition def = getCustomCommandFactoryDefinition(ncl);\n        if (StringUtils.isBlank(def.getFactoryClass())) {\n            throw new IOException(\"Command Factory `\" + def.getName() + \"` does NOT provide a Command Factory\"\n                    + \" implementation\");\n        }\n\n        try {\n            Class commandFactoryClass = ncl.loadClass(def.getFactoryClass());\n            Object factory = commandFactoryClass.getDeclaredConstructor().newInstance();\n            if (!(factory instanceof CustomCommandFactory)) {\n                throw new IOException(\"Class \" + def.getFactoryClass()\n                        + \" does not implement CustomCommandFactory interface\");\n            }\n           return (CustomCommandFactory) factory;\n        } catch (Exception e) {\n            if (e instanceof IOException) {\n                throw (IOException) e;\n            }\n            log.error().exception(e).attr(\"factoryClass\", def.getFactoryClass())\n                    .log(\"Failed to load class\");\n            throw new IOException(e);\n        }\n    }\n}\n","sourceCodeStart":146,"sourceCodeEnd":178,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client-tools/src/main/java/org/apache/pulsar/admin/cli/utils/CustomCommandFactoryProvider.java#L146-L178","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\npublic class MyCommands { public List<CmdBase> commands() { ... } }\n// after\npublic class MyCommands implements CustomCommandFactory {\n  public MyCommands() {}\n  @Override public List<CmdBase> getCommands() { ... }\n}","handlingStrategy":"type-guard","validationCode":"Class<?> c = Class.forName(factoryClassName, true, loader);\nif (!CustomCommandFactory.class.isAssignableFrom(c))\n    throw new IllegalArgumentException(factoryClassName + \" does not implement CustomCommandFactory\");\nif (c.getConstructor() == null) /* needs public no-arg ctor */;","typeGuard":"static boolean isValidFactory(Class<?> c) {\n    return CustomCommandFactory.class.isAssignableFrom(c)\n        && java.lang.reflect.Modifier.isPublic(c.getModifiers());\n}","tryCatchPattern":"try {\n    CustomCommandFactory f = CustomCommandFactoryProvider.load(def);\n} catch (IOException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"does not implement CustomCommandFactory\")) {\n        LOG.error(\"Plugin {} is not a CustomCommandFactory; fix the class or remove the definition\", def.getFactoryClass(), e);\n    } else { throw e; }\n}","preventionTips":["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"],"tags":["cli","plugin","reflection","class-loading"],"backgroundTag":"class-does-not-implement-interface","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}