apache/pulsar · error · RestException

Cannot terminate non-persistent topic: ${segmentTopic}

Error message

Cannot terminate non-persistent topic: ${segmentTopic}

What it means

HTTP 400 thrown by terminateSegment when the loaded topic instance is not a PersistentTopic. Termination (sealing the ledger so no more writes are accepted) is only defined for persistent topics, so a non-persistent instance is rejected as a bad request.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/Segments.java:169

            @Parameter(description = "Segment descriptor (e.g. 0000-7fff-1)", required = true)
            @PathParam("descriptor") String descriptor,
            @Parameter(description = "Whether leader broker redirected this call to this broker.")
            @QueryParam("authoritative") @DefaultValue("false") boolean authoritative) {
        validateNamespaceName(tenant, namespace);
        TopicName segmentTopic = segmentTopicName(tenant, namespace, encodedTopic, descriptor);

        validateSuperUserAccessAsync()
                .thenCompose(__ -> validateTopicOwnershipAsync(segmentTopic, authoritative))
                .thenCompose(__ -> pulsar().getBrokerService().getTopicIfExists(segmentTopic.toString()))
                .thenCompose(optTopic -> {
                    if (optTopic.isEmpty()) {
                        throw new RestException(Response.Status.NOT_FOUND,
                                "Segment topic not found: " + segmentTopic);
                    }
                    if (optTopic.get() instanceof PersistentTopic pt) {
                        return pt.terminate().thenApply(__ -> null);
                    }
                    throw new RestException(Response.Status.BAD_REQUEST,
                            "Cannot terminate non-persistent topic: " + segmentTopic);
                })
                .thenAccept(__ -> {
                    log.info().attr("clientAppId", clientAppId()).attr("segment", segmentTopic)
                            .log("Terminated segment topic");
                    asyncResponse.resume(Response.noContent().build());
                })
                .exceptionally(ex -> {
                    log.error().attr("clientAppId", clientAppId()).attr("segment", segmentTopic)
                            .exception(ex).log("Failed to terminate segment topic");
                    resumeAsyncResponseExceptionally(asyncResponse, ex);
                    return null;
                });
    }

    @PUT
    @Path("/{tenant}/{namespace}/{topic}/{descriptor}/subscription/{subscription}")
    @Operation(summary = "Create a subscription cursor on the segment topic at the earliest"

View on GitHub (pinned to 820761864e)

Solutions

  1. Ensure the segment topic is addressed with the persistent:// domain — segment topics are always persistent.
  2. Fix the code building the segment topic name (SegmentTopicName.fromParent) or the URL path so it targets the persistent topic.
  3. If you need to stop writes on a non-persistent topic, disconnect producers instead — terminate does not apply.

Example fix

// before
TopicName seg = TopicName.get("non-persistent", ns, localName); // 400
// after
TopicName seg = TopicName.get("persistent", ns, localName);
Defensive patterns

Strategy: validation

Validate before calling

if (!segmentTopic.toString().startsWith('persistent://')) {
  throw new Error('segment topics must use the persistent:// domain');
}

Type guard

const isPersistentTopicName = (tn) => typeof tn === 'string' && tn.startsWith('persistent://');

Try / catch

try {
  await admin.scalableTopics().terminateSegment(segmentTopic);
} catch (e) {
  if (e.status === 400 && /non-persistent/.test(e.message)) { /* fix domain prefix */ }
  throw e;
}

Prevention

When it happens

Trigger: Calling the segment terminate endpoint against a segment topic that materialized as a non-persistent topic — e.g. the segment name maps to a non-persistent domain topic or a misconfigured topic domain, so the broker loaded a NonPersistentTopic instead.

Common situations: Constructing the segment topic name with the wrong domain prefix (non-persistent:// instead of persistent://); namespace-level policies or custom pluggable topic types yielding a different Topic implementation; test setups using non-persistent topics interchangeably.

Related errors


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