{"record":{"id":"2199ef84ea14d69d","repo":"apache/pulsar","slug":"invalid-segment-topic-name-local-name-must-contai","errorCode":null,"errorMessage":"Invalid segment topic name: local name must contain '<parent-topic>/<hashStart>-<hashEnd>-<segmentId>'. Got: ${completeTopicName}","messagePattern":"Invalid segment topic name: local name must contain '<parent-topic>/<hashStart>-<hashEnd>-<segmentId>'\\. Got: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java","lineNumber":202,"sourceCode":"                List<String> parts = splitBySlash(completeTopicName.substring(index + \"://\".length()),\n                        splitLimit);\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: \"\n                                    + completeTopicName);\n                } else if (parts.size() != 3) {\n                    throw new IllegalArgumentException(\"Invalid topic name \" + completeTopicName);\n                }\n                this.tenant = parts.get(0);\n                this.namespacePortion = parts.get(1);\n                String rawLocalName = parts.get(2);\n\n                // For segment:// domains, split local name into parent topic name + descriptor\n                if (this.domain == TopicDomain.segment) {\n                    int lastSlash = rawLocalName.lastIndexOf('/');\n                    if (lastSlash <= 0) {\n                        throw new IllegalArgumentException(\n                                \"Invalid segment topic name: local name must contain\"\n                                        + \" '<parent-topic>/<hashStart>-<hashEnd>-<segmentId>'. Got: \"\n                                        + completeTopicName);\n                    }\n                    this.localName = rawLocalName.substring(0, lastSlash);\n                    String descriptor = rawLocalName.substring(lastSlash + 1);\n                    String[] descParts = descriptor.split(\"-\");\n                    if (descParts.length != 3) {\n                        throw new IllegalArgumentException(\n                                \"Invalid segment descriptor: expected '<hexStart>-<hexEnd>-<segmentId>',\"\n                                        + \" got: '\" + descriptor + \"'\");\n                    }\n                    this.segmentRange = HashRange.of(\n                            Integer.parseInt(descParts[0], 16),\n                            Integer.parseInt(descParts[1], 16));\n                    this.segmentId = Long.parseLong(descParts[2]);\n                } else {\n                    this.localName = rawLocalName;","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java#L184-L220","documentation":"For the segment:// domain, the topic local name must end with a '/'-separated segment descriptor appended to the parent topic name: '<parent-topic>/<hashStart>-<hashEnd>-<segmentId>'. If there is no slash or the slash is at position 0 (no parent topic), TopicName throws this IllegalArgumentException.","triggerScenarios":"TopicName.get('segment://tenant/ns/topic') (no descriptor) or TopicName.get('segment://tenant/ns/0001-00ff-0') (descriptor but no parent-topic slash).","commonSituations":"Using the segment:// domain (scalable-segments feature) with an ordinary topic name; forgetting to append the segment descriptor; building the name by concatenation that drops the parent topic prefix.","solutions":["Append a valid descriptor: 'segment://tenant/ns/parent-topic/0000-ffff-0'","Ensure the local name contains both a parent topic AND '/' + descriptor (lastIndexOf('/') must be > 0)","If you meant a regular topic, use topic:// or persistent:// domain instead of segment://"],"exampleFix":"// before\nTopicName.get(\"segment://tenant/ns/my-topic\");\n// after\nTopicName.get(\"segment://tenant/ns/my-topic/0000-ffff-0\");","handlingStrategy":"validation","validationCode":"static boolean isSegmentTopicNameValid(String name) {\n    if (name == null || !name.startsWith(\"segment://\")) return false;\n    String path = name.substring(\"segment://\".length());\n    String[] parts = path.split(\"/\", -1);\n    if (parts.length < 4) return false; // tenant/ns/parent-topic.../descriptor\n    String descriptor = parts[parts.length - 1];\n    int lastSlash = path.lastIndexOf('/');\n    return lastSlash > 0 && descriptor.matches(\"[0-9a-fA-F]+-[0-9a-fA-F]+-\\\\d+\");\n}","typeGuard":"static boolean hasSegmentDescriptor(String segmentTopic) {\n    return segmentTopic != null && segmentTopic.lastIndexOf('/') > segmentTopic.indexOf(\"://\") + 2;\n}","tryCatchPattern":"try {\n    TopicName tn = TopicName.get(segmentTopic);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Invalid segment topic name\")) {\n        throw new IllegalArgumentException(\"segment:// names need '<parent-topic>/<hexStart>-<hexEnd>-<segmentId>'\");\n    }\n    throw e;\n}","preventionTips":["Build segment names with a helper that always emits parent-topic + '/' + formatted descriptor","Remember ordinary topic names are not valid segment:// names — a descriptor is mandatory","Centralize segment-name construction/formatting in one utility","Unit-test segment name round-trips (construct then parse)"],"tags":["java","illegal-argument","segment-domain","topic-name","pulsar"],"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"}