1. Overview
Our applications often have to handle file uploads via an HTTP request. Since Spring 5, we can now make these requests reactive.
The added support for Reactive Programming allows us to work in a non-blocking way, using a small number of threads and Backpressure.
In this article, we'll use WebClient – a non-blocking, reactive HTTP client – to illustrate how to upload a file. WebClient is part of the reactive programming library called Project Reactor. We'll cover two different approaches to uploading a file using a BodyInserter.
2. Uploading a File with WebClient
In order to use WebClient, we'll need to add the spring-boot-starter-webflux dependency to our project:
<dependency>
<groupId>org.springframework.boot</groupId>.
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
2.1. Uploading a File from a Resource
To start with, we want to declare our URL:
URI url = UriComponentsBuilder.fromHttpUrl(EXTERNAL_UPLOAD_URL).build().toUri();
Let's say in this example we want to upload a PDF. We'll use MediaType.APPLICATION_PDF as our ContentType.
Our upload endpoint returns an HttpStatus. Since we're expecting only one result, we'll wrap it in a Mono:
Mono<HttpStatus> httpStatusMono = webClient.post()
.uri(url)
.contentType(MediaType.APPLICATION_PDF)
.body(BodyInserters.fromResource(resource))
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(HttpStatus.class).thenReturn(response.statusCode());
} else {
throw new ServiceException("Error uploading file");
}
});
The method consuming this method can also return a Mono, and we can continue until we actually need to access the result. Once we're ready, we can call the block() method on the Mono object.
The fromResource() method uses the InputStream of the passed resource to write to the output message.
2.2. Uploading a File from Multipart Resource
If our external upload endpoint takes a Multipart form data, we can use the MultiPartBodyBuilder to take care of the parts:
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("file", multipartFile.getResource());
Here, we could be adding various parts according to our requirements. The value in the map can be an Object or an HttpEntity.
When we call WebClient, we use BodyInsterter.fromMultipartData and build the object:
.body(BodyInserters.fromMultipartData(builder.build()))
We update the content type to MediaType.MULTIPART_FORM_DATA to reflect the changes.
Let's look at the entire call:
Mono<HttpStatus> httpStatusMono = webClient.post()
.uri(url)
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(builder.build()))
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(HttpStatus.class).thenReturn(response.statusCode());
} else {
throw new ServiceException("Error uploading file");
}
});
3. Conclusion
In this tutorial, we've shown two ways to upload a file with WebClient using BodyInserters. As always, the code is available over on GitHub.