Skip to content

feat(spring): @Streaming annotation for binary response streaming#618

Open
nsmnds wants to merge 2 commits into
masterfrom
claude/streaming-annotation-binary-e4xDT
Open

feat(spring): @Streaming annotation for binary response streaming#618
nsmnds wants to merge 2 commits into
masterfrom
claude/streaming-annotation-binary-e4xDT

Conversation

@nsmnds

@nsmnds nsmnds commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a @Streaming annotation that opts a Bytes response into chunked,
never-fully-buffered response streaming on Spring endpoints — both server
and client (Spring WirespecWebClient) sides.

endpoint DownloadReport GET /api/report -> {
    @Streaming
    200 -> Bytes
}

What changes

  • Spring Kotlin/Java emitters substitute org.springframework.core.io.Resource
    for the body type when a response is annotated @Streaming, skip the JSON
    serialize step, and emit a STREAMING marker on the handler companion.
  • WirespecResponseBodyAdvice (Kotlin + Java) dispatches on a runtime
    Resource body and writes Resource.inputStream straight to
    response.body with application/octet-stream.
  • WirespecWebClient (Kotlin + Java) detects the marker reflectively
    and consumes the response via bodyToMono(Resource.class) instead of
    buffering bytes.
  • Validation: @Streaming on a non-Bytes response fails the emit.
  • Core emitter hooks: KotlinEndpointDefinitionEmitter and
    JavaEndpointDefinitionEmitter expose three new open hooks
    (emitResponseBodyType, emitResponseSerializeBody,
    emitResponseDeserializeBody) plus a companion-extras hook so per-emitter
    subclasses can override response-body behavior without touching the
    request path.
  • concatGenerics sanitizer: now strips dots so fully-qualified body
    types produce valid identifiers.

Out of scope (intentional)

  • Other client SDKs (Ktor, OkHttp, JS, Python, etc.) keep buffering via
    the existing RawResponse.body: ByteArray? contract — unchanged.
  • fromResponse still works for non-streaming callers (tests, mocks)
    by wrapping the buffered body in ByteArrayResource. The Spring
    streaming client bypasses that path entirely.

Test plan

  • :src:compiler:core:jvmTest, :src:compiler:emitters:kotlin:jvmTest,
    :src:compiler:emitters:java:jvmTest — all green
  • :src:integration:spring:jvmTest — all green (existing 49 tests +
    6 new emitter tests asserting Resource body / STREAMING marker /
    @Streaming validation for both Kotlin and Java)
  • :src:converter:openapi:jvmTest, :src:integration:jackson:jvmTest
    — all green (verifies concatGenerics change is backward-compatible)
  • Manual end-to-end: streaming client receives first byte before
    full body for a slow InputStreamResource server (recommended
    follow-up integration test, not yet added)

https://claude.ai/code/session_013z9Zaawbdh3pnyMg4b2DSG

claude added 2 commits April 17, 2026 06:36
Lets a spec author opt in to chunked, never-fully-buffered response
streaming on Spring endpoints whose body is `Bytes`:

    endpoint DownloadReport GET /api/report -> {
        @streaming
        200 -> Bytes
    }

The Spring Kotlin/Java emitters substitute `org.springframework.core.io.Resource`
for the body type, skip the JSON serialize step, and add a `STREAMING`
marker on the handler companion. `WirespecResponseBodyAdvice` writes the
`Resource.inputStream` straight to `response.body`. `WirespecWebClient`
detects the marker and streams via `bodyToMono(Resource.class)` instead
of buffering bytes.

Other client SDKs are unaffected — `RawResponse.body: ByteArray?` stays
the same, and `fromResponse` still works (wrapping in `ByteArrayResource`)
for non-streaming callers like tests and mocks.

To enable Spring's per-emitter behavior, the core Kotlin/Java emitters
expose three new `open` hooks (`emitResponseBodyType`,
`emitResponseSerializeBody`, `emitResponseDeserializeBody`) plus a
companion-extras hook used to inject the marker.

https://claude.ai/code/session_013z9Zaawbdh3pnyMg4b2DSG
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants