apache/pulsar · error · RestException

Segment topic not found: ${segmentTopic}

Error message

Segment topic not found: ${segmentTopic}

What it means

HTTP 404 thrown by the segment terminate endpoint when the segment topic resolves but is not loaded on any broker (getTopicIfExists returns empty). Segment topics are never auto-created, and terminate requires a live in-memory topic instance on the owning broker.

Source

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

            @Parameter(description = "Specify the tenant", required = true)
            @PathParam("tenant") String tenant,
            @Parameter(description = "Specify the namespace", required = true)
            @PathParam("namespace") String namespace,
            @Parameter(description = "Specify the parent topic name", required = true)
            @PathParam("topic") @Encoded String encodedTopic,
            @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;

View on GitHub (pinned to 820761864e)

Solutions

  1. Verify the segment topic name (hash range + segment id) against the scalable topic's metadata.
  2. Trigger load of the segment topic (e.g. produce/consume once or use the admin API to look it up) and retry termination.
  3. Confirm the segment still exists in the scalable topic metadata before terminating; if deleted, skip it.

Example fix

// before
await admin.scalableTopics().terminateSegment(segmentTopicName); // 404 not loaded
// after
const md = await admin.scalableTopics().getScalableTopicMetadataAsync(tenant, ns, topic);
if (md.getSegments().containsKey(segmentId)) {
    await admin.scalableTopics().terminateSegment(segmentTopicName);
}
Defensive patterns

Strategy: retry

Validate before calling

const md = await admin.scalableTopics().getScalableTopicMetadataAsync(tenant, ns, parentTopic);
if (!md.getSegments().containsKey(segmentId)) throw new Error('segment does not exist in metadata');

Type guard

const segmentIsKnown = (metadata, segId) => metadata?.getSegments?.().has(segId) ?? false;

Try / catch

try {
  await admin.scalableTopics().terminateSegment(segmentTopic);
} catch (e) {
  if (e.status === 404) { await sleep(loadRetryMs); return admin.scalableTopics().terminateSegment(segmentTopic); }
  throw e;
}

Prevention

When it happens

Trigger: Calling the terminateSegment REST endpoint for a segment whose topic has not been loaded/owned by this broker path — e.g. after broker restart, before first use, after the segment was deleted, or a wrong segment hash-range/segment-id in the composed segment name.

Common situations: Terminating a segment of a freshly created scalable topic whose segment topic was unloaded; race between segment deletion and a terminate call; mistyped segment topic name; broker ownership moved and the redirect/ownership check passed but load hadn't completed.

Related errors


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