The spring-webflux
module includes a reactive, non-blocking client for HTTP requests
with a functional-style API client and Reactive Streams support. WebClient
depends on a
lower level HTTP client library to execute requests and that support is pluggable.
WebClient
uses the same codecs as WebFlux server applications do, and
shares a common base package, some common APIs, and infrastructure with the
server functional web framework.
The API exposes Reactor Flux
and Mono
types, also see
web-reactive.adoc. By default it uses
it uses Reactor Netty as the HTTP client
library but others can be plugged in through a custom ClientHttpConnector
.
By comparison to the RestTemplate, the
WebClient
is:
-
non-blocking, reactive, and supports higher concurrency with less hardware resources.
-
provides a functional API that takes advantage of Java 8 lambdas.
-
supports both synchronous and asynchronous scenarios.
-
supports streaming up or down from a server.
The RestTemplate
is not a good fit for use in non-blocking applications, and therefore
Spring WebFlux application should always use the WebClient
. The WebClient
should also
be preferred in Spring MVC, in most high concurrency scenarios, and for composing a
sequence of remote, inter-dependent calls.
The retrieve()
method is the easiest way to get a response body and decode it:
WebClient client = WebClient.create("http://example.org");
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
You can also get a stream of objects decoded from the response:
Flux<Quote> result = client.get()
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);
By default, responses with 4xx or 5xx status codes result in an error of type
WebClientResponseException
but you can customize that:
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxServerError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);
The exchange()
method provides more control. The below example is equivalent
to retrieve()
but also provides access to the ClientResponse
:
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class));
At this level you can also create a full ResponseEntity
:
Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.toEntity(Person.class));
Note that unlike retrieve()
, with exchange()
there are no automatic error signals for
4xx and 5xx responses. You have to check the status code and decide how to proceed.
Caution
|
When using |
The request body can be encoded from an Object:
Mono<Person> personMono = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(personMono, Person.class)
.retrieve()
.bodyToMono(Void.class);
You can also have a stream of objects encoded:
Flux<Person> personFlux = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body(personFlux, Person.class)
.retrieve()
.bodyToMono(Void.class);
Or if you have the actual value, use the syncBody
shortcut method:
Person person = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.syncBody(person)
.retrieve()
.bodyToMono(Void.class);
To send form data, provide a MultiValueMap<String, String>
as the body. Note that the
content is automatically set to "application/x-www-form-urlencoded"
by the
FormHttpMessageWriter
:
MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(formData)
.retrieve()
.bodyToMono(Void.class);
You can also supply form data in-line via BodyInserters
:
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);
To send multipart data, you need to provide a MultiValueMap<String, ?>
whose values are
either Objects representing part content, or HttpEntity
representing the content and
headers for a part. MultipartBodyBuilder
provides a convenient API to prepare a
multipart request:
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
In most cases you do not have to specify the Content-Type
for each part. The content
type is determined automatically based on the HttpMessageWriter
chosen to serialize it,
or in the case of a Resource
based on the file extension. If necessary you can
explicitly provide the MediaType
to use for each part through one fo the overloaded
builder part
methods.
Once a MultiValueMap
is prepared, the easiest way to pass it to the the WebClient
is
through the syncBody
method:
MultipartBodyBuilder builder = ...;
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(builder.build())
.retrieve()
.bodyToMono(Void.class);
If the MultiValueMap
contains at least one non-String value, which could also be
represent regular form data (i.e. "application/x-www-form-urlencoded"), you don’t have to
set the Content-Type
to "multipart/form-data". This is always the case when using
MultipartBodyBuilder
which ensures an HttpEntity
wrapper.
As an alternative to MultipartBodyBuilder
, you can also provide multipart content,
inline-style, through the built-in BodyInserters
. For example:
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromMultipartData("fieldPart", "value").with("filePart", resource))
.retrieve()
.bodyToMono(Void.class);
A simple way to create WebClient
is through the static factory methods create()
and
create(String)
with a base URL for all requests. You can also use WebClient.builder()
for access to more options.
To customize the underlying HTTP client:
SslContext sslContext = ...
ClientHttpConnector connector = new ReactorClientHttpConnector(
builder -> builder.sslContext(sslContext));
WebClient webClient = WebClient.builder()
.clientConnector(connector)
.build();
To customize the HTTP codecs used for encoding and decoding HTTP messages:
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> {
// ...
})
.build();
WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies)
.build();
The builder can be used to insert Filters.
Explore the WebClient.Builder
in your IDE for other options related to URI building,
default headers (and cookies), and more.
After the WebClient
is built, you can always obtain a new builder from it, in order to
build a new WebClient
, based on, but without affecting the current instance:
WebClient modifiedClient = client.mutate()
// user builder methods...
.build();
WebClient
supports interception style request filtering:
WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();
ExchangeFilterFunctions
provides a filter for basic authentication:
// static import of ExchangeFilterFunctions.basicAuthentication
WebClient client = WebClient.builder()
.filter(basicAuthentication("user", "pwd"))
.build();
You can also mutate an existing WebClient
instance without affecting the original:
WebClient filteredClient = client.mutate()
.filter(basicAuthentication("user", "pwd")
.build();
To test code that uses the WebClient
, you can use a mock web server such as the
OkHttp MockWebServer. To see example
use, check
WebClientIntegrationTests
in the Spring Framework tests, or the
static-server
sample in the OkHttp repository.