apache/pulsar · error · IOException

The '%s' connector does not provide a source implementation

Error message

The '%s' connector does not provide a source implementation

What it means

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.

Source

Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/io/ConnectorUtils.java:71

@UtilityClass
@CustomLog
public class ConnectorUtils {

    /**
     * Computes a SHA-256 digest of a file as lower-case hex (for connector archive identity on reload).
     */
    public static String computeArchiveChecksumHex(Path path) throws IOException {
        return HexFormat.of().formatHex(FileUtils.calculateSha256sum(path.toAbsolutePath().normalize().toFile()));
    }

    /**
     * Extract the Pulsar IO Source class from a connector archive.
     */
    public static String getIOSourceClass(NarClassLoader narClassLoader) throws IOException {
        ConnectorDefinition conf = getConnectorDefinition(narClassLoader);
        if (StringUtils.isEmpty(conf.getSourceClass())) {
            throw new IOException(
                    String.format("The '%s' connector does not provide a source implementation", conf.getName()));
        }

        try {
            // Try to load source class and check it implements Source interface
            Class<?> sourceClass = narClassLoader.loadClass(conf.getSourceClass());
            if (!(Source.class.isAssignableFrom(sourceClass) || BatchSource.class.isAssignableFrom(sourceClass))) {
                throw new IOException(String.format("Class %s does not implement interface %s or %s",
                        conf.getSourceClass(), Source.class.getName(), BatchSource.class.getName()));
            }
        } catch (Throwable t) {
            Exceptions.rethrowIOException(t);
        }

        return conf.getSourceClass();
    }

    /**

View on GitHub (pinned to 820761864e)

Solutions

  1. Use a connector NAR that actually implements a Source (check the connector's docs for source/sink support)
  2. Add the sourceClass entry to the connector's definition metadata and rebuild the NAR
  3. Verify the archive with getConnectorDefinition() before use and confirm getSourceClass() is non-empty

Example fix

// before: using a sink-only archive
String cls = ConnectorUtils.getIOSourceClass(loader); // throws
// after: check first
ConnectorDefinition def = ConnectorUtils.getConnectorDefinition(loader);
if (StringUtils.isNotEmpty(def.getSourceClass())) {
    String cls = ConnectorUtils.getIOSourceClass(loader);
}
Defensive patterns

Strategy: validation

Validate before calling

ConnectorDefinition def = ConnectorUtils.getConnectorDefinition(narClassLoader);
if (def == null || StringUtils.isEmpty(def.getSourceClass())) {
    throw new IllegalArgumentException("NAR is not a source connector: " + def.getName());
}

Type guard

boolean isSourceConnector(ConnectorDefinition def) {
    return def != null && StringUtils.isNotEmpty(def.getSourceClass());
}

Try / catch

try {
    String src = ConnectorUtils.getIOSourceClass(loader);
} catch (IOException e) {
    if (e.getMessage().contains("does not provide a source implementation")) {
        // treat as sink-only connector, route accordingly
    } else { throw e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/9a88d4154527a9a5. Report an issue: GitHub.