skip to content

How does Spring WebFlux serialize and deserialize JSON, and what role does Jackson2JsonEncoder/Decoder play?

level: middleimportance: should knowfreq 45%

answer

  1. Jackson2JsonEncoder/Decoder wrap ObjectMapper
  2. Reactive twin of MappingJackson2HttpMessageConverter
  3. Flux + application/json = array; + x-ndjson = streamed
  4. Non-blocking Jackson async parser
  5. Customize via configureHttpMessageCodecs / defaultCodecs().jackson2...

basics

~10 s

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

By 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
java
@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

for a junior

Know that WebFlux uses Jackson for JSON and that there is an encoder for responses and a decoder for requests.

for a middle

Explain the Jackson2JsonEncoder/Decoder pair, that they wrap an ObjectMapper, and the array-vs-NDJSON difference for Flux.

for a senior

Discuss the non-blocking parser, maxInMemorySize buffering for single values, and the layered customization options.

for a principal

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

context