{"record":{"id":"8057170f2bc83d95","repo":"apache/pulsar","slug":"function-classname-cannot-be-null","errorCode":null,"errorMessage":"Function classname cannot be null","messagePattern":"Function classname 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":802,"sourceCode":"                            \"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        }\n        for (String topic : allInputTopics) {\n            if (!TopicName.isValid(topic)) {\n                throw new IllegalArgumentException(String.format(\"Input topic %s is invalid\", topic));\n            }\n        }\n\n        if (!isEmpty(functionConfig.getOutput())) {\n            if (!TopicName.isValid(functionConfig.getOutput())) {\n                throw new IllegalArgumentException(\n                        String.format(\"Output topic %s is invalid\", functionConfig.getOutput()));\n            }","sourceCodeStart":784,"sourceCodeEnd":820,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L784-L820","documentation":"For Python runtime functions, doCommonChecks requires a className pointing to the Python class implementing the function (unlike Go, which needs none, and Java, whose className is checked separately in doJavaChecks). A null/empty className with Runtime.PYTHON throws this IllegalArgumentException.","triggerScenarios":"Submitting a function with functionConfig.setRuntime(FunctionConfig.Runtime.PYTHON) but without setClassName(\"mymodule.MyFunction\"), through validateNonJavaFunction, the REST API, or pulsar-admin; Go functions with no className do NOT trigger this — only PYTHON configs.","commonSituations":"Copying a Go function config template for a Python function (Go configs omit className legitimately); YAML config with 'runtime: PYTHON' but no 'className'; typos in the class name field key.","solutions":["Set the fully-qualified Python class: functionConfig.setClassName(\"my.module.MyFunction\").","Add the 'className' key to the Python function's YAML/JSON config.","Ensure the class actually exists in the submitted Python payload/archive and matches the module path.","If the function is Go, confirm the runtime is set to GOLANG — then className is not required."],"exampleFix":"// before\nconfig.setRuntime(FunctionConfig.Runtime.PYTHON);\nconfig.setTenant(\"public\");\nconfig.setNamespace(\"default\");\n// after\nconfig.setRuntime(FunctionConfig.Runtime.PYTHON);\nconfig.setClassName(\"examples.function.MyFunction\");\nconfig.setTenant(\"public\");\nconfig.setNamespace(\"default\");","handlingStrategy":"validation","validationCode":"if (config.getRuntime() == FunctionConfig.Runtime.PYTHON\n        && (config.getClassName() == null || config.getClassName().trim().isEmpty())) {\n    throw new IllegalArgumentException(\"Python functions require FunctionConfig.className\");\n}","typeGuard":"static boolean hasRequiredClassName(FunctionConfig c) {\n    if (c == null || c.getRuntime() == null) return false;\n    switch (c.getRuntime()) {\n        case PYTHON: return c.getClassName() != null && !c.getClassName().trim().isEmpty();\n        case GOLANG: return true; // className not required for Go\n        default: return true; // Java checked separately in doJavaChecks\n    }\n}","tryCatchPattern":"try {\n    FunctionConfigUtils.validateNonJavaFunction(functionConfig, null);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"classname\")) {\n        throw new IllegalStateException(\"Python functions need 'className' (e.g. mymodule.MyFunction)\", e);\n    }\n    throw e;\n}","preventionTips":["Set className whenever you set runtime to PYTHON — do it in the same builder step.","Verify the class exists inside the submitted Python archive (module path matches className).","Don't reuse Go function config templates for Python functions without adding className.","Keep separate validated templates per runtime (Java/Python/Go)."],"tags":["pulsar","functions","python","configuration"],"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"}