{"record":{"id":"db08954d546164f1","repo":"apache/pulsar","slug":"sink-tenant-cannot-be-null","errorCode":null,"errorMessage":"Sink tenant cannot be null","messagePattern":"Sink tenant cannot be null","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java","lineNumber":419,"sourceCode":"            sinkConfig.setTransformFunction(\"builtin://\" + functionDetails.getBuiltin());\n        }\n        if (!functionDetails.getClassName().equals(IdentityFunction.class.getName())) {\n            sinkConfig.setTransformFunctionClassName(functionDetails.getClassName());\n        }\n        if (!isEmpty(functionDetails.getUserConfig())) {\n            sinkConfig.setTransformFunctionConfig(functionDetails.getUserConfig());\n        }\n\n\n        return sinkConfig;\n    }\n\n    public static ExtractedSinkDetails validateAndExtractDetails(SinkConfig sinkConfig,\n                                                                 ValidatableFunctionPackage sinkFunction,\n                                                                 ValidatableFunctionPackage transformFunction,\n                                                                 boolean validateConnectorConfig) {\n        if (isEmpty(sinkConfig.getTenant())) {\n            throw new IllegalArgumentException(\"Sink tenant cannot be null\");\n        }\n        if (isEmpty(sinkConfig.getNamespace())) {\n            throw new IllegalArgumentException(\"Sink namespace cannot be null\");\n        }\n        if (isEmpty(sinkConfig.getName())) {\n            throw new IllegalArgumentException(\"Sink name cannot be null\");\n        }\n\n        // make we sure we have one source of input\n        Collection<String> allInputs = collectAllInputTopics(sinkConfig);\n        if (allInputs.isEmpty()) {\n            throw new IllegalArgumentException(\"Must specify at least one topic of input via topicToSerdeClassName, \"\n                    + \"topicsPattern, topicToSchemaType or inputSpecs\");\n        }\n        for (String topic : allInputs) {\n            if (!TopicName.isValid(topic)) {\n                throw new IllegalArgumentException(String.format(\"Input topic %s is invalid\", topic));\n            }","sourceCodeStart":401,"sourceCodeEnd":437,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java#L401-L437","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set tenant on the SinkConfig before submission: sinkConfig.setTenant(\"public\") (or your target tenant).","Check your config YAML/JSON includes the 'tenant' field under the sink config.","Verify the code path constructing SinkConfig (e.g. from FunctionConfig conversion) copies tenant correctly.","Catch IllegalArgumentException and report a clear config error to the user before calling the admin API."],"exampleFix":"// before\nSinkConfig cfg = new SinkConfig();\ncfg.setNamespace(\"default\");\ncfg.setName(\"my-sink\");\n// after\nSinkConfig cfg = new SinkConfig();\ncfg.setTenant(\"public\");\ncfg.setNamespace(\"default\");\ncfg.setName(\"my-sink\");","handlingStrategy":"validation","validationCode":"if (sinkConfig.getTenant() == null || sinkConfig.getTenant().isEmpty()) {\n    throw new IllegalArgumentException(\"sinkConfig.tenant must be set before submission\");\n}","typeGuard":"static boolean hasTenant(SinkConfig cfg) {\n    return cfg != null && cfg.getTenant() != null && !cfg.getTenant().trim().isEmpty();\n}","tryCatchPattern":"try {\n    SinkConfigUtils.validateAndExtractDetails(cfg, sinkPkg, transformPkg, true);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"tenant\")) {\n        log.error(\"Sink config missing tenant: fix config and retry\", e);\n    }\n    throw e;\n}","preventionTips":["Always set tenant (namespace, name) together when building a SinkConfig.","Load configs through a schema-validated YAML parser that marks tenant required.","Reuse a shared builder that enforces the required identity triple.","Add a unit test asserting the fully-qualified name tenant/namespace/name is complete."],"tags":["pulsar","config-validation","sink","illegal-argument"],"backgroundTag":"missing-required-field","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}