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
- Log/inspect e.getCause() to find the real error; the wrapping exception's own message is not informative.
- If the cause is a connection problem, verify broker connectivity and client configuration before retrying close().
- Avoid sharing the producer across threads without synchronization so teardown does not race.
- 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
- Always inspect getCause() on PulsarClientException thrown from close()
- Do not close a producer concurrently with sends
- Prefer closeAsync() to observe the original failure type
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
- ServiceUrlProvider has already been initialized
- Authentication already closed.
- Close interrupted
- Interrupted while creating segment producer
- (wraps v4 producer error message)
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/4d033b1cd42c26b8.
Report an issue: GitHub.