Repository navigation
8.3.0: stream application/x-ndjson endpoints without making Spring MVC services reactive - #472
Merged
jemacineiras merged 3 commits intoSep 29, 2026
Conversation
…ces reactive An operation whose successful response or request body is application/x-ndjson (or application/stream+json) is detected from the contract; its schema, the items of an array schema, or an OpenAPI 3.2 itemSchema, is the type of each line. - Spring MVC servers split the operation into the endpoint Spring maps, which is not overridden, and a plain method to implement returning ResponseEntity<Stream<T>> (or taking a Stream<T> body). The generated NdjsonSupport sends each item as it is produced through StreamingResponseBody, with the application's ObjectMapper, closes the stream, and flushes the servlet response, since Spring Framework 7.0.6 ignores flushing the body stream. A JSON alternative of the same response, which must be the array of the items, gets its own endpoint picked by the Accept header. - WebFlux servers, WebClient clients and reactive @HttpExchange interfaces take and return Flux<T>; ApiWebClient reads and writes NDJSON and sends Publisher bodies. - Blocking clients read a response whole, so generation fails for a streaming operation and asks for reactive=true. Also: itemSchema no longer crashes the generator, and a response declaring several media types renders a valid @apiresponse content array. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 32 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
jemacineiras
approved these changes
Sep 29, 2026
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Some endpoints need to stream
application/x-ndjsonwhile the rest of the service stays a plain Spring MVC application. Until now such a contract generatedResponseEntity<ItemDTO>withproduces = "application/json"(one item, wrong content type),ResponseEntity<Mono<ItemDTO>>for WebFlux, andbodyToMonoin the WebClient client; an OpenAPI 3.2itemSchemacrashed the generator with a NullPointerException.An operation whose successful response, or whose request body, is
application/x-ndjsonorapplication/jsonl(JSON Lines, the same format) is now detected from the contract. Its schema is the type of each line, or an array of it, anditemSchemais read the same way. Every other operation generates as before.Spring MVC servers — no Reactor, no WebFlux
The operation is split into the endpoint Spring maps and a plain method to implement:
The generated
listItemsNdjsonendpoint sends it through a generatedNdjsonSupportclass, which:StreamingResponseBodyon Spring MVC's async executor;ObjectMapper, or a default one when there is none;ResponseEntity.A streamed request body arrives as a
Stream<ItemDTO>, read line by line as it is consumed. When the response also declaresapplication/json, which must then be the array of the items, a…Jsonendpoint sends the same items as an array. Spring picks one or the other fromAccept;*/*or noAcceptgets JSON.flush()of the body stream into a no-op (spring.http.response.flush.enabled). Even a hand-writtenStreamingResponseBodybuffered the whole response.NdjsonSupporttherefore flushes theHttpServletResponseafter each line, which works on Servlet 4 to 6.Reactive flows
Flux<ItemDTO>, with the streamed media type inproduces/consumes.Flux<ItemDTO>(bodyToFlux), and…WithHttpInforeturnsMono<ResponseEntity<Flux<ItemDTO>>>(toEntityFlux);Acceptis the streamed media type;ApiWebClientnow registers its Jackson codecs for NDJSON and JSON Lines (both as streaming types), which it rejected before, and sendsPublisherbodies.application/x-ndjsononly. For JSON Lines, a WebFlux server or a reactive@HttpExchangebacked by the application'sWebClient.Builderneeds aCodecCustomizer; the README shows it for Boot 4 and 2/3.@HttpExchangereturnsMono<ResponseEntity<Flux<ItemDTO>>>and takes aFlux<ItemDTO>body.Blocking clients
RestClient/RestTemplate, and
@HttpExchangewithoutreactive, read a response whole (BufferingClientHttpRequestFactory). Generation fails for a streaming operation and asks forreactive=true. A realStream<T>for them is left for a follow-up.Contract checks (fail generation)
application/stream+json: deprecated, no longer streamed by Spring Framework 7, and not read byApiWebClient's codecs. The error asks forapplication/x-ndjsonorapplication/jsonl.application/jsonalternative of the streamed response that is not the array of its items, e.g. a page wrapper.Also fixed
A response declaring more than one media type rendered a
@Content(...)mediaType = ...that does not compile, in both server templates. It now renderscontent = {@Content(...), @Content(...)}. Single-media-type output is unchanged; no existing asset had the broken form.Test plan
New fixture
testNdjsonStreaming. It covers:$refitem, an array of items, JSON + NDJSON, and an inlineitemSchema;400next to a streamed200;Generated for the Spring MVC server on Boot 2 (javax), 3 (jakarta + Jackson 2) and 4 (Jackson 3), for WebFlux, for WebClient, and for reactive
@HttpExchange.Error tests:
application/stream+json; RestClient and blocking@HttpExchangerefused; a JSON alternative that does not match the items; a mixed streamed body.Changed existing asset:
testWebClientApiGeneration/springboot4/ApiWebClient.java(NDJSON codecs andPublisherbody), reviewed.Engine: 256 tests green, checkstyle green; Maven plugin ITs green; Gradle
test integrationTestgreen (JDK 21)Runtime check outside the repo: the generated code compiled into Spring Boot 2.7.18, 3.5.8 and 4.0.8 apps, exercised over real HTTP; 23 tests green. They check:
ObjectMapperis used (non_nullhonoured);Acceptnegotiation between the JSON and NDJSON endpoints;@HttpExchangewith the README'sCodecCustomizer(and failing without it);@HttpExchange(4.0) against the generated MVC server, both streaming and sendingFluxbodies.The WebFlux server was compiled, not run.
README: new "Streaming endpoints (application/x-ndjson)" section
Version bumped to 8.3.0 in all 5 places
🤖 Generated with Claude Code