apache/pulsar · error · PulsarClientException

(wraps v4 producer send failure message)

Error message

(wraps v4 producer send failure message)

What it means

During sendInternal, if the underlying v4 (per-segment) producer's send() fails with an error that is not a 'segment gone' condition, the failure is immediately rethrown as a PulsarClientException carrying the original message. Only segment-gone errors are retried; everything else propagates on the first attempt.

Source

Thrown at pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/ScalableTopicProducer.java:219

        for (int attempt = 0; attempt < SEND_RETRY_MAX_ATTEMPTS; attempt++) {
            long segmentId = routeMessage(key);
            try {
                var producer = getOrCreateSegmentProducer(segmentId);
                var v4MsgId = buildV4Message(producer, key, value, properties,
                        eventTime, sequenceId, deliverAfter, deliverAt, replicationClusters, txn)
                        .send();
                return new MessageIdV5(v4MsgId, segmentId);
            } catch (PulsarClientException e) {
                // Thrown while (re)creating the per-segment producer — already a V5 exception
                // (it may wrap a v4 TopicTerminated/AlreadyClosed cause).
                if (!isSegmentGoneError(e)) {
                    throw e;
                }
                lastError = e;
            } catch (org.apache.pulsar.client.api.PulsarClientException e) {
                // Thrown by the v4 producer's send().
                if (!isSegmentGoneError(e)) {
                    throw new PulsarClientException(e.getMessage(), e);
                }
                lastError = new PulsarClientException(e.getMessage(), e);
            }
            // The target segment is gone: sealed by a split/merge, or terminated by a
            // regular-to-scalable migration. Drop the stale per-segment producer and wait
            // for the DAG watch to deliver the new layout; routeMessage on the next attempt
            // lands on an active child.
            log.info().attr("segmentId", segmentId).attr("attempt", attempt + 1)
                    .log("Target segment gone, waiting for layout update");
            segmentProducers.remove(segmentId);
            try {
                Thread.sleep(Math.min(100L * (attempt + 1), SEND_RETRY_MAX_BACKOFF_MS));
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                throw new PulsarClientException("Interrupted while waiting for layout update", ie);
            }
        }
        throw lastError != null ? lastError

View on GitHub (pinned to 820761864e)

Solutions

  1. Inspect the message/cause: for broker unavailability check serviceUrl and broker health before retrying.
  2. If the topic was actually migrated/sealed but isSegmentGoneError() misclassified it, upgrade the library or widen the segment-gone detection pattern.
  3. Renew credentials if the cause is authentication/authorization.
  4. Wrap send with application-level retry with backoff for transient broker errors, since sendInternal does not retry non-segment-gone errors.

Example fix

// before
producer.send(msg); // throws on transient broker error
// after
try {
    producer.send(msg);
} catch (PulsarClientException e) {
    if (isRetryable(e)) { retryWithBackoff(msg); } else { throw e; }
}
Defensive patterns

Strategy: retry

Validate before calling

// before sending
if (producer == null || topicState == TERMINATED) { throw new IllegalStateException("topic unavailable"); }

Try / catch

try {
    producer.send(msg);
} catch (PulsarClientException e) {
    if (isTransient(e)) { retryWithBackoff(msg); } else { throw e; }
}

Prevention

When it happens

Trigger: Calling send()/MessageBuilderV5 send where the v4 segment producer fails for reasons like topic terminated, producer fenced, authentication failure, or broker unavailable, and the error does not match the isSegmentGoneError() heuristic.

Common situations: Broker restarts or network partitions mid-send; topic deleted while a scalable producer is attached; auth token expiry; producing after a regular-to-scalable migration misdetected as a normal failure.

Related errors


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