apache/pulsar · error · IllegalStateException

No active segments

Error message

No active segments

What it means

SegmentRouter.route() throws IllegalStateException when the supplied activeSegments list is empty. The router cannot pick a destination segment without at least one active segment, so routing a keyed message fails fast.

Source

Thrown at pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/SegmentRouter.java:55

    private final AtomicInteger roundRobinCounter = new AtomicInteger(0);

    /**
     * Route a message key to the segment that owns its hash range.
     *
     * <p>If every active segment is a legacy segment (synthetic layout for a not-yet-migrated
     * regular topic), the routing switches to {@code signSafeMod(murmurHash3_32(key), N)} over
     * {@code segment_id} so V5 producers route the same way v4 partitioned-topic producers do
     * — preserving per-key destinations while clients gradually upgrade.
     *
     * @param key the message key
     * @param activeSegments the currently active segments (sorted by hash range)
     * @return the segment ID to route to
     * @throws IllegalStateException if no segment covers the hash
     */
    long route(String key, List<ActiveSegment> activeSegments) {
        if (activeSegments.isEmpty()) {
            throw new IllegalStateException("No active segments");
        }
        if (allLegacy(activeSegments)) {
            return routeModN(key, activeSegments);
        }
        int hash = hash(key);
        for (var segment : activeSegments) {
            if (segment.hashRange().contains(hash)) {
                return segment.segmentId();
            }
        }
        throw new IllegalStateException("No segment covers hash " + hash + " for key: " + key);
    }

    /**
     * Route a message without a key using round-robin across active segments.
     */
    long routeRoundRobin(List<ActiveSegment> activeSegments) {
        if (activeSegments.isEmpty()) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Wait for the initial layout/DAG watch callback before routing; use eagerAttachInitialAsync to pre-populate segments.
  2. Guard the call: check activeSegments non-empty (or fall back to buffering/rejecting sends) before invoking route.
  3. If all segments are sealed, check topic state — the topic may be terminated and should not accept sends.
  4. Fix caller logic that empties the list via filters or stale snapshot handling.

Example fix

// before
long seg = router.route(key, activeSegments); // throws when empty
// after
if (activeSegments.isEmpty()) { throw new NoLayoutException("layout not ready"); }
long seg = router.route(key, activeSegments);
Defensive patterns

Strategy: validation

Validate before calling

if (activeSegments == null || activeSegments.isEmpty()) {
    throw new IllegalStateException("Layout not ready: no active segments to route key");
}
long seg = router.route(key, activeSegments);

Type guard

static boolean hasActiveSegments(List<SegmentRouter.ActiveSegment> s) {
    return s != null && !s.isEmpty();
}

Try / catch

try {
    long seg = router.route(key, activeSegments);
} catch (IllegalStateException e) {
    // layout not loaded yet: buffer or defer the message
}

Prevention

When it happens

Trigger: Calling route(key, activeSegments) (directly or through producer routing) with an empty list — e.g. layout not yet loaded, all segments sealed, or a caller passing a filtered/empty list of ActiveSegment.

Common situations: Producing immediately after topic creation before the first layout arrives; topic terminated or migrated leaving no active segments; bugs in caller code filtering the segment list to empty.

Related errors


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