{"record":{"id":"0cd67999e8d12dac","repo":"alibaba/canal","slug":"more-than-1-default-extension-name-on-extension-0cd679","errorCode":null,"errorMessage":"more than 1 default extension name on extension ${type.getName()}: ${Arrays.toString(names)}","messagePattern":"more than 1 default extension name on extension (.+?): (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"connector/core/src/main/java/com/alibaba/otter/canal/connector/core/spi/ExtensionLoader.java","lineNumber":257,"sourceCode":"                                         // the app using Spring Tools Suit play\n                                         // button.\n            jarPath = jarPath.replaceAll(\"/classes/.*\", \"/classes/\");\n        }\n        Path path = Paths.get(jarPath).getParent(); // Paths - from java 8\n        if (path != null) {\n            return path.toString();\n        }\n        return null;\n    }\n\n    private Map<String, Class<?>> loadExtensionClasses(String spiDir, String standbyDir) {\n        final SPI defaultAnnotation = type.getAnnotation(SPI.class);\n        if (defaultAnnotation != null) {\n            String value = defaultAnnotation.value();\n            if ((value = value.trim()).length() > 0) {\n                String[] names = NAME_SEPARATOR.split(value);\n                if (names.length > 1) {\n                    throw new IllegalStateException(\"more than 1 default extension name on extension \" + type.getName()\n                                                    + \": \" + Arrays.toString(names));\n                }\n                if (names.length == 1) cachedDefaultName = names[0];\n            }\n        }\n\n        Map<String, Class<?>> extensionClasses = new HashMap<>();\n\n        if (spiDir != null && standbyDir != null) {\n            // 1. plugin folder，customized extension classLoader\n            // （jar_dir/plugin）\n            String dir = File.separator + this.getJarDirectoryPath() + spiDir; // +\n                                                                               // \"plugin\";\n\n            File externalLibDir = new File(dir);\n            if (!externalLibDir.exists()) {\n                externalLibDir = new File(File.separator + this.getJarDirectoryPath() + standbyDir);\n            }","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/alibaba/canal/blob/87be50e87686a3e8af08c368d0e1ffd1f59eb04a/connector/core/src/main/java/com/alibaba/otter/canal/connector/core/spi/ExtensionLoader.java#L239-L275","documentation":"Thrown by loadExtensionClasses when the @SPI annotation value on the interface contains more than one comma-separated name after split. The SPI default-value contract allows exactly one default extension name; multiple defaults are ambiguous and rejected before any class is loaded.","triggerScenarios":"Declaring @SPI(\"a,b\") or @SPI(\"a, b, c\") on an extension interface — the NAME_SEPARATOR split yields >1 element.","commonSituations":"Developer lists several 'preferred' defaults on the annotation thinking the loader will pick one; copy-paste from a multi-impl comment; accidental comma in the annotation value.","solutions":["Set exactly one default name in the annotation: @SPI(\"rocketmq\").","If multiple implementations exist, register each as a named entry in the SPI descriptor and select at runtime via the name, not via multiple defaults."],"exampleFix":"// before\n@SPI(\"rocketmq,kafka\")\npublic interface CanalMQProducer { ... }\n// after\n@SPI(\"rocketmq\")\npublic interface CanalMQProducer { ... }","handlingStrategy":"validation","validationCode":"// Validate the @SPI default value at build/test time\nimport com.alibaba.otter.canal.connector.core.spi.SPI;\nSPI a = MyInterface.class.getAnnotation(SPI.class);\nif (a != null && a.value().trim().length() > 0) {\n    String[] names = a.value().trim().split(\"\\\\s*,+\\\\s*\");\n    if (names.length > 1) {\n        throw new AssertionError(\"@SPI on \" + MyInterface.class\n            + \" declares >1 default: \" + java.util.Arrays.toString(names));\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep exactly one name in @SPI values.","Add a unit test asserting each @SPI interface has at most one default name."],"tags":["spi","annotation","config","extension-loader"],"backgroundTag":null,"analyzedSha":"87be50e87686a3e8af08c368d0e1ffd1f59eb04a","analyzedAt":"2026-08-14T04:30:11.918Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}