As explained in Multipart Data , ServerWebExchange provides access to multipart content. The best way to handle a file upload form (for example, from a browser) in a controller is through data binding to a command object , as the following example shows:

@PostMapping("/form") public String handleFormUpload(MyForm form, BindingResult errors) { // ... @PostMapping("/form") fun handleFormUpload(form: MyForm, errors: BindingResult): String { // ... --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp Content-Disposition: form-data; name="meta-data" Content-Type: application/json; charset=UTF-8 Content-Transfer-Encoding: 8bit "name": "value" --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp Content-Disposition: form-data; name="file-data"; filename="file.properties" Content-Type: text/xml Content-Transfer-Encoding: 8bit ... File Data ...

You can access individual parts with @RequestPart , as the following example shows:

@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata, (1)
		@RequestPart("file-data") FilePart file) { (2)
	// ...
@PostMapping("/")
fun handle(@RequestPart("meta-data") Part metadata, (1)
		@RequestPart("file-data") FilePart file): String { (2)
	// ...
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) { (1)
	// ...
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData): String { (1)
	// ...

You can use @RequestPart in combination with jakarta.validation.Valid or Spring’s @Validated annotation, which causes Standard Bean Validation to be applied. Validation errors lead to a WebExchangeBindException that results in a 400 (BAD_REQUEST) response. The exception contains a BindingResult with the error details and can also be handled in the controller method by declaring the argument with an async wrapper and then using error related operators:

@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
	// use one of the onError* operators...
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
	// ...
@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) { (1)
	// ...
@PostMapping("/")
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { (1)
	// ...

To access multipart data sequentially, in a streaming fashion, you can use @RequestBody with Flux<PartEvent> (or Flow<PartEvent> in Kotlin). Each part in a multipart HTTP message will produce at least one PartEvent containing both headers and a buffer with the contents of the part.

File uploads will produce one or more FilePartEvent objects, containing the filename used when uploading. If the file is large enough to be split across multiple buffers, the first FilePartEvent will be followed by subsequent events.

@PostMapping("/")
public void handle(@RequestBody Flux<PartEvent> allPartsEvents) { (1)
    allPartsEvents.windowUntil(PartEvent::isLast) (2)
            .concatMap(p -> p.switchOnFirst((signal, partEvents) -> { (3)
                if (signal.hasValue()) {
                    PartEvent event = signal.get();
                    if (event instanceof FormPartEvent formEvent) { (4)
                        String value = formEvent.value();
                        // handle form field
                    else if (event instanceof FilePartEvent fileEvent) { (5)
                        String filename = fileEvent.filename();
                        Flux<DataBuffer> contents = partEvents.map(PartEvent::content); (6)
                        // handle file upload
                    else {
                        return Mono.error(new RuntimeException("Unexpected event: " + event));
                else {
                    return partEvents; // either complete or error signal
The final PartEvent for a particular part will have isLast() set to true, and can be
followed by additional events belonging to subsequent parts.
This makes the isLast property suitable as a predicate for the Flux::windowUntil operator, to
split events from all parts into windows that each belong to a single part.
The Flux::switchOnFirst operator allows you to see whether you are handling a form field or
file upload.
Handling the form field.
Handling the file upload.
The body contents must be completely consumed, relayed, or released to avoid memory leaks.
	@PostMapping("/")
	fun handle(@RequestBody allPartsEvents: Flux<PartEvent>) = { (1)
      allPartsEvents.windowUntil(PartEvent::isLast) (2)
          .concatMap {
              it.switchOnFirst { signal, partEvents -> (3)
                  if (signal.hasValue()) {
                      val event = signal.get()
                      if (event is FormPartEvent) { (4)
                          val value: String = event.value();
                          // handle form field
                      } else if (event is FilePartEvent) { (5)
                          val filename: String = event.filename();
                          val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content); (6)
                          // handle file upload
                      } else {
                          return Mono.error(RuntimeException("Unexpected event: " + event));
                  } else {
                      return partEvents; // either complete or error signal
The final PartEvent for a particular part will have isLast() set to true, and can be
followed by additional events belonging to subsequent parts.
This makes the isLast property suitable as a predicate for the Flux::windowUntil operator, to
split events from all parts into windows that each belong to a single part.
The Flux::switchOnFirst operator allows you to see whether you are handling a form field or
file upload.
Handling the form field.
Handling the file upload.
The body contents must be completely consumed, relayed, or released to avoid memory leaks.
        

© VMware, Inc. or its affiliates. Terms of UsePrivacyTrademark Guidelines Thank youYour California Privacy RightsCookie Settings

Apache®, Apache Tomcat®, Apache Kafka®, Apache Cassandra™, and Apache Geode™ are trademarks or registered trademarks of the Apache Software Foundation in the United States and/or other countries. Java™, Java™ SE, Java™ EE, and OpenJDK™ are trademarks of Oracle and/or its affiliates. Kubernetes® is a registered trademark of the Linux Foundation in the United States and other countries. Linux® is the registered trademark of Linus Torvalds in the United States and other countries. Windows® and Microsoft® Azure are registered trademarks of Microsoft Corporation. “AWS” and “Amazon Web Services” are trademarks or registered trademarks of Amazon.com Inc. or its affiliates. All other trademarks and copyrights are property of their respective owners and are only mentioned for informative purposes. Other names may be trademarks of their respective owners.