apache/pulsar · error · IllegalArgumentException

Failed to extract source class from archive

Error message

Failed to extract source class from archive

What it means

For built-in (NAR) sources — className not set — the library reads the connector definition from META-INF/services/pulsar-io.yaml and takes connectorDefinition.getSourceClass() as the implementation class. If the descriptor exists but has no sourceClass (or it is blank), this IllegalArgumentException is thrown: the archive declares itself a connector but doesn't name a Source implementation.

Source

Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java:301

        if (sourceConfig.getParallelism() != null && sourceConfig.getParallelism() <= 0) {
            throw new IllegalArgumentException("Source parallelism must be a positive number");
        }
        if (sourceConfig.getResources() != null) {
            ResourceConfigUtils.validate(sourceConfig.getResources());
        }

        String sourceClassName = sourceConfig.getClassName();
        // if class name in source config is not set, this should be a built-in source
        // thus we should try to find it class name in the NAR service definition
        if (sourceClassName == null) {
            ConnectorDefinition connectorDefinition = sourceFunction.getFunctionMetaData(ConnectorDefinition.class);
            if (connectorDefinition == null) {
                throw new IllegalArgumentException(
                        "Source package doesn't contain the META-INF/services/pulsar-io.yaml file.");
            }
            sourceClassName = connectorDefinition.getSourceClass();
            if (sourceClassName == null) {
                throw new IllegalArgumentException("Failed to extract source class from archive");
            }
        }

        // check if source implements the correct interfaces
        TypeDescription sourceClass;
        try {
            sourceClass = sourceFunction.resolveType(sourceClassName);
        } catch (TypePool.Resolution.NoSuchTypeException e) {
            throw new IllegalArgumentException(
              String.format("Source class %s not found in class loader", sourceClassName), e);
        }

        if (!(sourceClass.asErasure().isAssignableTo(Source.class) || sourceClass.asErasure()
                .isAssignableTo(BatchSource.class))) {
            throw new IllegalArgumentException(
                    String.format("Source class %s does not implement the correct interface",
                            sourceClass.getName()));
        }

View on GitHub (pinned to 820761864e)

Solutions

  1. Ensure the archive is a SOURCE connector and its pulsar-io.yaml contains a valid sourceClass: entry
  2. If the package is actually a sink or generic function, register it through the sinks/functions API instead of sources
  3. Set sourceConfig.setClassName(...) to the Source implementation explicitly to bypass descriptor extraction
  4. Open the NAR and inspect META-INF/services/pulsar-io.yaml to confirm the key and value

Example fix

// pulsar-io.yaml inside the NAR
// before
sinkClass: com.example.MySink
// after
sourceClass: com.example.MySource
Defensive patterns

Strategy: validation

Validate before calling

// For NAR packages, parse the descriptor before submitting:
ConnectorDefinition def = null;
try (java.util.zip.ZipFile zar = new java.util.zip.ZipFile(cfg.getArchive())) {
    java.util.zip.ZipEntry e = zar.getEntry("META-INF/services/pulsar-io.yaml");
    if (e != null) {
        def = new com.fasterxml.jackson.dataformat.yaml.YAMLMapper()
                .readValue(zar.getInputStream(e), ConnectorDefinition.class);
    }
}
if (cfg.getClassName() == null && (def == null || def.getSourceClass() == null)) {
    throw new IllegalStateException("NAR descriptor has no sourceClass; not deployable as a source");
}

Try / catch

try {
    SourceConfigUtils.validateAndExtractDetails(cfg, pkg, true);
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("Failed to extract source class")) {
        // inspect pulsar-io.yaml: add sourceClass or register via the sinks API if it's a sink
    }
}

Prevention

When it happens

Trigger: Uploading a NAR whose pulsar-io.yaml exists but omits the sourceClass field (e.g. it's a sink-only connector descriptor, or the YAML key is misspelled), while sourceConfig.className is null.

Common situations: Trying to deploy a sink connector NAR as a source (descriptor has sinkClass but no sourceClass); hand-edited pulsar-io.yaml with wrong key casing; connector releases where the class was renamed and the descriptor wasn't updated.

Related errors


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