skip to content

How do you handle a multipart request with an unknown number of parts (e.g. multiple files) in WebFlux?

level: middleimportance: should knowfreq 45%

answer

  1. Flux<Part> = stream all parts
  2. Mono<MultiValueMap<String,Part>> = collect all
  3. instanceof FilePart / FormFieldPart
  4. must drain each part before next
  5. @RequestPart Flux<FilePart> for repeated name

basics

~10 s

Bind the whole body as Flux<Part> (or Mono<MultiValueMap<String, Part>>). Iterate the flux, check each part's type (FilePart vs FormFieldPart) via instanceof, and process filename/value accordingly.

solid answer

~40 s

For a variable set of parts you don't bind one named part — you take the whole multipart body. Two shapes: @RequestBody Flux<Part> streams parts one at a time as they arrive; Mono<MultiValueMap<String, Part>> collects them all keyed by name. Flux<Part> is preferred for many/large files because it doesn't hold every part at once. In the flux you switch on type: instanceof FilePart for files (filename(), transferTo) and FormFieldPart for text fields (value()). Key gotcha: with Flux<Part> you must fully consume each part's content before the next part emits — the parser is sequential over one connection, so skipping a part's body without draining it stalls the stream. You can also bind a named repeated part directly, e.g. @RequestPart("files") Flux<FilePart>.

code

java · 14 lines
java
@PostMapping(value = "/multi", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Mono<Void> handle(@RequestBody Flux<Part> parts) {
    return parts.concatMap(part -> {          // concatMap keeps parts sequential
        if (part instanceof FilePart file) {
            return file.transferTo(Path.of("/tmp", file.filename()));
        }
        if (part instanceof FormFieldPart field) {
            log.info("field {} = {}", field.name(), field.value());
            return Mono.empty();
        }
        // drain unknown part so the stream can advance
        return part.content().map(DataBuffer::readableByteCount).then();
    }).then();
}

go deeper

for a junior

Know Flux<Part> exists for many parts and instanceof FilePart.

for a middle

Choose between Flux<Part> and MultiValueMap and know each part must be consumed in order.

for a senior

Explain the sequential window semantics and why concatMap (not concurrent flatMap) is correct.

for a principal

Weigh memory/backpressure trade-offs and set reader limits appropriately for the workload.

**The problem.** `@RequestPart("file") FilePart` is fine for exactly one known part. When the client sends several files and fields with names you don't know ahead of time, you need the whole body. **Binding options:** - `@RequestBody Flux<Part>` — a stream of all parts in arrival order. Backpressure-aware; parts are produced as the body is parsed. - `Mono<MultiValueMap<String, Part>>` — collects every part into a map keyed by part name (multiple parts can share a name). Convenient but buffers all parts (to memory/temp files) before your code runs. - `@RequestPart("files") Flux<FilePart>` — binds *only* the parts named `files` as a flux. **Discriminating part types.** Every element is a `Part`; use `instanceof`: ```java if (part instanceof FilePart fp) { fp.filename(); fp.transferTo(...); } else if (part instanceof FormFieldPart ff) { ff.value(); } ``` **The sequential-consumption gotcha.** The reactive multipart parser (`DefaultPartHttpMessageReader`) reads a single input stream. Parts are strictly ordered and each part's `content()` (a `Flux<DataBuffer>`) is a *window* into that stream. You must consume (or explicitly drain/release) part N's content before part N+1 can be emitted. If you `filter` out a part and never touch its body, or subscribe to the outer `Flux<Part>` without consuming inner content, the pipeline can hang and buffers can leak. `transferTo`, `DataBufferUtils.write`, or `DataBufferUtils.release` on each buffer all satisfy this. **Ordering.** `Mono<MultiValueMap>` sidesteps sequencing (Spring drains everything) at the cost of buffering. Choose `Flux<Part>` when memory matters and you can process parts as they stream. **When to use which:** small forms with a few fields — `MultiValueMap` is simplest. Many/large uploads — `Flux<Part>` streamed to storage. Fully streaming pass-through/proxy — `PartEvent` (separate question).

  • Why can iterating Flux<Part> hang if you skip a part?
    The parser reads one underlying connection stream sequentially; each part's content is a window over it. The next part is only emitted after the current part's body is consumed. Skipping without draining/releasing the body stalls the pipeline and leaks buffers.
  • When would you pick Mono<MultiValueMap<String, Part>> over Flux<Part>?
    For small forms with a handful of fields where convenience beats memory — Spring collects all parts (buffering to memory up to maxInMemorySize, then temp files) so you can random-access them by name without worrying about consumption order.

saying these in an interview costs you the question

  • Believing you can filter Flux<Part> and ignore skipped parts' bodies
  • Thinking Mono<MultiValueMap> streams without buffering
  • Using concurrency (flatMap with high concurrency) that reorders/overlaps part consumption

context