{"record":{"id":"d729631aeb4958d9","repo":"apache/pulsar","slug":"invalid-segment-descriptor-expected-hexstart","errorCode":null,"errorMessage":"Invalid segment descriptor: expected '<hexStart>-<hexEnd>-<segmentId>', got: '${descriptor}'","messagePattern":"Invalid segment descriptor: expected '<hexStart>-<hexEnd>-<segmentId>', got: '(.+?)'","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java","lineNumber":211,"sourceCode":"                }\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;\n                    this.segmentRange = null;\n                    this.segmentId = -1;\n                }\n\n                if (this.domain == TopicDomain.segment) {\n                    this.completeTopicName = String.format(\"%s://%s/%s/%s/%s\",\n                            domain, tenant, namespacePortion, localName,\n                            String.format(\"%04x-%04x-%d\", segmentRange.start(), segmentRange.end(), segmentId));\n                } else {","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java#L193-L229","documentation":"For a segment:// topic name, the part after the last '/' must be a descriptor of exactly three dash-separated fields: hash range start (hex), hash range end (hex), and a numeric segment id — '<hexStart>-<hexEnd>-<segmentId>'. Any other dash-segment count throws this IllegalArgumentException.","triggerScenarios":"TopicName.get('segment://tenant/ns/topic/abc') (1 field), 'segment://tenant/ns/topic/0000-ffff' (2 fields), or 'segment://tenant/ns/topic/a-b-c-d' (4 fields).","commonSituations":"Hand-writing segment names without the 3-part descriptor; hash range values accidentally containing extra '-'; generated names corrupted by other '-' splitting logic; typo dropping one of the three fields.","solutions":["Format the descriptor as '<hexStart>-<hexEnd>-<segmentId>', e.g. '0000-ffff-0' (hash bounds parsed as hex, segment id as decimal)","Count the dash-separated fields: the descriptor must yield exactly 3 parts","Build names with the same formatting the parser emits: String.format(\"%04x-%04x-%d\", start, end, segmentId)"],"exampleFix":"// before\nTopicName.get(\"segment://tenant/ns/my-topic/0000-ffff\");\n// after\nTopicName.get(\"segment://tenant/ns/my-topic/0000-ffff-0\");","handlingStrategy":"validation","validationCode":"static boolean isSegmentDescriptorValid(String descriptor) {\n    return descriptor != null && descriptor.matches(\"[0-9a-fA-F]+-[0-9a-fA-F]+-\\\\d+\");\n}\nstatic String formatSegmentDescriptor(int hashStart, int hashEnd, long segmentId) {\n    return String.format(\"%04x-%04x-%d\", hashStart, hashEnd, segmentId);\n}","typeGuard":"static boolean isWellFormedSegmentName(String name) {\n    if (name == null || !name.contains(\"/\")) return false;\n    return isSegmentDescriptorValid(name.substring(name.lastIndexOf('/') + 1));\n}","tryCatchPattern":"try {\n    TopicName tn = TopicName.get(segmentTopic);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Invalid segment descriptor\")) {\n        throw new IllegalArgumentException(\"Descriptor must be <hexStart>-<hexEnd>-<segmentId>, e.g. 0000-ffff-0\");\n    }\n    throw e;\n}","preventionTips":["Always format descriptors with String.format(\"%04x-%04x-%d\", start, end, segmentId) instead of manual concatenation","Remember hash bounds are hex, segment id is decimal — never mix encodings","Never split or rejoin segment names on '-' with generic string utilities","Add a regex pre-check ([0-9a-fA-F]+-[0-9a-fA-F]+-\\d+) before parsing"],"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-14T05:17:10.506Z"}