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

  1. Append a non-blank topic name after the namespace
  2. Fix unresolved template placeholders in config
  3. 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

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


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