apache/pulsar · error · IllegalArgumentException

Failed to extract sink class from archive

Error message

Failed to extract sink class from archive

What it means

When className is unset, validateAndExtractDetails reads the connector definition from META-INF/services/pulsar-io.yaml; if the YAML parses but declares no sinkClass (e.g. it only defines a source, or the sinkClass field is empty), this IllegalArgumentException is thrown because a sink deployment cannot proceed without an implementing class.

Source

Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java:469

            ResourceConfigUtils.validate(sinkConfig.getResources());
        }

        if (sinkConfig.getTimeoutMs() != null && sinkConfig.getTimeoutMs() < 0) {
            throw new IllegalArgumentException("Sink timeout must be a positive number");
        }

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

        // check if sink implements the correct interfaces
        TypeDefinition sinkClass;
        try {
            sinkClass = sinkFunction.resolveType(sinkClassName);
        } catch (TypePool.Resolution.NoSuchTypeException e) {
            throw new IllegalArgumentException(
                    String.format("Sink class %s not found", sinkClassName), e);
        }

        String functionClassName = sinkConfig.getTransformFunctionClassName();
        TypeDefinition typeArg;
        ValidatableFunctionPackage inputFunction;
        if (transformFunction != null) {
            // if function class name in sink config is not set, this should be a built-in function
            // thus we should try to find it class name in the NAR service definition

View on GitHub (pinned to 820761864e)

Solutions

  1. Add/fix the sinkClass entry in META-INF/services/pulsar-io.yaml inside the NAR.
  2. Use the correct archive — one that implements a sink (or set className explicitly to your sink class).
  3. If the connector is source-only, deploy it via 'pulsar-admin source create' instead.
  4. Inspect the YAML: 'unzip -p my-sink.nar META-INF/services/pulsar-io.yaml' to confirm sinkClass is present.

Example fix

// before — META-INF/services/pulsar-io.yaml
name: my-connector
sourceClass: org.example.MySource
// after
name: my-connector
sourceClass: org.example.MySource
sinkClass: org.example.MySink
Defensive patterns

Strategy: validation

Validate before calling

ConnectorDefinition def = sinkPkg.getFunctionMetaData(ConnectorDefinition.class);
if (sinkConfig.getClassName() == null && (def == null || def.getSinkClass() == null)) {
    throw new IllegalArgumentException("connector archive does not declare a sinkClass");
}

Type guard

static boolean declaresSinkClass(ValidatableFunctionPackage pkg, String className) {
    if (className != null) return true;
    ConnectorDefinition d = pkg.getFunctionMetaData(ConnectorDefinition.class);
    return d != null && d.getSinkClass() != null && !d.getSinkClass().isEmpty();
}

Try / catch

try {
    SinkConfigUtils.validateAndExtractDetails(cfg, sinkPkg, transformPkg, true);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Failed to extract sink class")) {
        log.error("pulsar-io.yaml has no sinkClass: is this a source-only connector?", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Uploading a connector NAR whose pulsar-io.yaml defines sourceClass but not sinkClass (a source-only connector used with 'sink create'); pulsar-io.yaml with an empty sinkClass key.

Common situations: Reusing a source connector archive as a sink; hand-edited pulsar-io.yaml missing the sinkClass line; generic connector archives where only one direction is implemented.

Related errors


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