elastic/elasticsearch · error · RuntimeException
Named component {}({}) does not extend from an extensible cl
Error message
Named component {}({}) does not extend from an extensible class What it means
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.
Source
Thrown at libs/plugin-scanner/src/main/java/org/elasticsearch/plugin/scanner/NamedComponentScanner.java:88
(classname, map) -> new AnnotationVisitor(Opcodes.ASM9) {
@Override
public void visit(String key, Object value) {
assert key.equals("value");
assert value instanceof String;
map.put(value.toString(), classname);
}
}
);
namedComponentsScanner.visit(classReaders);
Map<String, Map<String, String>> componentInfo = new HashMap<>();
for (var e : namedComponentsScanner.getFoundClasses().entrySet()) {
String name = e.getKey();
String classnameWithSlashes = e.getValue();
String extensibleClassnameWithSlashes = extensibleClassScanner.getFoundClasses().get(classnameWithSlashes);
if (extensibleClassnameWithSlashes == null) {
throw new RuntimeException(
"Named component " + name + "(" + pathToClassName(classnameWithSlashes) + ") does not extend from an extensible class"
);
}
var named = componentInfo.computeIfAbsent(pathToClassName(extensibleClassnameWithSlashes), k -> new HashMap<>());
named.put(name, pathToClassName(classnameWithSlashes));
}
return componentInfo;
}
private static String pathToClassName(String classWithSlashes) {
return classWithSlashes.replace('/', '.');
}
}
View on GitHub (pinned to db6a809a66)
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.
Example fix
// before
@NamedComponent("my_thing")
public class MyThing implements Thing { ... } // Thing not @Extensible
// after
@Extensible
public interface Thing { ... }
@NamedComponent("my_thing")
public class MyThing implements Thing { ... } Defensive patterns
Strategy: validation
Validate before calling
// At build time, before scanning, assert every @NamedComponent has an @Extensible base.
for (Class<?> c : namedComponentClasses) {
boolean hasExtensibleBase = Arrays.stream(c.getInterfaces())
.flatMap(i -> Stream.concat(Stream.of(i), Arrays.stream(i.getInterfaces())))
.anyMatch(i -> i.isAnnotationPresent(Extensible.class));
if (!hasExtensibleBase) {
throw new IllegalStateException(c + " is @NamedComponent but extends no @Extensible type");
}
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Unexpected class file version ${rawVersion} for ${cn.name} (
- Error patching JAR [%s]: SHA256 digest mismatch (%s). This J
- error patching [%s]: the jar does not contain [%s]
- TransportVersion.fromName must be called with a non-empty St
- Cannot merge SEARCH_TIME and INDEX_TIME analysis mode.
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/9b3cca1758a52896.
Report an issue: GitHub.