apache/pulsar · error · IllegalArgumentException
${topic} has blank local topic
Error message
${topic} has blank local topic What it means
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.
Source
Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java:565
* @return the full topic name.
*/
public static String toFullTopicName(String topic) {
final int index = topic.indexOf("://");
if (index >= 0) {
TopicDomain.getEnum(topic.substring(0, index));
final List<String> parts = splitBySlash(topic.substring(index + "://".length()), 4);
if (parts.size() == 4) {
throw new IllegalArgumentException(
"V1 topic names (with cluster component) are no longer supported. "
+ "Please use the V2 format: '<domain>://tenant/namespace/topic'. Got: " + topic);
}
if (parts.size() != 3) {
throw new IllegalArgumentException(topic + " is invalid. "
+ "Expected format: '<domain>://tenant/namespace/topic'");
}
NamespaceName.validateNamespaceName(parts.get(0), parts.get(1));
if (StringUtils.isBlank(parts.get(2))) {
throw new IllegalArgumentException(topic + " has blank local topic");
}
return topic; // it's a valid full topic name
} else {
List<String> parts = splitBySlash(topic, 0);
if (parts.size() != 1 && parts.size() != 3) {
throw new IllegalArgumentException(topic + " is invalid");
}
if (parts.size() == 1) {
if (StringUtils.isBlank(parts.get(0))) {
throw new IllegalArgumentException(topic + " has blank local topic");
}
return "persistent://public/default/" + parts.get(0);
} else {
NamespaceName.validateNamespaceName(parts.get(0), parts.get(1));
if (StringUtils.isBlank(parts.get(2))) {
throw new IllegalArgumentException(topic + " has blank local topic");
}
return "persistent://" + topic;View on GitHub (pinned to 820761864e)
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
Example fix
// before
String url = "persistent://public/default/" + maybeNullTopic; // blank
TopicName.toFullTopicName(url);
// after
if (StringUtils.isNotBlank(topic)) { TopicName.toFullTopicName("persistent://public/default/" + topic); } Defensive patterns
Strategy: validation
Validate before calling
public static boolean hasLocalTopic(String topicUrl) {
int i = topicUrl.indexOf("://");
return i >= 0 && topicUrl.substring(i + 3).split("/").length == 3
&& !topicUrl.substring(i + 3).split("/")[2].trim().isEmpty();
} Try / catch
try {
String full = TopicName.toFullTopicName(url);
} catch (IllegalArgumentException e) {
if (e.getMessage().endsWith("has blank local topic")) {
throw new ConfigException("Topic portion of '" + url + "' is empty; set the topic name");
}
} Prevention
- 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
When it happens
Trigger: Calling toFullTopicName() with a trailing-slash URL like 'persistent://my-tenant/my-ns/' or a URL where the topic part is whitespace.
Common situations: 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.
Related errors
- Invalid topic name: %s. Topic local name must not be blank.
- Invalid topic name: ${completeTopicName}
- V1 topic names (with cluster component) are no longer suppor
- ${topic} is invalid. Expected format: '<domain>://tenant/nam
- ${topic} is invalid
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/531be9e68e913bfe.
Report an issue: GitHub.