apache/pulsar · error · IllegalArgumentException

Sink tenant cannot be null

Error message

Sink tenant cannot be null

What it means

SinkConfigUtils.validateAndExtractDetails validates a sink configuration before creating/updating a Pulsar IO sink. It throws IllegalArgumentException when the sink's tenant field is null or empty, because tenant is required to build the fully-qualified sink identity (tenant/namespace/name) and to resolve permissions.

Source

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

            sinkConfig.setTransformFunction("builtin://" + functionDetails.getBuiltin());
        }
        if (!functionDetails.getClassName().equals(IdentityFunction.class.getName())) {
            sinkConfig.setTransformFunctionClassName(functionDetails.getClassName());
        }
        if (!isEmpty(functionDetails.getUserConfig())) {
            sinkConfig.setTransformFunctionConfig(functionDetails.getUserConfig());
        }


        return sinkConfig;
    }

    public static ExtractedSinkDetails validateAndExtractDetails(SinkConfig sinkConfig,
                                                                 ValidatableFunctionPackage sinkFunction,
                                                                 ValidatableFunctionPackage transformFunction,
                                                                 boolean validateConnectorConfig) {
        if (isEmpty(sinkConfig.getTenant())) {
            throw new IllegalArgumentException("Sink tenant cannot be null");
        }
        if (isEmpty(sinkConfig.getNamespace())) {
            throw new IllegalArgumentException("Sink namespace cannot be null");
        }
        if (isEmpty(sinkConfig.getName())) {
            throw new IllegalArgumentException("Sink name cannot be null");
        }

        // make we sure we have one source of input
        Collection<String> allInputs = collectAllInputTopics(sinkConfig);
        if (allInputs.isEmpty()) {
            throw new IllegalArgumentException("Must specify at least one topic of input via topicToSerdeClassName, "
                    + "topicsPattern, topicToSchemaType or inputSpecs");
        }
        for (String topic : allInputs) {
            if (!TopicName.isValid(topic)) {
                throw new IllegalArgumentException(String.format("Input topic %s is invalid", topic));
            }

View on GitHub (pinned to 820761864e)

Solutions

  1. Set tenant on the SinkConfig before submission: sinkConfig.setTenant("public") (or your target tenant).
  2. Check your config YAML/JSON includes the 'tenant' field under the sink config.
  3. Verify the code path constructing SinkConfig (e.g. from FunctionConfig conversion) copies tenant correctly.
  4. Catch IllegalArgumentException and report a clear config error to the user before calling the admin API.

Example fix

// before
SinkConfig cfg = new SinkConfig();
cfg.setNamespace("default");
cfg.setName("my-sink");
// after
SinkConfig cfg = new SinkConfig();
cfg.setTenant("public");
cfg.setNamespace("default");
cfg.setName("my-sink");
Defensive patterns

Strategy: validation

Validate before calling

if (sinkConfig.getTenant() == null || sinkConfig.getTenant().isEmpty()) {
    throw new IllegalArgumentException("sinkConfig.tenant must be set before submission");
}

Type guard

static boolean hasTenant(SinkConfig cfg) {
    return cfg != null && cfg.getTenant() != null && !cfg.getTenant().trim().isEmpty();
}

Try / catch

try {
    SinkConfigUtils.validateAndExtractDetails(cfg, sinkPkg, transformPkg, true);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("tenant")) {
        log.error("Sink config missing tenant: fix config and retry", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling createSink/updateSink (or the sink create REST/admin API, or pulsar-admin sink create) with a SinkConfig whose setTenant() was never called or was set to empty string.

Common situations: Programmatically building SinkConfig from YAML/JSON where the tenant key was misspelled or omitted; deserialization from a partial config file; frameworks generating configs that skip tenant.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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