apache/pulsar · error · IllegalArgumentException

V1 topic names (with cluster component) are no longer suppor

Error message

V1 topic names (with cluster component) are no longer supported. Please use the V2 format: '<domain>://tenant/namespace/topic'. Got: ${completeTopicName}

What it means

Pulsar V1 topic names embedded a cluster component: 'domain://cluster/tenant/namespace/topic' (4 path parts after '://'). The parser rejects this shape outright and asks callers to migrate to the V2 format 'domain://tenant/namespace/topic'. It is thrown from the TopicName constructor whenever a fully-qualified name splits into 4 parts after the scheme.

Source

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

                } else {
                    throw new IllegalArgumentException(
                        "Invalid short topic name '" + completeTopicName + "', it should be in the format of "
                        + "<tenant>/<namespace>/<topic> or <topic>");
                }
                this.segmentRange = null;
                this.segmentId = -1;
                this.completeTopicName = domain.name() + "://" + tenant + "/" + namespacePortion + "/" + localName;
            } else {
                this.domain = TopicDomain.getEnum(completeTopicName.substring(0, index));
                // Scalable topic domains (topic://, segment://) only support the new format
                // and local names may contain '/', so use limit(3) to keep the rest as localName.
                boolean isScalableDomain = this.domain == TopicDomain.topic
                        || this.domain == TopicDomain.segment;
                int splitLimit = isScalableDomain ? 3 : 4;
                List<String> parts = splitBySlash(completeTopicName.substring(index + "://".length()),
                        splitLimit);
                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: "
                                    + completeTopicName);
                } else if (parts.size() != 3) {
                    throw new IllegalArgumentException("Invalid topic name " + completeTopicName);
                }
                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);

View on GitHub (pinned to 820761864e)

Solutions

  1. Remove the cluster component: 'persistent://clusterX/tenant/ns/topic' -> 'persistent://tenant/ns/topic'
  2. Re-derive topic names from current cluster config instead of legacy stored values
  3. If reading legacy data, map old names to V2 names with a migration/shim before calling TopicName.get

Example fix

// before
TopicName topic = TopicName.get("persistent://us-west/my-tenant/my-ns/my-topic");
// after
TopicName topic = TopicName.get("persistent://my-tenant/my-ns/my-topic");
Defensive patterns

Strategy: validation

Validate before calling

static String stripV1Cluster(String topic) {
    java.util.regex.Matcher m = java.util.regex.Pattern
        .compile("^([a-z]+)://[^/]+/([^/]+/[^/]+/[^/]+)$").matcher(topic);
    if (m.matches()) {
        throw new IllegalArgumentException("V1 topic name not supported: " + topic);
    }
    return topic;
}

Type guard

static boolean isV2TopicName(String name) {
    java.util.regex.Matcher m = java.util.regex.Pattern
        .compile("^[a-z]+://[^/]+/[^/]+/[^/]+$").matcher(name == null ? "" : name);
    return m.matches();
}

Try / catch

try {
    TopicName tn = TopicName.get(topic);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("V1 topic names")) {
        topic = migrateV1ToV2(topic); // drop cluster component, retry once
        TopicName tn = TopicName.get(topic);
    } else throw e;
}

Prevention

When it happens

Trigger: Calling TopicName.get('persistent://clusterX/tenant/ns/topic') or any API that resolves a topic name containing a legacy cluster segment after '://'.

Common situations: Migrating from very old Pulsar clusters (pre-2.x V1 naming); configs, scripts, or bookmarked topic URLs copied from an old deployment; broker/standalone configs carried over across a version upgrade.

Related errors


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