Which HttpMessageReaders decode multipart in WebFlux, and how do MultipartHttpMessageReader, DefaultPartHttpMessageReader, and the PartEvent reader differ?
answer
- DefaultPartHttpMessageReader -> Flux<Part>, streaming, no Synchronoss
- MultipartHttpMessageReader -> Mono<MultiValueMap>, buffers all
- PartEventHttpMessageReader -> Flux<PartEvent>, zero buffering
- limits: maxInMemorySize/maxParts/maxDiskUsagePerPart/maxHeadersSize
- target type selects the reader
basics
~10 sDefaultPartHttpMessageReader parses the body into a streaming Flux<Part>. MultipartHttpMessageReader wraps it and collects parts into a Mono<MultiValueMap<String, Part>>. PartEventHttpMessageReader produces a fully streaming Flux<PartEvent> without buffering to disk.
solid answer
~40 sWebFlux decodes multipart bodies via HttpMessageReaders chosen by target type. DefaultPartHttpMessageReader is the built-in, dependency-free streaming parser that yields Flux<Part>; it enforces limits like maxInMemorySize (memory-per-part before spilling to a temp file), maxDiskUsagePerPart, maxParts, and maxHeadersSize. MultipartHttpMessageReader is a thin wrapper that delegates to the Part reader and aggregates into Mono<MultiValueMap<String, Part>> for map-style access — this buffers all parts. PartEventHttpMessageReader targets Flux<PartEvent> (FilePartEvent/FormPartEvent): each event carries a slice of content, so nothing is ever buffered to memory or disk — ideal for streaming proxies. So: pick Flux<Part> for streaming with random file handling, MultiValueMap for convenience on small forms, and PartEvent for zero-buffer relays. You tune the readers via CodecCustomizer / server codecs.
code
java · 23 lines// Tune the streaming part reader's limits
@Configuration
class MultipartConfig implements WebFluxConfigurer {
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
DefaultPartHttpMessageReader partReader = new DefaultPartHttpMessageReader();
partReader.setMaxInMemorySize(256 * 1024); // spill parts > 256KB to temp file
partReader.setMaxParts(20);
partReader.setMaxDiskUsagePerPart(50L * 1024 * 1024); // 50MB per file
configurer.customCodecs().reader(partReader);
// wrap for MultiValueMap access:
configurer.customCodecs().reader(new MultipartHttpMessageReader(partReader));
}
}
// Zero-buffer streaming with PartEvent (e.g. proxying upstream)
@PostMapping(value = "/relay", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Mono<Void> relay(@RequestBody Flux<PartEvent> events) {
return events
.filter(e -> e instanceof FilePartEvent)
.concatMap(e -> Mono.fromRunnable(() -> DataBufferUtils.release(e.content())))
.then();
}go deeper
Know a reader turns the body into Parts and there are limits.
Distinguish Flux<Part> (streaming) from MultiValueMap (buffered) and know maxInMemorySize.
Name the three readers, their target types, buffering behavior, and the Synchronoss removal.
Design limit policy (maxParts/maxDiskUsagePerPart) and pick PartEvent for zero-buffer gateways.
**Reader selection.** WebFlux maps a controller parameter/`@RequestBody` type to an `HttpMessageReader`. For `multipart/form-data` the relevant readers are: **1. `DefaultPartHttpMessageReader`** — the default, fully reactive parser with **no external dependency** (it replaced the old Synchronoss-based `SynchronossPartHttpMessageReader`, which was removed in Spring 6). It reads the raw body and emits `Flux<Part>`. It owns the safety limits: - `maxInMemorySize` — max bytes of a single part kept in memory before it is written to a temp file (protects the heap). - `maxDiskUsagePerPart` — cap on temp-file size per part (-1 = unlimited). - `maxParts` — max number of parts. - `maxHeadersSize` — cap on a part's headers. - `headersCharset` — charset for part headers. Exceeding a limit raises a `DecodingException` (typically surfaced as 4xx). Temp files are cleaned up when parts are consumed/released. **2. `MultipartHttpMessageReader`** — a decorator that delegates to a `Part` reader (the Default one) and **collects** all parts into `Mono<MultiValueMap<String, Part>>`, keyed by part name. Convenient random access by name, but it materializes every part first (in memory up to `maxInMemorySize`, else temp file). This is what backs a `Mono<MultiValueMap<String, Part>>` or `@RequestPart` binding to a map. **3. `PartEventHttpMessageReader`** — targets `Flux<PartEvent>`. A `PartEvent` is a streaming event: `FormPartEvent` for fields and `FilePartEvent` for files, and a single file arrives as *multiple* `FilePartEvent`s each carrying a `DataBuffer` slice (the first has the headers/filename, `isLast()` marks the end). Because content is delivered as events, **nothing is buffered to memory or disk** — this is the lowest-memory option and the right tool for a streaming gateway/proxy (you can re-emit `PartEvent`s straight into a downstream WebClient request via `BodyInserters.fromProducer`/`PartEvent` support). **Configuration.** These readers live in the server codec configuration. You customize them with a `CodecCustomizer` bean or `WebFluxConfigurer#configureHttpMessageCodecs`, e.g. setting `maxInMemorySize` on the multipart reader. (General codec configuration mechanics belong to the codecs leaf; here the point is *which* reader handles *which* target type and their buffering behavior.) **Choosing:** - `Flux<Part>` (Default reader) — streaming, per-part handling, bounded memory. Default choice for uploads. - `Mono<MultiValueMap<String, Part>>` (Multipart reader) — small forms, map convenience, accepts full buffering. - `Flux<PartEvent>` (PartEvent reader) — zero-buffer streaming/relay of arbitrary parts. **Gotchas.** - The three readers are distinguished purely by the requested target type — asking for the wrong type gets you the wrong buffering behavior. - `maxInMemorySize` defaults are conservative; large-header or large-field forms can trip limits and yield decoding errors. - `MultiValueMap` looks cheap but is the most memory-hungry for big uploads.
- Why is Mono<MultiValueMap<String, Part>> the most memory-hungry option?MultipartHttpMessageReader must materialize every part before completing the map, buffering each in memory up to maxInMemorySize and spilling the rest to temp files. Flux<Part> and PartEvent process incrementally, so peak memory is bounded by a single part/chunk.
- What replaced the Synchronoss multipart reader and why?DefaultPartHttpMessageReader, a fully reactive parser with no third-party dependency, replaced SynchronossPartHttpMessageReader (removed in Spring Framework 6). It removes an external dependency and integrates cleanly with the reactive buffer/backpressure model.
- When would PartEvent beat Flux<Part>?When relaying/proxying uploads downstream without touching disk or heap — PartEvent delivers content as streaming events you can pipe straight into a WebClient request, so nothing is ever buffered to a temp file the way a FilePart may be.
saying these in an interview costs you the question
- Thinking MultiValueMap streams without buffering
- Believing WebFlux still needs the Synchronoss library
- Assuming maxInMemorySize caps the whole request rather than per-part memory before spill
- Confusing PartEvent (streaming events) with Part (windowed content)