apache/shenyu · error · IllegalArgumentException

extension clazz (clazz) without @SPI Annotation

Error message

extension clazz (clazz) without @SPI Annotation

What it means

getExtensionLoader requires the extension interface to carry the @SPI annotation, which marks it as an extension point (optionally with a default value like @SPI("roundRobin")). This IllegalArgumentException is thrown when the class is a valid interface but lacks @SPI. The annotation is what tells the framework which implementation name is the default.

Solutions

  1. Add @SPI to the interface declaration
  2. Optionally specify a default: @SPI("defaultImplName")
  3. Confirm you are using org.apache.shenyu.spi.SPI, not another annotation with the same simple name

Example fix

// before
public interface LoadBalance { String select(...); }
// after
@SPI("roundRobin")
public interface LoadBalance { String select(...); }
Defensive patterns

Strategy: validation

Validate before calling

if (!LoadBalance.class.isAnnotationPresent(org.apache.shenyu.spi.SPI.class)) { throw new IllegalStateException("LoadBalance must be @SPI-annotated"); }

Try / catch

try { ExtensionLoader.getExtensionLoader(iface); } catch (IllegalArgumentException e) { log.error("Missing @SPI on {}: {}", iface, e.getMessage()); }

Prevention

When it happens

Trigger: Calling ExtensionLoader.getExtensionLoader(SomeInterface.class) where SomeInterface is an interface but is not annotated with @SPI.

Common situations: Writing a brand-new SPI interface and forgetting the @SPI annotation; passing a plain third-party interface (e.g. a JDK or Spring interface) to the ShenYu SPI loader; annotation removed during refactoring.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/a0f04e0e766d0785. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-spi/src/main/java/org/apache/shenyu/spi/ExtensionLoader.java:100

    }
    
    /**
     * Gets extension loader.
     *
     * @param <T>   the type parameter
     * @param clazz the clazz
     * @param cl    the cl
     * @return the extension loader.
     */
    public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz, final ClassLoader cl) {
        
        Objects.requireNonNull(clazz, "extension clazz is null");
        
        if (!clazz.isInterface()) {
            throw new IllegalArgumentException("extension clazz (" + clazz + ") is not interface!");
        }
        if (!clazz.isAnnotationPresent(SPI.class)) {
            throw new IllegalArgumentException("extension clazz (" + clazz + ") without @" + SPI.class + " Annotation");
        }
        ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) LOADERS.get(clazz);
        if (Objects.nonNull(extensionLoader)) {
            return extensionLoader;
        }
        LOADERS.putIfAbsent(clazz, new ExtensionLoader<>(clazz, cl));
        return (ExtensionLoader<T>) LOADERS.get(clazz);
    }
    
    /**
     * Gets extension loader.
     *
     * @param <T>   the type parameter
     * @param clazz the clazz
     * @return the extension loader
     */
    public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz) {
        return getExtensionLoader(clazz, ExtensionLoader.class.getClassLoader());

View on GitHub (pinned to 567142e072)