apache/dubbo · error · IllegalStateException

More than 1 adaptive class found: {}, {}

Error message

More than 1 adaptive class found: {}, {}

What it means

Thrown by cacheAdaptiveClass() when a second, different class annotated with @Adaptive is encountered for the same SPI interface while loading SPI configs. Dubbo permits at most one @Adaptive-annotated implementation per SPI interface; if two distinct classes claim @Adaptive, the second registration (without overridden=true) fails. The first cached adaptive class wins.

Source

Thrown at dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java:1388

        if (activate != null) {
            cachedActivates.put(name, activate);
        } else if (Dubbo2CompactUtils.isEnabled() && Dubbo2ActivateUtils.isActivateLoaded()) {
            // support com.alibaba.dubbo.common.extension.Activate
            Annotation oldActivate = clazz.getAnnotation(Dubbo2ActivateUtils.getActivateClass());
            if (oldActivate != null) {
                cachedActivates.put(name, oldActivate);
            }
        }
    }

    /**
     * cache Adaptive class which is annotated with <code>Adaptive</code>
     */
    private void cacheAdaptiveClass(Class<?> clazz, boolean overridden) {
        if (cachedAdaptiveClass == null || overridden) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            throw new IllegalStateException(
                    "More than 1 adaptive class found: " + cachedAdaptiveClass.getName() + ", " + clazz.getName());
        }
    }

    /**
     * cache wrapper class
     * <p>
     * like: ProtocolFilterWrapper, ProtocolListenerWrapper
     */
    private void cacheWrapperClass(Class<?> clazz) {
        if (cachedWrapperClasses == null) {
            cachedWrapperClasses = new ConcurrentHashSet<>();
        }
        cachedWrapperClasses.add(clazz);
    }

    /**
     * test if clazz is a wrapper class

View on GitHub (pinned to 3a3043227f)

Solutions

  1. Ensure only one class annotated with @Adaptive implements the SPI interface across the entire classpath.
  2. If overriding the built-in adaptive class, exclude the original jar/config or use the overridden loading path so the later one replaces the earlier.
  3. Run 'grep -r @Adaptive' across your dependencies or use jar scanning to find both adaptive classes and remove the unintended one.

Example fix

// before: two classes
// jar1: @Adaptive public class AProtocol implements Protocol {}
// jar2: @Adaptive public class BProtocol implements Protocol {}
// throws [149]

// after: keep only one, remove @Adaptive from the other or delete the class
// jar1: @Adaptive public class AProtocol implements Protocol {}
Defensive patterns

Strategy: validation

Validate before calling

// Scan classpath for multiple @Adaptive classes on the same interface
int count = 0;
for (Class<?> c : scanImplementations(iface)) {
    if (c.isAnnotationPresent(Adaptive.class)) count++;
}
if (count > 1) throw new IllegalStateException("Multiple @Adaptive for " + iface);

Prevention

When it happens

Trigger: Two jars each ship a class annotated with @Adaptive implementing the same SPI interface. loadClass() routes @Adaptive-annotated classes to cacheAdaptiveClass(), and if cachedAdaptiveClass is already set to a different class, the guard throws. Common with custom overrides that forget to remove the original adaptive class.

Common situations: A custom jar provides its own @Adaptive implementation of an interface that already has a built-in adaptive class (e.g., dubbo's own Protocol$Adaptive-style annotated class); merging modules that each define an adaptive implementation; a shading/relocation that duplicates the adaptive class.

Related errors


AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14). Data as JSON: /api/errors/14247870501ed199. Report an issue: GitHub.