apache/shenyu · error · IllegalStateException
load extension resources error,subClass without @Join…
Error message
load extension resources error,subClass without @Join annotation
What it means
loadClass also requires every registered implementation to be annotated with @Join, which supplies its order and singleton flag. This IllegalStateException is thrown when a class registered in META-INF/shenyu/ is a valid subtype of the SPI interface but lacks the @Join annotation: '<subClass> without @Join annotation'.
Solutions
- Add @Join to the implementation class
- Optionally set attributes: @Join(order = 1, isSingleton = true)
- Rebuild and confirm the annotation is on the class actually referenced by the resource file
Example fix
// before
public class RandomLoadBalance implements LoadBalance { ... }
// after
@Join
public class RandomLoadBalance implements LoadBalance { ... } Defensive patterns
Strategy: validation
Validate before calling
if (!RandomLoadBalance.class.isAnnotationPresent(Join.class)) { throw new IllegalStateException("RandomLoadBalance must be @Join-annotated"); } Try / catch
try { T ext = loader.getJoin(name); } catch (IllegalStateException e) { log.error("Missing @Join: {}", e.getMessage()); throw e; } Prevention
- Add @Join to every class listed in META-INF/shenyu/ files
- Use a reflective test that asserts @Join on all registered implementations
- Remember ShenYu SPI (unlike Dubbo) requires @Join on implementations
When it happens
Trigger: An implementation class implementing the SPI interface is listed in META-INF/shenyu/<interface-fqcn> but the class body has no @Join annotation.
Common situations: Registering the resource entry but forgetting the annotation on a new implementation; annotation lost in refactoring/code generation; assuming registration alone (as in Dubbo-style SPI) is enough in ShenYu, where @Join is required.
Related errors
- extension clazz (clazz) without @SPI Annotation
- load extension resources error,subClass subtype is not of…
- doRegister Failed to execute, because selectorDO object is…
- plugin not enabled for current namespace or plugin not…
- client register param must config the appName or contextPath
AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12).
Data as JSON: /api/errors/6e4a8f23604e8949.
Report an issue: GitHub.
Appendix: source
Thrown at shenyu-spi/src/main/java/org/apache/shenyu/spi/ExtensionLoader.java:311
loadClass(classes, name, classPath);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("load extension resources error", e);
}
}
});
} catch (IOException e) {
throw new IllegalStateException("load extension resources error", e);
}
}
private void loadClass(final Map<String, ClassEntity> classes,
final String name, final String classPath) throws ClassNotFoundException {
Class<?> subClass = Objects.nonNull(this.classLoader) ? Class.forName(classPath, true, this.classLoader) : Class.forName(classPath);
if (!clazz.isAssignableFrom(subClass)) {
throw new IllegalStateException("load extension resources error," + subClass + " subtype is not of " + clazz);
}
if (!subClass.isAnnotationPresent(Join.class)) {
throw new IllegalStateException("load extension resources error," + subClass + " without @" + Join.class + " annotation");
}
ClassEntity oldClassEntity = classes.get(name);
if (Objects.isNull(oldClassEntity)) {
Join joinAnnotation = subClass.getAnnotation(Join.class);
ClassEntity classEntity = new ClassEntity(name, joinAnnotation.order(), subClass, joinAnnotation.isSingleton());
classes.put(name, classEntity);
} else if (!Objects.equals(oldClassEntity.getClazz(), subClass)) {
throw new IllegalStateException("load extension resources error,Duplicate class " + clazz.getName() + " name "
+ name + " on " + oldClassEntity.getClazz().getName() + " or " + subClass.getName());
}
}
/**
* The type Holder.
*
* @param <T> the type parameter.
*/
private static final class Holder<T> {View on GitHub (pinned to 567142e072)