How does Spring WebFlux serialize and deserialize JSON, and what role does Jackson2JsonEncoder/Decoder play?
answer
- Jackson2JsonEncoder/Decoder wrap ObjectMapper
- Reactive twin of MappingJackson2HttpMessageConverter
- Flux + application/json = array; + x-ndjson = streamed
- Non-blocking Jackson async parser
- Customize via configureHttpMessageCodecs / defaultCodecs().jackson2...
basics
~10 sWebFlux uses Jackson under the hood. Jackson2JsonEncoder turns your objects into JSON bytes for the response; Jackson2JsonDecoder turns request JSON bytes back into objects. They use a Jackson ObjectMapper, like MVC does.
solid answer
~30 sBy default WebFlux registers Jackson2JsonEncoder and Jackson2JsonDecoder for application/json (and streaming variants). They wrap a Jackson ObjectMapper and are adapted into HttpMessageWriter/Reader via EncoderHttpMessageWriter/DecoderHttpMessageReader. Unlike MVC's blocking MappingJackson2HttpMessageConverter, they operate on Flux<DataBuffer>: for a single object the input is aggregated then parsed with Jackson's non-blocking parser; for a Flux of many elements with a streaming media type (application/x-ndjson) each element is encoded and flushed independently. To customize serialization you typically register a Jackson2ObjectMapperBuilder-configured ObjectMapper, or override the codecs in WebFluxConfigurer.configureHttpMessageCodecs and pass your own Jackson2JsonEncoder/Decoder. Boot auto-configures these from the primary ObjectMapper bean and jackson properties.
code
java · 16 lines@Configuration
public class JsonCodecConfig implements WebFluxConfigurer {
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
.failOnUnknownProperties(false)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();
configurer.defaultCodecs()
.jackson2JsonEncoder(new Jackson2JsonEncoder(mapper));
configurer.defaultCodecs()
.jackson2JsonDecoder(new Jackson2JsonDecoder(mapper));
}
}go deeper
Know that WebFlux uses Jackson for JSON and that there is an encoder for responses and a decoder for requests.
Explain the Jackson2JsonEncoder/Decoder pair, that they wrap an ObjectMapper, and the array-vs-NDJSON difference for Flux.
Discuss the non-blocking parser, maxInMemorySize buffering for single values, and the layered customization options.
Reason about content-negotiation implications of streaming media types, Boot auto-config wiring of the ObjectMapper, and migration paths (Jackson 3 codec naming).
## The default JSON path When Jackson is on the classpath (Spring Boot's `spring-boot-starter-webflux` pulls it in), WebFlux registers a JSON codec pair by default: - **`Jackson2JsonEncoder`** — object stream to JSON `DataBuffer`s (writing responses / request bodies). - **`Jackson2JsonDecoder`** — JSON `DataBuffer`s to objects (reading request / response bodies). Both wrap a Jackson **`ObjectMapper`**. They are the reactive counterparts of MVC's `MappingJackson2HttpMessageConverter`. At runtime each is adapted into the HTTP layer by `EncoderHttpMessageWriter` / `DecoderHttpMessageReader`. ## Supported media types The JSON codecs advertise `application/json`, `application/*+json`, and the streaming types `application/x-ndjson` (and the older `application/stream+json`). This media-type list is how content negotiation picks them. ## Single object vs stream of objects - **`Mono<T>` / single value:** the decoder aggregates the incoming `DataBuffer`s up to `maxInMemorySize`, then parses once. The encoder writes one JSON document. - **`Flux<T>` with `application/json`:** the elements are collected and serialized as a single JSON **array**. - **`Flux<T>` with a streaming type (`application/x-ndjson`):** each element is encoded and flushed as its own line as soon as it is emitted — true streaming, no full-buffering. This is what enables Server-Sent-Events-like JSON streaming. Jackson's **non-blocking (async) parser** is what allows the decoder to feed bytes incrementally rather than requiring the entire body up front. ## Customizing Several levels, from least to most invasive: 1. **Global ObjectMapper** — define/customize the `ObjectMapper` (or use Spring Boot's `spring.jackson.*` properties, `Jackson2ObjectMapperBuilder`, `@JsonComponent`, or a `Jackson2ObjectMapperBuilderCustomizer`). Boot wires this mapper into the default codecs. 2. **Per-codec ObjectMapper** — construct `new Jackson2JsonEncoder(customMapper)` / `new Jackson2JsonDecoder(customMapper)` and register them in `WebFluxConfigurer.configureHttpMessageCodecs`, calling `defaultCodecs().jackson2JsonEncoder(...)` / `jackson2JsonDecoder(...)` to replace the defaults. 3. **Per-request** — with `WebClient`, use `.codecs(...)` on the builder. ## Gotchas - The Jackson **decoder buffers** the full body (for a single value) to parse it, so it is subject to `maxInMemorySize` (default 256 KB) — large bodies throw `DataBufferLimitException`. - For a `Flux` response you must choose the right media type: `application/json` gives an array (aggregated), streaming types give element-by-element output. Getting this wrong means clients see either a blocking array or unexpected NDJSON. - `@RequestBody Mono<T>`/`Flux<T>` binding goes through the same decoder; validation and error mapping happen after decoding. - In newer Spring versions the class family is being generalized (e.g. `JacksonJsonEncoder`/`Decoder` for Jackson 3), but the concept is identical.
- What is the difference between returning Flux<T> with application/json vs application/x-ndjson?With application/json the elements are aggregated and written as a single JSON array (client waits for completion). With application/x-ndjson each element is encoded and flushed independently as newline-delimited JSON, enabling true streaming.
- If you define a custom ObjectMapper bean in a Boot WebFlux app, do the JSON codecs pick it up automatically?Yes. Boot's CodecCustomizer wires the primary ObjectMapper into the default Jackson2JsonEncoder/Decoder, so a customized mapper (or spring.jackson.* properties) applies without manually re-registering codecs.
saying these in an interview costs you the question
- Saying WebFlux uses MappingJackson2HttpMessageConverter
- Claiming JSON is always fully buffered even for streaming Flux responses
- Thinking a custom ObjectMapper bean has no effect on WebFlux codecs