apache/pulsar · error · PulsarClientException

(wraps underlying failure cause)

Error message

(wraps underlying failure cause)

What it means

When closeAsync() completes exceptionally with a non-PulsarClientException cause, close() wraps the underlying cause in a plain PulsarClientException. The message is the cause's toString/inheritance default, so inspect getCause() for the real failure.

Source

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

    @Override
    public AsyncProducer<T> async() {
        return asyncView;
    }

    @Override
    public void close() throws PulsarClientException {
        try {
            closeAsync().get();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new PulsarClientException("Close interrupted", e);
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof PulsarClientException pce) {
                throw pce;
            }
            throw new PulsarClientException(cause);
        }
    }

    /**
     * Send a message synchronously with routing. Called by MessageBuilderV5.
     * Returns a MessageIdV5 that includes the segment ID for ack routing.
     */
    MessageIdV5 sendInternal(
            String key, T value, java.util.Map<String, String> properties,
            java.time.Instant eventTime, Long sequenceId,
            java.time.Duration deliverAfter, java.time.Instant deliverAt,
            java.util.List<String> replicationClusters,
            org.apache.pulsar.client.api.v5.Transaction txn) throws PulsarClientException {

        PulsarClientException lastError = null;
        for (int attempt = 0; attempt < SEND_RETRY_MAX_ATTEMPTS; attempt++) {
            long segmentId = routeMessage(key);
            try {

View on GitHub (pinned to 820761864e)

Solutions

  1. Log/inspect e.getCause() to find the real error; the wrapping exception's own message is not informative.
  2. If the cause is a connection problem, verify broker connectivity and client configuration before retrying close().
  3. Avoid sharing the producer across threads without synchronization so teardown does not race.
  4. If persistent, prefer closeAsync() to observe the original failure type directly.

Example fix

// before
try { producer.close(); } catch (PulsarClientException e) { log.error(e.getMessage()); }
// after
try { producer.close(); } catch (PulsarClientException e) { log.error("close failed", e.getCause() != null ? e.getCause() : e); }
Defensive patterns

Strategy: try-catch

Type guard

static boolean hasCause(Throwable e) { return e != null && e.getCause() != null; }

Try / catch

try {
    producer.close();
} catch (PulsarClientException e) {
    Throwable cause = e.getCause();
    log.error("Producer close failed", cause != null ? cause : e);
}

Prevention

When it happens

Trigger: closeAsync()'s internal future fails with an unexpected throwable (e.g. NPE, IllegalStateException from segment-producer bookkeeping) rather than a PulsarClientException; the synchronous close() then rethrows it wrapped.

Common situations: Internal bugs during segment producer teardown; concurrent close() and routeMessage() on the same producer; broker connection failures surfaced as runtime exceptions from the v4 client.

Related errors


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