{"record":{"id":"8df99480ce87e9a4","repo":"apache/pulsar","slug":"wraps-v4-producer-send-failure-message","errorCode":null,"errorMessage":"(wraps v4 producer send failure message)","messagePattern":"\\(wraps v4 producer send failure message\\)","errorType":"exception","errorClass":"PulsarClientException","httpStatus":null,"severity":"error","filePath":"pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/ScalableTopicProducer.java","lineNumber":219,"sourceCode":"        for (int attempt = 0; attempt < SEND_RETRY_MAX_ATTEMPTS; attempt++) {\n            long segmentId = routeMessage(key);\n            try {\n                var producer = getOrCreateSegmentProducer(segmentId);\n                var v4MsgId = buildV4Message(producer, key, value, properties,\n                        eventTime, sequenceId, deliverAfter, deliverAt, replicationClusters, txn)\n                        .send();\n                return new MessageIdV5(v4MsgId, segmentId);\n            } catch (PulsarClientException e) {\n                // Thrown while (re)creating the per-segment producer — already a V5 exception\n                // (it may wrap a v4 TopicTerminated/AlreadyClosed cause).\n                if (!isSegmentGoneError(e)) {\n                    throw e;\n                }\n                lastError = e;\n            } catch (org.apache.pulsar.client.api.PulsarClientException e) {\n                // Thrown by the v4 producer's send().\n                if (!isSegmentGoneError(e)) {\n                    throw new PulsarClientException(e.getMessage(), e);\n                }\n                lastError = new PulsarClientException(e.getMessage(), e);\n            }\n            // The target segment is gone: sealed by a split/merge, or terminated by a\n            // regular-to-scalable migration. Drop the stale per-segment producer and wait\n            // for the DAG watch to deliver the new layout; routeMessage on the next attempt\n            // lands on an active child.\n            log.info().attr(\"segmentId\", segmentId).attr(\"attempt\", attempt + 1)\n                    .log(\"Target segment gone, waiting for layout update\");\n            segmentProducers.remove(segmentId);\n            try {\n                Thread.sleep(Math.min(100L * (attempt + 1), SEND_RETRY_MAX_BACKOFF_MS));\n            } catch (InterruptedException ie) {\n                Thread.currentThread().interrupt();\n                throw new PulsarClientException(\"Interrupted while waiting for layout update\", ie);\n            }\n        }\n        throw lastError != null ? lastError","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/ScalableTopicProducer.java#L201-L237","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the message/cause: for broker unavailability check serviceUrl and broker health before retrying.","If the topic was actually migrated/sealed but isSegmentGoneError() misclassified it, upgrade the library or widen the segment-gone detection pattern.","Renew credentials if the cause is authentication/authorization.","Wrap send with application-level retry with backoff for transient broker errors, since sendInternal does not retry non-segment-gone errors."],"exampleFix":"// before\nproducer.send(msg); // throws on transient broker error\n// after\ntry {\n    producer.send(msg);\n} catch (PulsarClientException e) {\n    if (isRetryable(e)) { retryWithBackoff(msg); } else { throw e; }\n}","handlingStrategy":"retry","validationCode":"// before sending\nif (producer == null || topicState == TERMINATED) { throw new IllegalStateException(\"topic unavailable\"); }","typeGuard":null,"tryCatchPattern":"try {\n    producer.send(msg);\n} catch (PulsarClientException e) {\n    if (isTransient(e)) { retryWithBackoff(msg); } else { throw e; }\n}","preventionTips":["Implement application-level retry with backoff for transient broker errors","Keep broker connectivity and credentials valid (token renewal)","Upgrade the library if segment-gone detection misclassifies migration errors"],"tags":["send","retry","pulsar-client","broker"],"backgroundTag":"producer-send-failed","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}