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

  1. Supply the full V2 topic name '<domain>://<tenant>/<namespace>/<local-name>', e.g. persistent://public/default/my-topic
  2. Check the source config/env value for truncation or a missing final segment
  3. 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

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


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