{"record":{"id":"9b3cca1758a52896","repo":"elastic/elasticsearch","slug":"named-component-does-not-extend-from-an-ext","errorCode":null,"errorMessage":"Named component {}({}) does not extend from an extensible class","messagePattern":"Named component (.+?)\\((.+?)\\) does not extend from an extensible class","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"libs/plugin-scanner/src/main/java/org/elasticsearch/plugin/scanner/NamedComponentScanner.java","lineNumber":88,"sourceCode":"            (classname, map) -> new AnnotationVisitor(Opcodes.ASM9) {\n                @Override\n                public void visit(String key, Object value) {\n                    assert key.equals(\"value\");\n                    assert value instanceof String;\n                    map.put(value.toString(), classname);\n                }\n            }\n        );\n\n        namedComponentsScanner.visit(classReaders);\n\n        Map<String, Map<String, String>> componentInfo = new HashMap<>();\n        for (var e : namedComponentsScanner.getFoundClasses().entrySet()) {\n            String name = e.getKey();\n            String classnameWithSlashes = e.getValue();\n            String extensibleClassnameWithSlashes = extensibleClassScanner.getFoundClasses().get(classnameWithSlashes);\n            if (extensibleClassnameWithSlashes == null) {\n                throw new RuntimeException(\n                    \"Named component \" + name + \"(\" + pathToClassName(classnameWithSlashes) + \") does not extend from an extensible class\"\n                );\n            }\n            var named = componentInfo.computeIfAbsent(pathToClassName(extensibleClassnameWithSlashes), k -> new HashMap<>());\n            named.put(name, pathToClassName(classnameWithSlashes));\n        }\n        return componentInfo;\n    }\n\n    private static String pathToClassName(String classWithSlashes) {\n        return classWithSlashes.replace('/', '.');\n    }\n\n}\n","sourceCodeStart":70,"sourceCodeEnd":103,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/plugin-scanner/src/main/java/org/elasticsearch/plugin/scanner/NamedComponentScanner.java#L70-L103","documentation":"Thrown at build time by NamedComponentScanner.scanForNamedClasses when a class annotated @NamedComponent is not itself (or via its hierarchy) annotated @Extensible / does not resolve to an extensible base class in the extensibleClassScanner's findings. The scanner cross-references the two ASM scans; a @NamedComponent with no matching @Extensible entry is a contract violation. RuntimeException aborting the build/plugin descriptor generation.","triggerScenarios":"Annotating a class with @NamedComponent(value=\"foo\") but forgetting @Extensible on the base interface/superclass it extends; renaming/moving the extensible base so ASM no longer records it; an inheritance chain where @Extensible is on an interface that the named component does not transitively implement (according to the class files on the scanner's classpath).","commonSituations":"Plugin authors adding a new named component for the first time and missing the @Extensible declaration on the extension point; refactoring that splits an interface without re-annotating; a build that runs the scanner with an incomplete classpath so the extensible base is never visited.","solutions":["Annotate the extension-point interface or abstract class the named component extends/implements with @org.elasticsearch.plugin.Extensible.","Ensure the extensible base is on the compile/scanner classpath (the ASM ClassReaders must visit it).","Rebuild the plugin descriptor (re-run the gradle scanner task) after adding @Extensible so the cross-reference resolves.","If the named component genuinely has no extensible base, remove @NamedComponent — it is not a valid extension point."],"exampleFix":"// before\n@NamedComponent(\"my_thing\")\npublic class MyThing implements Thing { ... }  // Thing not @Extensible\n\n// after\n@Extensible\npublic interface Thing { ... }\n\n@NamedComponent(\"my_thing\")\npublic class MyThing implements Thing { ... }","handlingStrategy":"validation","validationCode":"// At build time, before scanning, assert every @NamedComponent has an @Extensible base.\nfor (Class<?> c : namedComponentClasses) {\n    boolean hasExtensibleBase = Arrays.stream(c.getInterfaces())\n        .flatMap(i -> Stream.concat(Stream.of(i), Arrays.stream(i.getInterfaces())))\n        .anyMatch(i -> i.isAnnotationPresent(Extensible.class));\n    if (!hasExtensibleBase) {\n        throw new IllegalStateException(c + \" is @NamedComponent but extends no @Extensible type\");\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always annotate the extension-point interface/abstract class with @Extensible.","Keep the extensible base on the scanner's classpath so ASM visits it.","Re-run the scanner task after adding @Extensible before rebuilding the descriptor."],"tags":["plugin-api","build-time","scanner","asm","extensibility"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}