3434 * If the first subscriber fails to consume all the data (e.g., due to early cancellation or errors),
3535 * subsequent retry attempts will fail since the complete data set is not available for resubscription.</p>
3636 *
37- * <p><b>Usage Example :</b></p>
37+ * <p><b>Usage Examples :</b></p>
3838 * {@snippet :
39+ * // Simple usage (default behavior, immediate send):
3940 * AsyncRequestBody originalBody = AsyncRequestBody.fromString("Hello World");
4041 * BufferedSplittableAsyncRequestBody retryableBody =
4142 * BufferedSplittableAsyncRequestBody.create(originalBody);
43+ *
44+ * // With full buffering enabled for slow streaming sources:
45+ * BufferedSplittableAsyncRequestBody fullBufferedBody =
46+ * BufferedSplittableAsyncRequestBody.builder()
47+ * .asyncRequestBody(originalBody)
48+ * .fullBufferingEnabled(true)
49+ * .build();
4250 * }
4351 *
4452 * <p><b>Performance Considerations:</b></p>
@@ -56,44 +64,33 @@ public final class BufferedSplittableAsyncRequestBody implements AsyncRequestBod
5664 private final AsyncRequestBody delegate ;
5765 private final boolean fullBufferingEnabled ;
5866
59- private BufferedSplittableAsyncRequestBody (AsyncRequestBody delegate , boolean fullBufferingEnabled ) {
60- this .delegate = delegate ;
61- this .fullBufferingEnabled = fullBufferingEnabled ;
67+ private BufferedSplittableAsyncRequestBody (Builder builder ) {
68+ this .delegate = Validate . paramNotNull ( builder . asyncRequestBody , "asyncRequestBody" ) ;
69+ this .fullBufferingEnabled = builder . fullBufferingEnabled ;
6270 }
6371
6472 /**
6573 * Creates a new {@link BufferedSplittableAsyncRequestBody} that wraps the provided {@link AsyncRequestBody}.
6674 *
6775 * <p>Full buffering is disabled by default. Each part is sent to the downstream subscriber immediately
68- * upon initialization in the known-content-length path (existing behavior) .
76+ * upon initialization in the known-content-length path.
6977 *
7078 * @param delegate the {@link AsyncRequestBody} to wrap and make retryable. Must not be null.
7179 * @return a new {@link BufferedSplittableAsyncRequestBody} instance
7280 * @throws NullPointerException if delegate is null
7381 */
7482 public static BufferedSplittableAsyncRequestBody create (AsyncRequestBody delegate ) {
7583 Validate .paramNotNull (delegate , "delegate" );
76- return new BufferedSplittableAsyncRequestBody ( delegate , false );
84+ return builder (). asyncRequestBody ( delegate ). build ( );
7785 }
7886
7987 /**
80- * Creates a new {@link BufferedSplittableAsyncRequestBody} that wraps the provided {@link AsyncRequestBody},
81- * with an option to enable full buffering before sending parts downstream.
82- *
83- * <p>When {@code fullBufferingEnabled} is {@code true}, each part is fully buffered before being sent to the
84- * downstream subscriber. This guarantees that the retry buffer is always populated before the HTTP layer
85- * subscribes, making per-part retry deterministically successful for slow streaming sources (e.g., SFTP).
88+ * Returns a new {@link Builder} for creating a {@link BufferedSplittableAsyncRequestBody} with configuration options.
8689 *
87- * <p>When {@code fullBufferingEnabled} is {@code false}, behavior is identical to {@link #create(AsyncRequestBody)}.
88- *
89- * @param delegate the {@link AsyncRequestBody} to wrap and make retryable. Must not be null.
90- * @param fullBufferingEnabled whether to enable full buffering before sending parts downstream
91- * @return a new {@link BufferedSplittableAsyncRequestBody} instance
92- * @throws NullPointerException if delegate is null
90+ * @return a new builder instance
9391 */
94- public static BufferedSplittableAsyncRequestBody create (AsyncRequestBody delegate , boolean fullBufferingEnabled ) {
95- Validate .paramNotNull (delegate , "delegate" );
96- return new BufferedSplittableAsyncRequestBody (delegate , fullBufferingEnabled );
92+ public static Builder builder () {
93+ return new Builder ();
9794 }
9895
9996 @ Override
@@ -136,4 +133,55 @@ public void subscribe(Subscriber<? super ByteBuffer> s) {
136133 public String body () {
137134 return delegate .body ();
138135 }
136+
137+ /**
138+ * Builder for {@link BufferedSplittableAsyncRequestBody}.
139+ */
140+ public static final class Builder {
141+ private AsyncRequestBody asyncRequestBody ;
142+ private boolean fullBufferingEnabled = false ;
143+
144+ private Builder () {
145+ }
146+
147+ /**
148+ * Sets the {@link AsyncRequestBody} to wrap and make retryable.
149+ *
150+ * @param asyncRequestBody the request body to wrap. Must not be null.
151+ * @return this builder for method chaining
152+ */
153+ public Builder asyncRequestBody (AsyncRequestBody asyncRequestBody ) {
154+ this .asyncRequestBody = asyncRequestBody ;
155+ return this ;
156+ }
157+
158+ /**
159+ * Configures whether to fully buffer each part before sending it downstream.
160+ *
161+ * <p>When enabled, each part is fully buffered before being sent to the downstream subscriber.
162+ * This guarantees that the retry buffer is always populated before the HTTP layer subscribes,
163+ * making per-part retry deterministically successful for slow streaming sources (e.g., SFTP).
164+ *
165+ * <p>When disabled (the default), each part is sent immediately upon initialization in the
166+ * known-content-length path, allowing the HTTP connection to open while data is still arriving.
167+ *
168+ * @param fullBufferingEnabled whether to enable full buffering before sending parts downstream.
169+ * Defaults to {@code false}.
170+ * @return this builder for method chaining
171+ */
172+ public Builder fullBufferingEnabled (boolean fullBufferingEnabled ) {
173+ this .fullBufferingEnabled = fullBufferingEnabled ;
174+ return this ;
175+ }
176+
177+ /**
178+ * Builds a new {@link BufferedSplittableAsyncRequestBody} instance.
179+ *
180+ * @return a new instance configured by this builder
181+ * @throws NullPointerException if asyncRequestBody is null
182+ */
183+ public BufferedSplittableAsyncRequestBody build () {
184+ return new BufferedSplittableAsyncRequestBody (this );
185+ }
186+ }
139187}
0 commit comments