{"record":{"id":"a83051718aabb06b","repo":"apache/pulsar","slug":"function-tenant-cannot-be-null","errorCode":null,"errorMessage":"Function tenant cannot be null","messagePattern":"Function tenant cannot be null","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java","lineNumber":791,"sourceCode":"\n        if (functionConfig.getMaxMessageRetries() != null && functionConfig.getMaxMessageRetries() >= 0) {\n            throw new IllegalArgumentException(\"Message retries not yet supported in Go function\");\n        }\n    }\n\n    private static void verifyNoTopicClash(Collection<String> inputTopics, String outputTopic)\n            throws IllegalArgumentException {\n        if (inputTopics.contains(outputTopic)) {\n            throw new IllegalArgumentException(\n                    String.format(\n                            \"Output topic %s is also being used as an input topic (topics must be one or the other)\",\n                            outputTopic));\n        }\n    }\n\n    public static void doCommonChecks(FunctionConfig functionConfig) {\n        if (isEmpty(functionConfig.getTenant())) {\n            throw new IllegalArgumentException(\"Function tenant cannot be null\");\n        }\n        if (isEmpty(functionConfig.getNamespace())) {\n            throw new IllegalArgumentException(\"Function namespace cannot be null\");\n        }\n        if (isEmpty(functionConfig.getName())) {\n            throw new IllegalArgumentException(\"Function name cannot be null\");\n        }\n        // go doesn't need className. Java className is done in doJavaChecks.\n        if (functionConfig.getRuntime() == FunctionConfig.Runtime.PYTHON) {\n            if (isEmpty(functionConfig.getClassName())) {\n                throw new IllegalArgumentException(\"Function classname cannot be null\");\n            }\n        }\n\n        Collection<String> allInputTopics = collectAllInputTopics(functionConfig);\n        if (allInputTopics.isEmpty()) {\n            throw new IllegalArgumentException(\"No input topic(s) specified for the function\");\n        }","sourceCodeStart":773,"sourceCodeEnd":809,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L773-L809","documentation":"FunctionConfigUtils.doCommonChecks validates a function's FunctionConfig before it can be created or updated. The first mandatory field is the tenant, the top-level namespace grouping in Pulsar. If tenant is null or empty, validation fails immediately with this IllegalArgumentException so an incomplete function config never reaches the broker.","triggerScenarios":"Calling createFunction/updateFunction (via FunctionConfigUtils' createFunction paths, the functions worker REST API, or pulsar-admin functions create) with a FunctionConfig whose getTenant() returns null or the empty string; doCommonChecks is invoked from validateNonJavaFunction and validateJavaFunction.","commonSituations":"Building a FunctionConfig programmatically and forgetting setTenant(); YAML/JSON function config files that omit the 'tenant' key; CLI invocations missing the --tenant flag; configs deserialized from templates where tenant was left as a placeholder.","solutions":["Set the tenant on the FunctionConfig before submitting, e.g. functionConfig.setTenant(\"public\") (or pass --tenant to the CLI).","If loading from YAML/JSON, add the missing 'tenant' key to the config file.","Verify the field is populated after deserialization; empty-string tenants also fail, so trim/require a non-empty value.","Use a tenant that exists in the cluster (commonly 'public') if you are unsure which one to use."],"exampleFix":"// before\nFunctionConfig config = new FunctionConfig();\nconfig.setNamespace(\"default\");\nconfig.setName(\"my-fn\");\n// after\nFunctionConfig config = new FunctionConfig();\nconfig.setTenant(\"public\");\nconfig.setNamespace(\"default\");\nconfig.setName(\"my-fn\");","handlingStrategy":"validation","validationCode":"if (config == null || config.getTenant() == null || config.getTenant().trim().isEmpty()) {\n    throw new IllegalArgumentException(\"FunctionConfig.tenant must be set before submission\");\n}","typeGuard":"static boolean hasTenant(FunctionConfig c) {\n    return c != null && c.getTenant() != null && !c.getTenant().trim().isEmpty();\n}","tryCatchPattern":"try {\n    FunctionConfigUtils.validateNonJavaFunction(functionConfig, null);\n} catch (IllegalArgumentException e) {\n    log.error(\"Invalid function config: {}\", e.getMessage());\n    // surface a friendly message asking for --tenant\n}","preventionTips":["Always populate tenant/namespace/name as a set when building FunctionConfig objects.","Use function config YAML templates that include all three identity fields.","Run FunctionConfigUtils validation locally (or a dry-run) before submitting to the cluster.","Prefer pulsar-admin CLI defaults (--tenant public) in scripts to avoid omissions."],"tags":["pulsar","functions","configuration","validation"],"backgroundTag":"required-field-missing","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"}