{"record":{"id":"531be9e68e913bfe","repo":"apache/pulsar","slug":"topic-has-blank-local-topic","errorCode":null,"errorMessage":"${topic} has blank local topic","messagePattern":"(.+?) has blank local topic","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java","lineNumber":565,"sourceCode":"     * @return the full topic name.\n     */\n    public static String toFullTopicName(String topic) {\n        final int index = topic.indexOf(\"://\");\n        if (index >= 0) {\n            TopicDomain.getEnum(topic.substring(0, index));\n            final List<String> parts = splitBySlash(topic.substring(index + \"://\".length()), 4);\n            if (parts.size() == 4) {\n                throw new IllegalArgumentException(\n                        \"V1 topic names (with cluster component) are no longer supported. \"\n                        + \"Please use the V2 format: '<domain>://tenant/namespace/topic'. Got: \" + topic);\n            }\n            if (parts.size() != 3) {\n                throw new IllegalArgumentException(topic + \" is invalid. \"\n                    + \"Expected format: '<domain>://tenant/namespace/topic'\");\n            }\n            NamespaceName.validateNamespaceName(parts.get(0), parts.get(1));\n            if (StringUtils.isBlank(parts.get(2))) {\n                throw new IllegalArgumentException(topic + \" has blank local topic\");\n            }\n            return topic; // it's a valid full topic name\n        } else {\n            List<String> parts = splitBySlash(topic, 0);\n            if (parts.size() != 1 && parts.size() != 3) {\n                throw new IllegalArgumentException(topic + \" is invalid\");\n            }\n            if (parts.size() == 1) {\n                if (StringUtils.isBlank(parts.get(0))) {\n                    throw new IllegalArgumentException(topic + \" has blank local topic\");\n                }\n                return \"persistent://public/default/\" + parts.get(0);\n            } else {\n                NamespaceName.validateNamespaceName(parts.get(0), parts.get(1));\n                if (StringUtils.isBlank(parts.get(2))) {\n                    throw new IllegalArgumentException(topic + \" has blank local topic\");\n                }\n                return \"persistent://\" + topic;","sourceCodeStart":547,"sourceCodeEnd":583,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java#L547-L583","documentation":"In toFullTopicName, after confirming the string is a full URL with a valid namespace (3 segments after '://'), the local topic segment (parts[2]) is checked with StringUtils.isBlank. If it is empty or whitespace — e.g. 'persistent://tenant/ns/' — the method throws this IllegalArgumentException because a topic URL must name an actual topic.","triggerScenarios":"Calling toFullTopicName() with a trailing-slash URL like 'persistent://my-tenant/my-ns/' or a URL where the topic part is whitespace.","commonSituations":"Template/config substitution that left the topic slot empty ('persistent://public/default/${TOPIC}' unresolved); string-splitting code dropping the last segment; users pasting a namespace URL where a topic URL is required.","solutions":["Append a non-blank topic name after the namespace","Fix unresolved template placeholders in config","Validate the input ends with a non-empty topic segment before calling"],"exampleFix":"// before\nString url = \"persistent://public/default/\" + maybeNullTopic; // blank\nTopicName.toFullTopicName(url);\n// after\nif (StringUtils.isNotBlank(topic)) { TopicName.toFullTopicName(\"persistent://public/default/\" + topic); }","handlingStrategy":"validation","validationCode":"public static boolean hasLocalTopic(String topicUrl) {\n    int i = topicUrl.indexOf(\"://\");\n    return i >= 0 && topicUrl.substring(i + 3).split(\"/\").length == 3\n        && !topicUrl.substring(i + 3).split(\"/\")[2].trim().isEmpty();\n}","typeGuard":null,"tryCatchPattern":"try {\n    String full = TopicName.toFullTopicName(url);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().endsWith(\"has blank local topic\")) {\n        throw new ConfigException(\"Topic portion of '\" + url + \"' is empty; set the topic name\");\n    }\n}","preventionTips":["Check for unresolved ${...} placeholders in config before use","Strip trailing slashes from URLs before treating them as topic names","Require a non-blank topic in user-facing input forms"],"tags":["java","topic-naming","blank-value"],"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-14T05:17:10.506Z"}