{"record":{"id":"542ff054f4d15211","repo":"apache/pulsar","slug":"output-topic-s-is-invalid","errorCode":null,"errorMessage":"Output topic %s is invalid","messagePattern":"Output topic (.+?) is invalid","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java","lineNumber":818,"sourceCode":"        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            }\n        }\n\n        if (!isEmpty(functionConfig.getLogTopic())) {\n            if (!TopicName.isValid(functionConfig.getLogTopic())) {\n                throw new IllegalArgumentException(\n                        String.format(\"LogTopic topic %s is invalid\", functionConfig.getLogTopic()));\n            }\n        }\n\n        if (!isEmpty(functionConfig.getDeadLetterTopic())) {\n            if (!TopicName.isValid(functionConfig.getDeadLetterTopic())) {\n                throw new IllegalArgumentException(\n                        String.format(\"DeadLetter topic %s is invalid\", functionConfig.getDeadLetterTopic()));\n            }\n        }\n","sourceCodeStart":800,"sourceCodeEnd":836,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L800-L836","documentation":"FunctionConfigUtils.doCommonChecks validates the function's output topic with TopicName.isValid(). If the output field is non-empty but not a syntactically valid Pulsar topic name, creation fails with this IllegalArgumentException. This keeps the function from being deployed against a topic it could never publish to.","triggerScenarios":"Submitting a FunctionConfig via createFunction/updateFunction (CLI, REST admin API) where functionConfig.getOutput() is non-empty and fails TopicName.isValid: e.g. 'my-output' without tenant/namespace when no defaults apply, malformed persistent:// URI, or stray whitespace.","commonSituations":"Typos in the sink/output topic, mixing up the dead-letter and output fields, partial topic names after refactoring tenants/namespaces, template placeholders like ${OUTPUT_TOPIC} left unsubstituted in YAML.","solutions":["Set the output topic to a fully qualified name: persistent://<tenant>/<namespace>/<topic>","Remove or blank the output field if the function does not produce output (empty output is allowed)","Check for unsubstituted placeholders or whitespace in YAML/env-derived config","Validate locally with TopicName.isValid(output) before calling the admin API"],"exampleFix":"// before\nconfig.setOutput(\"results-topic\");\n// after\nconfig.setOutput(\"persistent://public/default/results-topic\");","handlingStrategy":"validation","validationCode":"if (config.getOutput() != null && !config.getOutput().isEmpty()\n        && !TopicName.isValid(config.getOutput().trim())) {\n    throw new IllegalArgumentException(\"Invalid output topic: \" + config.getOutput());\n}","typeGuard":"boolean validOptionalTopic(String t) {\n    return t == null || t.isEmpty() || TopicName.isValid(t.trim());\n}","tryCatchPattern":"try {\n    admin.functions().updateFunction(functionConfig, configLocation);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Output topic\")) {\n        // fix functionConfig.getOutput() to persistent://tenant/ns/topic\n    }\n    throw e;\n}","preventionTips":["Verify output topic exists/valid via admin top API before creating the function","Keep output names fully qualified; avoid shorthand","Scan manifests for unsubstituted placeholders like ${OUTPUT_TOPIC}","Trim values derived from env vars"],"tags":["pulsar-functions","validation","topic-name","configuration"],"backgroundTag":"invalid-topic-name","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"}