{"record":{"id":"6e4a8f23604e8949","repo":"apache/shenyu","slug":"load-extension-resources-error-subclass-without-join","errorCode":null,"errorMessage":"load extension resources error,subClass without @Join annotation","messagePattern":"load extension resources error,subClass without @Join annotation","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"shenyu-spi/src/main/java/org/apache/shenyu/spi/ExtensionLoader.java","lineNumber":311,"sourceCode":"                        loadClass(classes, name, classPath);\n                    } catch (ClassNotFoundException e) {\n                        throw new IllegalStateException(\"load extension resources error\", e);\n                    }\n                }\n            });\n        } catch (IOException e) {\n            throw new IllegalStateException(\"load extension resources error\", e);\n        }\n    }\n    \n    private void loadClass(final Map<String, ClassEntity> classes,\n                           final String name, final String classPath) throws ClassNotFoundException {\n        Class<?> subClass = Objects.nonNull(this.classLoader) ? Class.forName(classPath, true, this.classLoader) : Class.forName(classPath);\n        if (!clazz.isAssignableFrom(subClass)) {\n            throw new IllegalStateException(\"load extension resources error,\" + subClass + \" subtype is not of \" + clazz);\n        }\n        if (!subClass.isAnnotationPresent(Join.class)) {\n            throw new IllegalStateException(\"load extension resources error,\" + subClass + \" without @\" + Join.class + \" annotation\");\n        }\n        ClassEntity oldClassEntity = classes.get(name);\n        if (Objects.isNull(oldClassEntity)) {\n            Join joinAnnotation = subClass.getAnnotation(Join.class);\n            ClassEntity classEntity = new ClassEntity(name, joinAnnotation.order(), subClass, joinAnnotation.isSingleton());\n            classes.put(name, classEntity);\n        } else if (!Objects.equals(oldClassEntity.getClazz(), subClass)) {\n            throw new IllegalStateException(\"load extension resources error,Duplicate class \" + clazz.getName() + \" name \"\n                    + name + \" on \" + oldClassEntity.getClazz().getName() + \" or \" + subClass.getName());\n        }\n    }\n    \n    /**\n     * The type Holder.\n     *\n     * @param <T> the type parameter.\n     */\n    private static final class Holder<T> {","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-spi/src/main/java/org/apache/shenyu/spi/ExtensionLoader.java#L293-L329","documentation":"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'.","triggerScenarios":"An implementation class implementing the SPI interface is listed in META-INF/shenyu/<interface-fqcn> but the class body has no @Join annotation.","commonSituations":"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.","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"],"exampleFix":"// before\npublic class RandomLoadBalance implements LoadBalance { ... }\n// after\n@Join\npublic class RandomLoadBalance implements LoadBalance { ... }","handlingStrategy":"validation","validationCode":"if (!RandomLoadBalance.class.isAnnotationPresent(Join.class)) { throw new IllegalStateException(\"RandomLoadBalance must be @Join-annotated\"); }","typeGuard":null,"tryCatchPattern":"try { T ext = loader.getJoin(name); } catch (IllegalStateException e) { log.error(\"Missing @Join: {}\", e.getMessage()); throw e; }","preventionTips":["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"],"tags":["spi","annotation","registration"],"backgroundTag":"missing-required-annotation","analyzedSha":"567142e07261b3e615ae8850b30f4421f455cc5d","analyzedAt":"2026-09-12T10:08:21.293Z","contentChangedAt":"2026-09-12T10:08:21.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}