alibaba/canal · error · IllegalArgumentException

Extension type({}) is not extension, because WITHOUT @{} Ann

Error message

Extension type({}) is not extension, because WITHOUT @{} Annotation!

What it means

Thrown by ExtensionLoader.getExtensionLoader when the interface type lacks the @SPI annotation. The annotation declares the default implementation name; without it the loader refuses to manage the type because it cannot know the SPI contract/default. This mirrors Dubbo's SPI requirement.

Source

Thrown at connector/core/src/main/java/com/alibaba/otter/canal/connector/core/spi/ExtensionLoader.java:77

    private ConcurrentHashMap<String, IllegalStateException>         exceptions                 = new ConcurrentHashMap<>();

    private static <T> boolean withExtensionAnnotation(Class<T> type) {
        return type.isAnnotationPresent(SPI.class);
    }

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        return getExtensionLoader(type, DEFAULT_CLASSLOADER_POLICY);
    }

    @SuppressWarnings("unchecked")
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type, String classLoaderPolicy) {
        if (type == null) throw new IllegalArgumentException("Extension type == null");
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @"
                                               + SPI.class.getSimpleName() + " Annotation!");
        }

        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type, classLoaderPolicy));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

    public ExtensionLoader(Class<?> type){
        this.type = type;
        this.classLoaderPolicy = DEFAULT_CLASSLOADER_POLICY;
    }

    public ExtensionLoader(Class<?> type, String classLoaderPolicy){
        this.type = type;

View on GitHub (pinned to 87be50e876)

Solutions

  1. Annotate the interface with @SPI and a default name: @SPI("rocketmq") public interface CanalMQProducer { ... }.
  2. Confirm you imported com.alibaba.otter.canal.connector.core.spi.SPI, not a third-party @SPI.
  3. Only load types that are true canal extension points.

Example fix

// before
public interface CanalMQProducer { ... }
// after
@SPI("rocketmq")
public interface CanalMQProducer { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Verify the @SPI annotation is present before loading
if (!type.isAnnotationPresent(com.alibaba.otter.canal.connector.core.spi.SPI.class)) {
    throw new IllegalStateException(type + " is not annotated @SPI; cannot be an extension point");
}

Type guard

boolean isSpiInterface(Class<?> type) {
    return type != null && type.isInterface()
        && type.isAnnotationPresent(
            com.alibaba.otter.canal.connector.core.spi.SPI.class);
}

Prevention

When it happens

Trigger: Calling getExtensionLoader on an interface that is not annotated with com.alibaba.otter.canal.connector.core.spi.SPI.

Common situations: Introducing a new extension point and forgetting to annotate the interface with @SPI("defaultName"); using a plain library interface by mistake; SPI annotation import collision (a different SPI annotation class).

Related errors


AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14). Data as JSON: /api/errors/c92b67fd07dd14ed. Report an issue: GitHub.