Azure / Azure/azure-sdk-for-java
[FEATURE REQ] Define the synchronous streaming API shape
- Dominant language
- Java
- Stars
- 2.6k
- Forks
- 2.2k
- Avg merge
- 2d 8h
- Merged PRs (30d)
- 178
Description
**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`. 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` | Subclassed `IterableStream` | `CloseableIterableStream` |
|---|---|---|---|---|
| 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:
```java
return new IterableStream(eventIterable) {
@Override
public Iterator 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 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` 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**
- [x] Description Added
- [x] Expected solution specified
Contributor guide
Assessment
This issue has not been assessed yet.