{"record":{"id":"9a88d4154527a9a5","repo":"apache/pulsar","slug":"the-s-connector-does-not-provide-a-source-imple","errorCode":null,"errorMessage":"The '%s' connector does not provide a source implementation","messagePattern":"The '(.+?)' connector does not provide a source implementation","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/io/ConnectorUtils.java","lineNumber":71,"sourceCode":"\n@UtilityClass\n@CustomLog\npublic class ConnectorUtils {\n\n    /**\n     * Computes a SHA-256 digest of a file as lower-case hex (for connector archive identity on reload).\n     */\n    public static String computeArchiveChecksumHex(Path path) throws IOException {\n        return HexFormat.of().formatHex(FileUtils.calculateSha256sum(path.toAbsolutePath().normalize().toFile()));\n    }\n\n    /**\n     * Extract the Pulsar IO Source class from a connector archive.\n     */\n    public static String getIOSourceClass(NarClassLoader narClassLoader) throws IOException {\n        ConnectorDefinition conf = getConnectorDefinition(narClassLoader);\n        if (StringUtils.isEmpty(conf.getSourceClass())) {\n            throw new IOException(\n                    String.format(\"The '%s' connector does not provide a source implementation\", conf.getName()));\n        }\n\n        try {\n            // Try to load source class and check it implements Source interface\n            Class<?> sourceClass = narClassLoader.loadClass(conf.getSourceClass());\n            if (!(Source.class.isAssignableFrom(sourceClass) || BatchSource.class.isAssignableFrom(sourceClass))) {\n                throw new IOException(String.format(\"Class %s does not implement interface %s or %s\",\n                        conf.getSourceClass(), Source.class.getName(), BatchSource.class.getName()));\n            }\n        } catch (Throwable t) {\n            Exceptions.rethrowIOException(t);\n        }\n\n        return conf.getSourceClass();\n    }\n\n    /**","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/io/ConnectorUtils.java#L53-L89","documentation":"ConnectorUtils.getIOSourceClass extracts the Pulsar IO Source class from a NAR connector archive. After parsing the connector's META-INF definition, if the sourceClass field is empty, the archive does not declare a source implementation, so an IOException is thrown because a Source cannot be built from this connector.","triggerScenarios":"Calling getIOSourceClass(narClassLoader) on a connector NAR whose connector definition (META-INF/services/org.apache.pulsar.io.core.Connector or pulsar-io.yaml) declares no sourceClass, e.g. a sink-only connector.","commonSituations":"Deploying a sink-only connector (e.g. a JDBC/elasticsearch sink NAR) when creating a source; typos or missing fields in the connector metadata file; using a connector archive built from an older/empty definition.","solutions":["Use a connector NAR that actually implements a Source (check the connector's docs for source/sink support)","Add the sourceClass entry to the connector's definition metadata and rebuild the NAR","Verify the archive with getConnectorDefinition() before use and confirm getSourceClass() is non-empty"],"exampleFix":"// before: using a sink-only archive\nString cls = ConnectorUtils.getIOSourceClass(loader); // throws\n// after: check first\nConnectorDefinition def = ConnectorUtils.getConnectorDefinition(loader);\nif (StringUtils.isNotEmpty(def.getSourceClass())) {\n    String cls = ConnectorUtils.getIOSourceClass(loader);\n}","handlingStrategy":"validation","validationCode":"ConnectorDefinition def = ConnectorUtils.getConnectorDefinition(narClassLoader);\nif (def == null || StringUtils.isEmpty(def.getSourceClass())) {\n    throw new IllegalArgumentException(\"NAR is not a source connector: \" + def.getName());\n}","typeGuard":"boolean isSourceConnector(ConnectorDefinition def) {\n    return def != null && StringUtils.isNotEmpty(def.getSourceClass());\n}","tryCatchPattern":"try {\n    String src = ConnectorUtils.getIOSourceClass(loader);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"does not provide a source implementation\")) {\n        // treat as sink-only connector, route accordingly\n    } else { throw e; }\n}","preventionTips":["Keep separate NARs (or verify definitions) for source-only vs sink-only connectors","Validate all uploaded connector NARs with getConnectorDefinition at deploy time","Document which connectors are sink-only in your platform catalog"],"tags":["pulsar-io","connector","nar","missing-source-class"],"backgroundTag":"connector-missing-implementation","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}