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

  1. Add @Join to the implementation class
  2. Optionally set attributes: @Join(order = 1, isSingleton = true)
  3. 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

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


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)