apache/pulsar · error · IllegalArgumentException

Invalid segment descriptor: expected '<hexStart>-<hexEnd>-<s

Error message

Invalid segment descriptor: expected '<hexStart>-<hexEnd>-<segmentId>', got: '${descriptor}'

What it means

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.

Source

Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java:211

                }
                this.tenant = parts.get(0);
                this.namespacePortion = parts.get(1);
                String rawLocalName = parts.get(2);

                // For segment:// domains, split local name into parent topic name + descriptor
                if (this.domain == TopicDomain.segment) {
                    int lastSlash = rawLocalName.lastIndexOf('/');
                    if (lastSlash <= 0) {
                        throw new IllegalArgumentException(
                                "Invalid segment topic name: local name must contain"
                                        + " '<parent-topic>/<hashStart>-<hashEnd>-<segmentId>'. Got: "
                                        + completeTopicName);
                    }
                    this.localName = rawLocalName.substring(0, lastSlash);
                    String descriptor = rawLocalName.substring(lastSlash + 1);
                    String[] descParts = descriptor.split("-");
                    if (descParts.length != 3) {
                        throw new IllegalArgumentException(
                                "Invalid segment descriptor: expected '<hexStart>-<hexEnd>-<segmentId>',"
                                        + " got: '" + descriptor + "'");
                    }
                    this.segmentRange = HashRange.of(
                            Integer.parseInt(descParts[0], 16),
                            Integer.parseInt(descParts[1], 16));
                    this.segmentId = Long.parseLong(descParts[2]);
                } else {
                    this.localName = rawLocalName;
                    this.segmentRange = null;
                    this.segmentId = -1;
                }

                if (this.domain == TopicDomain.segment) {
                    this.completeTopicName = String.format("%s://%s/%s/%s/%s",
                            domain, tenant, namespacePortion, localName,
                            String.format("%04x-%04x-%d", segmentRange.start(), segmentRange.end(), segmentId));
                } else {

View on GitHub (pinned to 820761864e)

Solutions

  1. Format the descriptor as '<hexStart>-<hexEnd>-<segmentId>', e.g. '0000-ffff-0' (hash bounds parsed as hex, segment id as decimal)
  2. Count the dash-separated fields: the descriptor must yield exactly 3 parts
  3. Build names with the same formatting the parser emits: String.format("%04x-%04x-%d", start, end, segmentId)

Example fix

// before
TopicName.get("segment://tenant/ns/my-topic/0000-ffff");
// after
TopicName.get("segment://tenant/ns/my-topic/0000-ffff-0");
Defensive patterns

Strategy: validation

Validate before calling

static boolean isSegmentDescriptorValid(String descriptor) {
    return descriptor != null && descriptor.matches("[0-9a-fA-F]+-[0-9a-fA-F]+-\\d+");
}
static String formatSegmentDescriptor(int hashStart, int hashEnd, long segmentId) {
    return String.format("%04x-%04x-%d", hashStart, hashEnd, segmentId);
}

Type guard

static boolean isWellFormedSegmentName(String name) {
    if (name == null || !name.contains("/")) return false;
    return isSegmentDescriptorValid(name.substring(name.lastIndexOf('/') + 1));
}

Try / catch

try {
    TopicName tn = TopicName.get(segmentTopic);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Invalid segment descriptor")) {
        throw new IllegalArgumentException("Descriptor must be <hexStart>-<hexEnd>-<segmentId>, e.g. 0000-ffff-0");
    }
    throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/d729631aeb4958d9. Report an issue: GitHub.