apache/pulsar · error · IllegalArgumentException

Source package doesn't contain the META-INF/services/pulsar-

Error message

Source package doesn't contain the META-INF/services/pulsar-io.yaml file.

What it means

When SourceConfig.className is null, the library treats the package as a built-in (NAR) connector and tries to read its connector metadata from META-INF/services/pulsar-io.yaml inside the archive. If that descriptor is missing, the connector definition is null and this IllegalArgumentException is thrown — the package is neither a plain-class source nor a well-formed NAR connector.

Source

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

            if (!TopicName.isValid(sourceConfig.getLogTopic())) {
                throw new IllegalArgumentException(
                        String.format("LogTopic topic %s is invalid", sourceConfig.getLogTopic()));
            }
        }
        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()

View on GitHub (pinned to 820761864e)

Solutions

  1. Set sourceConfig.setClassName("com.example.MySource") explicitly so the archive metadata is not needed
  2. If it should be a NAR connector, rebuild the NAR so META-INF/services/pulsar-io.yaml (with the sourceClass entry) is packaged in the archive
  3. Verify the uploaded archive: unzip -l my-source.nar | grep pulsar-io.yaml and re-upload the correct file
  4. Check the YAML has a sourceClass key pointing at the Source implementation

Example fix

// before
SourceConfig cfg = new SourceConfig();
cfg.setArchive("/connectors/my-source.nar"); // className missing, NAR lacks descriptor
// after (option A: explicit class)
cfg.setClassName("com.example.MySource");
cfg.setArchive("/connectors/my-source.nar");
// after (option B: proper NAR)
// rebuild NAR including src/main/resources/META-INF/services/pulsar-io.yaml with sourceClass set
Defensive patterns

Strategy: validation

Validate before calling

if (cfg.getClassName() == null) {
    try (java.util.zip.ZipFile zar = new java.util.zip.ZipFile(cfg.getArchive())) {
        if (zar.getEntry("META-INF/services/pulsar-io.yaml") == null) {
            throw new IllegalStateException(
                cfg.getArchive() + " has no META-INF/services/pulsar-io.yaml; set className explicitly or rebuild the NAR");
        }
    }
}

Try / catch

try {
    SourceConfigUtils.validateAndExtractDetails(cfg, pkg, true);
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("META-INF/services/pulsar-io.yaml")) {
        // treat as packaging error: set className or rebuild/re-upload the NAR
    }
}

Prevention

When it happens

Trigger: Registering a source with archive set but className unset, where the uploaded NAR/JAR lacks META-INF/services/pulsar-io.yaml — e.g. a hand-assembled NAR, a jar built without the pulsar-io.yaml resource, or pointing at the wrong archive file.

Common situations: Building a custom connector NAR without including the service descriptor resource; using a fat jar of connector code rather than a proper NAR package; typos in the archive path so a wrong file gets uploaded; upgrading a connector to a build that no longer bundles the descriptor.

Related errors


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