apache/pulsar · error · RestException

${legacy} legacy v4 client connection(s) still attached to $

Error message

${legacy} legacy v4 client connection(s) still attached to ${persistentBase}; disconnect them (or all clients are V5) before migrating, or retry with force=true

What it means

HTTP 409 CONFLICT raised by the migration precheck when legacy protocol-v4 client connections are still attached to the source topic and force was not set. Migration to a scalable topic requires all consumers/producers to be on the v5 protocol, since v4 clients cannot talk to scalable topics.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/ScalableTopics.java:379

        }
        // For a partitioned topic, inspect per-partition stats rather than the aggregate:
        // aggregation merges publishers by producer name into fresh stat objects that drop
        // per-connection metadata, which would hide the V5-managed marker and make every
        // V5 connection look like a legacy v4 one.
        final CompletableFuture<Long> legacyCount = partitions > 0
                ? admin.topics().getPartitionedStatsAsync(persistentBase.toString(), true)
                        .thenApply(stats -> {
                            long count = 0;
                            for (TopicStats partitionStats : stats.getPartitions().values()) {
                                count += countLegacyConnections(partitionStats);
                            }
                            return count;
                        })
                : admin.topics().getStatsAsync(persistentBase.toString())
                        .thenApply(ScalableTopics::countLegacyConnections);
        return legacyCount.thenAccept(legacy -> {
            if (legacy > 0) {
                throw new RestException(Response.Status.CONFLICT,
                        legacy + " legacy v4 client connection(s) still attached to " + persistentBase
                                + "; disconnect them (or all clients are V5) before migrating, "
                                + "or retry with force=true");
            }
        });
    }

    private static long countLegacyConnections(TopicStats stats) {
        long count = 0;
        for (var publisher : stats.getPublishers()) {
            if (!isV5Managed(publisher.getMetadata())) {
                count++;
            }
        }
        for (var subscription : stats.getSubscriptions().values()) {
            for (var consumer : subscription.getConsumers()) {
                if (!isV5Managed(consumer.getMetadata())) {
                    count++;

View on GitHub (pinned to 820761864e)

Solutions

  1. Upgrade all connected clients to a protocol-v5-capable Pulsar client and let old connections drain, then retry migration.
  2. Identify which apps hold the legacy connections via topic stats and restart/upgrade them.
  3. Re-run the migration with force=true to bypass the precheck only if you accept v4 clients failing afterwards (they cannot consume from the scalable topic).

Example fix

// before
await admin.scalableTopics().migrateToScalable(tenant, ns, topic); // 409: legacy clients
// after
await admin.scalableTopics().migrateToScalable(tenant, ns, topic, /*force*/ false);
// ...or after upgrading clients, or deliberately:
await admin.scalableTopics().migrateToScalable(tenant, ns, topic, /*force*/ true);
Defensive patterns

Strategy: retry

Validate before calling

const stats = await admin.topics().getStatsAsync(`persistent://${tenant}/${ns}/${topic}`);
const legacy = countLegacyConnections(stats);
if (legacy > 0) throw new Error(`${legacy} legacy v4 clients connected; upgrade them or use force=true`);

Try / catch

try {
  await admin.scalableTopics().migrateToScalable(tenant, ns, topic);
} catch (e) {
  if (e.status === 409 && /legacy v4 client/.test(e.message)) {
    await waitForClientsToUpgrade();
    return admin.scalableTopics().migrateToScalable(tenant, ns, topic); // retry once
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling POST .../migrate without force=true while getStats-connection counting finds one or more v4 (legacy) client connections on the source topic or its partitions.

Common situations: Rolling out scalable-topic migration while old SDK versions (pre-v5 protocol) still run in production; forgotten background consumers (e.g. monitoring/backup apps) pinned to old client libs; canary environments sharing a topic with legacy services.

Related errors


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