Is your feature request related to a problem? Please describe.
Java SDKs need a consistent synchronous streaming API shape. The initial Search implementation uses a push-based listener, while #50081 proposes a pull-based CloseableIterableStream<T>. This is a child of #50264.
Describe the solution you'd like
The architectural choice is between a push-based listener that keeps stream ownership and cleanup inside the SDK, and a pull-based CloseableIterableStream that provides idiomatic iteration while explicitly requiring callers to manage the resource; a plain or subclassed IterableStream cannot safely represent early-termination ownership.
Choose and document one synchronous streaming pattern:
| Criterion |
Push-based listener |
Raw IterableStream<T> |
Subclassed IterableStream<T> |
CloseableIterableStream<T> |
| Consumption and ownership |
SDK pushes events and owns the response |
Caller pulls; ownership is unspecified |
Caller pulls; subclass manages hidden response ownership |
Caller pulls and explicitly owns a closeable result |
| Java ergonomics |
Callbacks; no for-each or Java Stream |
Supports for-each and stream() |
Supports for-each and stream() |
Supports both with outer try-with-resources |
| Full-consumption cleanup |
SDK closes on completion |
Not guaranteed by the type |
Iterator can close on exhaustion; onClose covers an explicitly closed Java stream |
Try-with-resources closes deterministically |
| Early exit or processing failure |
Safe if cancellation and callback-failure semantics are defined |
Cannot close through the API |
Java stream can be closed, but early for-each exit remains uncloseable |
Safe with try-with-resources |
| Lifecycle contract visibility |
Clear SDK ownership |
Not visible |
Hidden by the declared IterableStream type |
Explicit in the return type |
| Single-use semantics |
Naturally scoped to one invocation |
May appear repeatable |
Can enforce single use internally, but the contract is hidden |
Can explicitly document and enforce single use |
| Core and generator impact |
Requires listener/cancellation contract and callback generation |
No Core API; simplest generation |
No Core API; more generated lifecycle wrappers |
Adds a reusable Core type and simplifies per-service ownership |
| Principal trade-off |
Strong SDK lifecycle control, less idiomatic sync composition |
Smallest surface, incomplete lifecycle contract |
Better hidden cleanup, but cannot solve early for-each exit |
Clearest pull contract, at the cost of new Core API |
Conceptual shape of the subclassed IterableStream alternative:
return new IterableStream<T>(eventIterable) {
@Override
public Iterator<T> iterator() {
// Return a single-use iterator that releases or cancels the BinaryData body
// when iteration is exhausted or event decoding fails.
return wrappedIterator;
}
@Override
public Stream<T> stream() {
// Return a stream backed by iterator() and use onClose to release or
// cancel consumption of the BinaryData body.
return eventStream;
}
};
Stream.onClose runs only when the Java stream is explicitly closed; terminal operations such as forEach, count, or findFirst do not invoke it.
Proposal
Use CloseableIterableStream<T> for synchronous SSE because it preserves idiomatic pull-based Java consumption while making the BinaryData body's lifetime explicit and safely supporting early termination through try-with-resources.
Acceptance criteria:
- Compares usability, blocking behavior, backpressure, cancellation, and implementation complexity.
- Defines completion, errors, early termination, resource cleanup, and single-use or repeated-consumption behavior.
- Establishes the API shape that TypeSpec Java should generate.
- Includes API documentation and lifecycle tests for the selected pattern.
Describe alternatives you've considered
The listener pattern is used by the initial Search implementation. Pull-based alternatives range from an unmodified or service-subclassed IterableStream to the explicit CloseableIterableStream proposed in #50081.
Additional context
Parent: #50264
Pull-based proposal: #50081
Information Checklist
Is your feature request related to a problem? Please describe.
Java SDKs need a consistent synchronous streaming API shape. The initial Search implementation uses a push-based listener, while #50081 proposes a pull-based
CloseableIterableStream<T>. This is a child of #50264.Describe the solution you'd like
Choose and document one synchronous streaming pattern:
IterableStream<T>IterableStream<T>CloseableIterableStream<T>for-eachor JavaStreamfor-eachandstream()for-eachandstream()onClosecovers an explicitly closed Java streamfor-eachexit remains uncloseableIterableStreamtypefor-eachexitConceptual shape of the subclassed
IterableStreamalternative:Stream.onCloseruns only when the Java stream is explicitly closed; terminal operations such asforEach,count, orfindFirstdo not invoke it.Proposal
Use
CloseableIterableStream<T>for synchronous SSE because it preserves idiomatic pull-based Java consumption while making theBinaryDatabody's lifetime explicit and safely supporting early termination through try-with-resources.Acceptance criteria:
Describe alternatives you've considered
The listener pattern is used by the initial Search implementation. Pull-based alternatives range from an unmodified or service-subclassed
IterableStreamto the explicitCloseableIterableStreamproposed in #50081.Additional context
Parent: #50264
Pull-based proposal: #50081
Information Checklist