{"record":{"id":"040bf08005083f76","repo":"apache/pulsar","slug":"source-tenant-cannot-be-null","errorCode":null,"errorMessage":"Source tenant cannot be null","messagePattern":"Source tenant cannot be null","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java","lineNumber":266,"sourceCode":"            sourceConfig.setResources(resources);\n        }\n\n        if (!isEmpty(functionDetails.getRuntimeFlags())) {\n            sourceConfig.setRuntimeFlags(functionDetails.getRuntimeFlags());\n        }\n\n        if (!isEmpty(functionDetails.getCustomRuntimeOptions())) {\n            sourceConfig.setCustomRuntimeOptions(functionDetails.getCustomRuntimeOptions());\n        }\n\n        return sourceConfig;\n    }\n\n    public static ExtractedSourceDetails validateAndExtractDetails(SourceConfig sourceConfig,\n                                                                   ValidatableFunctionPackage sourceFunction,\n                                                                   boolean validateConnectorConfig) {\n        if (isEmpty(sourceConfig.getTenant())) {\n            throw new IllegalArgumentException(\"Source tenant cannot be null\");\n        }\n        if (isEmpty(sourceConfig.getNamespace())) {\n            throw new IllegalArgumentException(\"Source namespace cannot be null\");\n        }\n        if (isEmpty(sourceConfig.getName())) {\n            throw new IllegalArgumentException(\"Source name cannot be null\");\n        }\n        if (!isEmpty(sourceConfig.getTopicName()) && !TopicName.isValid(sourceConfig.getTopicName())) {\n            throw new IllegalArgumentException(\"Topic name is invalid\");\n        }\n        if (!isEmpty(sourceConfig.getLogTopic())) {\n            if (!TopicName.isValid(sourceConfig.getLogTopic())) {\n                throw new IllegalArgumentException(\n                        String.format(\"LogTopic topic %s is invalid\", sourceConfig.getLogTopic()));\n            }\n        }\n        if (sourceConfig.getParallelism() != null && sourceConfig.getParallelism() <= 0) {\n            throw new IllegalArgumentException(\"Source parallelism must be a positive number\");","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java#L248-L284","documentation":"SourceConfigUtils.validateAndExtractDetails validates a Pulsar IO Source configuration before creating/registering a source connector. The 'tenant' field is mandatory because every source must live inside a tenant/namespace/name fully-qualified function identity. The library throws IllegalArgumentException immediately when tenant is null or empty rather than producing a partially-constructed source.","triggerScenarios":"Calling validateAndExtractDetails (directly or via source registration APIs like SourcesBase.create/update or the CLI 'pulsar-admin sources create') with a SourceConfig whose tenant field is null or an empty string.","commonSituations":"Building SourceConfig programmatically and forgetting setTenant(); deserializing a JSON/YAML source config file that lacks a 'tenant' key; copying a config template and deleting the tenant line; wiring configs through layers where tenant is filled in later than validation runs.","solutions":["Set the tenant on the SourceConfig before validation, e.g. sourceConfig.setTenant(\"public\") or the tenant you intend to use","If the tenant should come from user input (CLI flag / REST field), check it is present and reject the request with a clear 400 message before calling validateAndExtractDetails","If loading from a config file, add 'tenant' to the YAML/JSON and re-load the SourceConfig"],"exampleFix":"// before\nSourceConfig cfg = new SourceConfig();\ncfg.setNamespace(\"default\");\ncfg.setName(\"my-source\");\n// after\nSourceConfig cfg = new SourceConfig();\ncfg.setTenant(\"public\");\ncfg.setNamespace(\"default\");\ncfg.setName(\"my-source\");","handlingStrategy":"validation","validationCode":"if (sourceConfig == null || sourceConfig.getTenant() == null || sourceConfig.getTenant().isEmpty()) {\n    throw new IllegalArgumentException(\"sourceConfig.tenant must be set before validation\");\n}","typeGuard":"static boolean hasTenant(SourceConfig cfg) {\n    return cfg != null && cfg.getTenant() != null && !cfg.getTenant().trim().isEmpty();\n}","tryCatchPattern":"try {\n    SourceConfigUtils.validateAndExtractDetails(cfg, pkg, true);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Source tenant cannot be null\")) {\n        // surface a 400-style message asking the user to supply --tenant\n    }\n}","preventionTips":["Always populate tenant/namespace/name together from a single builder or factory method","Validate user-supplied config files at load time with your own schema check","Default to the 'public' tenant only deliberately, never silently"],"tags":["pulsar-functions","config-validation","illegal-argument"],"backgroundTag":"missing-required-config-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"}