Azure / Azure/azure-sdk-for-java

[FEATURE REQ] Define the synchronous streaming API shape

Open
#50,414 0 comments 1 reaction 1 assignee Claimed by @XiaofeiCao View on GitHub
Azure.Core
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.