apache/pulsar · error · IllegalArgumentException
Invalid topic name: %s. Topic local name must not be blank.
Error message
Invalid topic name: %s. Topic local name must not be blank.
What it means
TopicName's constructor parses a complete topic name into domain, tenant, namespace, and local name. If the final (local) name segment — the topic name itself, optionally with a -partition-N suffix — is blank or missing, the constructor throws this IllegalArgumentException to reject the malformed name early. Pulsar throws it because a topic without a local name can never resolve to a real topic.
Source
Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java:235
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 {
this.completeTopicName = completeTopicName;
}
}
if (StringUtils.isBlank(localName)) {
throw new IllegalArgumentException(String.format("Invalid topic name: %s. Topic local name must not"
+ " be blank.", completeTopicName));
}
this.partitionIndex = getPartitionIndex(localName);
this.namespaceName = NamespaceName.get(tenant, namespacePortion);
} catch (NullPointerException e) {
throw new IllegalArgumentException("Invalid topic name: " + completeTopicName, e);
}
}
public boolean isPersistent() {
return TopicDomain.persistent == domain || TopicDomain.topic == domain || TopicDomain.segment == domain;
}
public boolean isScalable() {
return TopicDomain.topic == domain;
}
public boolean isSegment() {View on GitHub (pinned to 820761864e)
Solutions
- Supply the full V2 topic name '<domain>://<tenant>/<namespace>/<local-name>', e.g. persistent://public/default/my-topic
- Check the source config/env value for truncation or a missing final segment
- If building names in code, validate the local segment is non-blank before calling TopicName.get()
Example fix
// before
TopicName.get("persistent://public/default/");
// after
TopicName.get("persistent://public/default/my-topic"); Defensive patterns
Strategy: validation
Validate before calling
public static void requireValidTopicUrl(String url) {
if (url == null || url.chars().filter(c -> c == ':').count() == 0) throw new IllegalArgumentException("missing domain scheme");
String path = url.substring(url.indexOf("://") + 3);
String[] parts = path.split("/");
if (parts.length != 3 || parts[2].trim().isEmpty()) throw new IllegalArgumentException("local topic name must not be blank: " + url);
} Type guard
public static boolean isCompleteTopicName(String s) {
if (s == null) return false;
int i = s.indexOf("://");
if (i < 0) return false;
String[] parts = s.substring(i + 3).split("/");
return parts.length == 3 && !parts[2].trim().isEmpty();
} Try / catch
try {
TopicName tn = TopicName.get(candidate);
// use tn
} catch (IllegalArgumentException e) {
log.error("Rejected topic name '{}': {}", candidate, e.getMessage());
} Prevention
- Always use full V2 names: domain://tenant/namespace/topic
- Never build topic URLs by naive string concatenation of possibly-empty variables
- Validate with TopicName.isValid(topicName) or a split-segment check before calling TopicName.get
- Watch for truncated config values when copying topic URLs
When it happens
Trigger: Calling TopicName.get() (or the constructor) with a name whose path ends right after the namespace, e.g. 'persistent://my-tenant/my-ns', 'persistent://my-tenant/my-ns/', or a name where the local segment is whitespace. Also produced by callers that build topic names by string concatenation and drop the topic part.
Common situations: Configuration files or environment variables with a truncated topic URL (copy/paste cut off); templates where a ${topic} placeholder was left empty; programmatic name building from split strings where the last element was lost; REST/client calls passing a namespace URL instead of a topic URL.
Related errors
- configuredService should not be an instance of SystemTopicBa
- Segment not found
- Both segments must be active
- Segments are not adjacent: ${hashRange1} and ${hashRange2}
- Cannot rebucket non-active segment: ${segmentId}
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/9bdb8ab619456e10.
Report an issue: GitHub.