apache/pulsar · error · RestException

Create subscription on non-persistent topic can only be done

Error message

Create subscription on non-persistent topic can only be done through client

What it means

createSubscription only works for persistent topics; for non-persistent topics subscriptions must be created through the client (they are ephemeral and not managed via cursors in the metadata store), so the admin endpoint returns HTTP 400.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/PersistentTopics.java:1989

                    ResetCursorData resetCursorData,
            @Parameter(description = "Is replicated required to perform this operation")
            @QueryParam("replicated") boolean replicated
    ) {
        try {
            validateTopicName(tenant, namespace, topic);
            String decodedSubName = decode(encodedSubName);
            // If subscription is as "a/b". The url of HTTP API that defined as
            // "{tenant}/{namespace}/{topic}/{subscription}" will be like below:
            // "public/default/tp/a/b", then the broker will assume it is a topic that
            // using the old rule "{tenant}/{cluster}/{namespace}/{topic}/{subscription}".
            // So denied to create a subscription that contains "/".
            if (pulsar().getConfig().isStrictlyVerifySubscriptionName()
                    && !NamedEntity.isAllowed(decodedSubName)) {
                throw new RestException(Response.Status.BAD_REQUEST, "Please let the subscription only contains"
                    + " '/w(a-zA-Z_0-9)' or '_', the current value is " + decodedSubName);
            }
            if (!topicName.isPersistent()) {
                throw new RestException(Response.Status.BAD_REQUEST, "Create subscription on non-persistent topic "
                        + "can only be done through client");
            }
            Map<String, String> subscriptionProperties = resetCursorData == null ? null :
                    resetCursorData.getProperties();
            MessageIdImpl messageId = resetCursorData == null ? null :
                    new MessageIdImpl(resetCursorData.getLedgerId(), resetCursorData.getEntryId(),
                            resetCursorData.getPartitionIndex());
            internalCreateSubscription(asyncResponse, decode(encodedSubName), messageId, authoritative,
                    replicated, subscriptionProperties);
        } catch (WebApplicationException wae) {
            asyncResponse.resume(wae);
        } catch (Exception e) {
            asyncResponse.resume(new RestException(e));
        }
    }

    @POST
    @Path("/{tenant}/{namespace}/{topic}/subscription/{subName}/resetcursor/{timestamp}")

View on GitHub (pinned to 820761864e)

Solutions

  1. Create the subscription by connecting a consumer/reader client with that subscription name to the non-persistent topic
  2. Switch to the persistent topic endpoint only for persistent topics
  3. Reconsider topic persistence if durable admin-managed subscriptions are required

Example fix

// before
admin.topics().createSubscription("non-persistent://public/default/t", "sub", MessageId.earliest);
// after
try (Consumer<byte[]> c = client.newConsumer().topic("non-persistent://public/default/t")
        .subscriptionName("sub").subscriptionInitialPosition(InitialPosition.Earliest).subscribe()) {}
Defensive patterns

Strategy: validation

Validate before calling

if (topic.startsWith("non-persistent://")) { createSubscriptionViaClient(topic, sub); return; }

Type guard

boolean isAdminSubscriptionCreationAllowed(String t) { return t != null && t.startsWith("persistent://"); }

Try / catch

try { admin.topics().createSubscription(topic, sub, pos); } catch (PulsarAdminException e) { if (e.getStatusCode() == 400) { /* fall back to client consumer creation */ } }

Prevention

When it happens

Trigger: PUT /admin/v2/{tenant}/{namespace}/{topic}/subscription/{subName} (non-persistent endpoint variant) with topicName.isPersistent() == false.

Common situations: Generic admin scripts that use the same subscription-creation code path for all topics; migrations from persistent to non-persistent topics; using PulsarAdmin against a non-persistent topic URL.

Related errors


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