Skip to content

[FEATURE REQ] Define the synchronous streaming API shape #50414

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<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

  • Description Added
  • Expected solution specified

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions