0
# WebClient
1
2
WebClient is Spring WebFlux's reactive HTTP client, providing a modern, non-blocking alternative to RestTemplate. It supports reactive streams, backpressure, and integrates seamlessly with the reactive ecosystem for building scalable applications.
3
4
## Core WebClient Interface
5
6
### Main Interface
7
8
```java { .api }
9
public interface WebClient {
10
11
RequestHeadersUriSpec<?> get();
12
RequestBodyUriSpec post();
13
RequestBodyUriSpec put();
14
RequestBodyUriSpec patch();
15
RequestHeadersUriSpec<?> delete();
16
RequestHeadersUriSpec<?> options();
17
RequestHeadersUriSpec<?> head();
18
19
RequestBodyUriSpec method(HttpMethod method);
20
21
Builder mutate();
22
23
static WebClient create();
24
static WebClient create(String baseUrl);
25
static Builder builder();
26
27
interface Builder {
28
Builder baseUrl(String baseUrl);
29
Builder defaultUriVariables(Map<String, ?> defaultUriVariables);
30
Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory);
31
Builder defaultHeader(String header, String... values);
32
Builder defaultHeaders(Consumer<HttpHeaders> headersConsumer);
33
Builder defaultCookie(String cookie, String... values);
34
Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
35
Builder defaultRequest(Consumer<RequestHeadersSpec<?>> defaultRequest);
36
Builder filter(ExchangeFilterFunction filter);
37
Builder filters(Consumer<List<ExchangeFilterFunction>> filtersConsumer);
38
Builder clientConnector(ClientHttpConnector connector);
39
Builder codecs(Consumer<ClientCodecConfigurer> configurer);
40
Builder exchangeStrategies(ExchangeStrategies strategies);
41
Builder exchangeFunction(ExchangeFunction exchangeFunction);
42
Builder clone();
43
44
WebClient build();
45
}
46
}
47
```
48
49
## Request Specification Interfaces
50
51
### RequestHeadersUriSpec
52
53
```java { .api }
54
public interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>> extends UriSpec<S> {
55
}
56
57
public interface UriSpec<S extends RequestHeadersSpec<S>> {
58
S uri(String uri, Object... uriVariables);
59
S uri(String uri, Map<String, ?> uriVariables);
60
S uri(String uri, Function<UriBuilder, URI> uriFunction);
61
S uri(URI uri);
62
}
63
64
public interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
65
S header(String headerName, String... headerValues);
66
S headers(Consumer<HttpHeaders> headersConsumer);
67
S accept(MediaType... acceptableMediaTypes);
68
S acceptCharset(Charset... acceptableCharsets);
69
S cookie(String name, String value);
70
S cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
71
S ifModifiedSince(ZonedDateTime ifModifiedSince);
72
S ifNoneMatch(String... ifNoneMatches);
73
S attribute(String name, Object value);
74
S attributes(Consumer<Map<String, Object>> attributesConsumer);
75
S context(Function<Context, Context> contextModifier);
76
S httpRequest(Consumer<ClientHttpRequest> requestConsumer);
77
78
ResponseSpec retrieve();
79
Mono<ClientResponse> exchange();
80
<V> Mono<V> exchangeToMono(Function<ClientResponse, ? extends Mono<V>> responseHandler);
81
<V> Flux<V> exchangeToFlux(Function<ClientResponse, ? extends Flux<V>> responseHandler);
82
}
83
```
84
85
### RequestBodyUriSpec
86
87
```java { .api }
88
public interface RequestBodyUriSpec extends RequestBodySpec, RequestHeadersUriSpec<RequestBodySpec> {
89
}
90
91
public interface RequestBodySpec extends RequestHeadersSpec<RequestBodySpec> {
92
RequestBodySpec contentLength(long contentLength);
93
RequestBodySpec contentType(MediaType contentType);
94
95
RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter);
96
<T> RequestHeadersSpec<?> body(Publisher<T> publisher, Class<T> elementClass);
97
<T> RequestHeadersSpec<?> body(Publisher<T> publisher, ParameterizedTypeReference<T> elementTypeRef);
98
RequestHeadersSpec<?> body(Object body, Class<?> bodyType);
99
RequestHeadersSpec<?> body(Object body, ParameterizedTypeReference<?> bodyType);
100
<T> RequestHeadersSpec<?> body(T body, Class<T> bodyType);
101
<T> RequestHeadersSpec<?> body(T body, ParameterizedTypeReference<T> bodyType);
102
RequestHeadersSpec<?> bodyValue(Object body);
103
104
RequestHeadersSpec<?> syncBody(Object body);
105
}
106
```
107
108
## Response Handling
109
110
### ResponseSpec
111
112
```java { .api }
113
public interface ResponseSpec {
114
ResponseSpec onStatus(Predicate<HttpStatus> statusPredicate, Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction);
115
ResponseSpec onRawStatus(IntPredicate statusCodePredicate, Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction);
116
ResponseSpec onStatus(HttpStatus statusCode, Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction);
117
118
<T> Mono<T> bodyToMono(Class<T> elementClass);
119
<T> Mono<T> bodyToMono(ParameterizedTypeReference<T> elementTypeRef);
120
<T> Flux<T> bodyToFlux(Class<T> elementClass);
121
<T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> elementTypeRef);
122
123
Mono<ResponseEntity<Void>> toEntity(Void unused);
124
<T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyClass);
125
<T> Mono<ResponseEntity<T>> toEntity(ParameterizedTypeReference<T> bodyTypeReference);
126
<T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementClass);
127
<T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> elementTypeRef);
128
129
Mono<ResponseEntity<Flux<DataBuffer>>> toEntityFlux();
130
131
<T> ResponseSpec bodyToMono(Class<T> elementClass);
132
<T> ResponseSpec bodyToFlux(Class<T> elementClass);
133
}
134
```
135
136
### ClientResponse
137
138
```java { .api }
139
public interface ClientResponse {
140
141
HttpStatus statusCode();
142
int rawStatusCode();
143
ResponseCookies cookies();
144
145
<T> Mono<T> bodyToMono(Class<T> elementClass);
146
<T> Mono<T> bodyToMono(ParameterizedTypeReference<T> elementTypeRef);
147
<T> Flux<T> bodyToFlux(Class<T> elementClass);
148
<T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> elementTypeRef);
149
150
Mono<ResponseEntity<Void>> toEntity(Void unused);
151
<T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyClass);
152
<T> Mono<ResponseEntity<T>> toEntity(ParameterizedTypeReference<T> bodyTypeReference);
153
<T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementClass);
154
<T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> elementTypeRef);
155
156
Mono<WebClientResponseException> createException();
157
158
static Builder from(ClientResponse other);
159
160
interface Headers {
161
OptionalLong contentLength();
162
Optional<MediaType> contentType();
163
List<String> header(String headerName);
164
HttpHeaders asHttpHeaders();
165
}
166
167
interface Builder {
168
Builder statusCode(HttpStatus statusCode);
169
Builder rawStatusCode(int statusCode);
170
Builder header(String headerName, String... headerValues);
171
Builder headers(Consumer<HttpHeaders> headersConsumer);
172
Builder cookie(ResponseCookie cookie);
173
Builder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
174
Builder body(Flux<DataBuffer> body);
175
Builder body(String body);
176
Builder request(HttpRequest request);
177
178
ClientResponse build();
179
}
180
}
181
```
182
183
## Filters and Customization
184
185
### ExchangeFilterFunction
186
187
```java { .api }
188
@FunctionalInterface
189
public interface ExchangeFilterFunction {
190
Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next);
191
192
default ExchangeFilterFunction andThen(ExchangeFilterFunction afterFilter);
193
default RequestFilterFunction requestProcessor();
194
default ResponseFilterFunction responseProcessor();
195
196
static ExchangeFilterFunction ofRequestProcessor(Function<ClientRequest, Mono<ClientRequest>> processor);
197
static ExchangeFilterFunction ofResponseProcessor(Function<ClientResponse, Mono<ClientResponse>> processor);
198
}
199
```
200
201
### Built-in Filters
202
203
```java { .api }
204
public class ExchangeFilterFunctions {
205
206
public static ExchangeFilterFunction basicAuthentication(String username, String password);
207
public static ExchangeFilterFunction basicAuthentication(String username, String password, Charset charset);
208
209
public static ExchangeFilterFunction limitResponseSize(long maxByteCount);
210
211
public static ExchangeFilterFunction statusError(Predicate<HttpStatus> statusPredicate, Function<ClientResponse, ? extends Throwable> exceptionFunction);
212
}
213
```
214
215
## Configuration and Codecs
216
217
### ClientCodecConfigurer
218
219
```java { .api }
220
public interface ClientCodecConfigurer extends CodecConfigurer {
221
222
ClientDefaultCodecs defaultCodecs();
223
void clone(Consumer<ClientCodecConfigurer> consumer);
224
225
interface ClientDefaultCodecs extends DefaultCodecs {
226
void jackson2JsonEncoder(Encoder<?> encoder);
227
void jackson2JsonDecoder(Decoder<?> decoder);
228
void protobufDecoder(Decoder<?> decoder);
229
void multipartCodecs();
230
void serverSentEventDecoder(Decoder<?> decoder);
231
}
232
}
233
```
234
235
## Usage Examples
236
237
### Basic WebClient Usage
238
239
```java
240
@Service
241
public class ApiService {
242
243
private final WebClient webClient;
244
245
public ApiService(WebClient.Builder builder) {
246
this.webClient = builder
247
.baseUrl("https://api.example.com")
248
.defaultHeader(HttpHeaders.USER_AGENT, "MyApp/1.0")
249
.build();
250
}
251
252
public Mono<User> getUser(String id) {
253
return webClient.get()
254
.uri("/users/{id}", id)
255
.retrieve()
256
.bodyToMono(User.class);
257
}
258
259
public Flux<User> getAllUsers() {
260
return webClient.get()
261
.uri("/users")
262
.retrieve()
263
.bodyToFlux(User.class);
264
}
265
266
public Mono<User> createUser(User user) {
267
return webClient.post()
268
.uri("/users")
269
.contentType(MediaType.APPLICATION_JSON)
270
.bodyValue(user)
271
.retrieve()
272
.bodyToMono(User.class);
273
}
274
275
public Mono<User> updateUser(String id, User user) {
276
return webClient.put()
277
.uri("/users/{id}", id)
278
.contentType(MediaType.APPLICATION_JSON)
279
.bodyValue(user)
280
.retrieve()
281
.bodyToMono(User.class);
282
}
283
284
public Mono<Void> deleteUser(String id) {
285
return webClient.delete()
286
.uri("/users/{id}", id)
287
.retrieve()
288
.bodyToMono(Void.class);
289
}
290
}
291
```
292
293
### Error Handling
294
295
```java
296
@Service
297
public class RobustApiService {
298
299
private final WebClient webClient;
300
301
public RobustApiService(WebClient.Builder builder) {
302
this.webClient = builder
303
.baseUrl("https://api.example.com")
304
.build();
305
}
306
307
public Mono<User> getUser(String id) {
308
return webClient.get()
309
.uri("/users/{id}", id)
310
.retrieve()
311
.onStatus(HttpStatus::is4xxClientError, response -> {
312
if (response.statusCode() == HttpStatus.NOT_FOUND) {
313
return Mono.error(new UserNotFoundException("User not found: " + id));
314
}
315
return response.bodyToMono(String.class)
316
.flatMap(body -> Mono.error(new ApiException("Client error: " + body)));
317
})
318
.onStatus(HttpStatus::is5xxServerError, response ->
319
Mono.error(new ApiException("Server error: " + response.statusCode())))
320
.bodyToMono(User.class)
321
.doOnError(WebClientResponseException.class, ex ->
322
log.error("API call failed: {} {}", ex.getStatusCode(), ex.getResponseBodyAsString()))
323
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
324
.filter(throwable -> throwable instanceof WebClientResponseException &&
325
((WebClientResponseException) throwable).getStatusCode().is5xxServerError()));
326
}
327
328
public Mono<Optional<User>> getUserSafely(String id) {
329
return webClient.get()
330
.uri("/users/{id}", id)
331
.retrieve()
332
.bodyToMono(User.class)
333
.map(Optional::of)
334
.onErrorReturn(WebClientResponseException.NotFound.class, Optional.empty())
335
.onErrorMap(WebClientResponseException.class, ex ->
336
new ApiException("Failed to fetch user: " + ex.getMessage()));
337
}
338
}
339
```
340
341
### Advanced Configuration
342
343
```java
344
@Configuration
345
public class WebClientConfiguration {
346
347
@Bean
348
public WebClient webClient(WebClient.Builder builder) {
349
return builder
350
.baseUrl("https://api.example.com")
351
.defaultHeader(HttpHeaders.USER_AGENT, "MyApp/1.0")
352
.defaultCookie("session", "abc123")
353
.filter(loggingFilter())
354
.filter(authenticationFilter())
355
.filter(retryFilter())
356
.codecs(configurer -> {
357
configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024); // 2MB
358
configurer.defaultCodecs().jackson2JsonEncoder(customJsonEncoder());
359
configurer.defaultCodecs().jackson2JsonDecoder(customJsonDecoder());
360
})
361
.clientConnector(new ReactorClientHttpConnector(
362
HttpClient.create()
363
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
364
.responseTimeout(Duration.ofSeconds(30))
365
.followRedirect(true)
366
))
367
.build();
368
}
369
370
private ExchangeFilterFunction loggingFilter() {
371
return ExchangeFilterFunction.ofRequestProcessor(request -> {
372
log.info("Sending {} request to {}", request.method(), request.url());
373
return Mono.just(request);
374
});
375
}
376
377
private ExchangeFilterFunction authenticationFilter() {
378
return (request, next) -> {
379
ClientRequest authorizedRequest = ClientRequest.from(request)
380
.header(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken())
381
.build();
382
return next.exchange(authorizedRequest);
383
};
384
}
385
386
private ExchangeFilterFunction retryFilter() {
387
return (request, next) -> {
388
return next.exchange(request)
389
.flatMap(response -> {
390
if (response.statusCode().is5xxServerError()) {
391
return Mono.error(new ServerException("Server error"));
392
}
393
return Mono.just(response);
394
})
395
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
396
.filter(ServerException.class::isInstance));
397
};
398
}
399
}
400
```
401
402
### File Upload and Download
403
404
```java
405
@Service
406
public class FileService {
407
408
private final WebClient webClient;
409
410
public FileService(WebClient.Builder builder) {
411
this.webClient = builder
412
.baseUrl("https://file-api.example.com")
413
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)) // Unlimited
414
.build();
415
}
416
417
public Mono<String> uploadFile(String filename, Resource file) {
418
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
419
parts.add("file", file);
420
parts.add("filename", filename);
421
422
return webClient.post()
423
.uri("/upload")
424
.contentType(MediaType.MULTIPART_FORM_DATA)
425
.body(BodyInserters.fromMultipartData(parts))
426
.retrieve()
427
.bodyToMono(String.class);
428
}
429
430
public Mono<Resource> downloadFile(String fileId) {
431
return webClient.get()
432
.uri("/files/{id}", fileId)
433
.accept(MediaType.APPLICATION_OCTET_STREAM)
434
.retrieve()
435
.bodyToMono(Resource.class);
436
}
437
438
public Flux<DataBuffer> streamFile(String fileId) {
439
return webClient.get()
440
.uri("/files/{id}/stream", fileId)
441
.accept(MediaType.APPLICATION_OCTET_STREAM)
442
.retrieve()
443
.bodyToFlux(DataBuffer.class);
444
}
445
446
public Mono<Void> uploadLargeFile(String filename, Flux<DataBuffer> fileData) {
447
return webClient.post()
448
.uri("/upload/stream")
449
.contentType(MediaType.APPLICATION_OCTET_STREAM)
450
.header("X-Filename", filename)
451
.body(fileData, DataBuffer.class)
452
.retrieve()
453
.bodyToMono(Void.class);
454
}
455
}
456
```
457
458
### Server-Sent Events
459
460
```java
461
@Service
462
public class EventStreamService {
463
464
private final WebClient webClient;
465
466
public EventStreamService(WebClient.Builder builder) {
467
this.webClient = builder
468
.baseUrl("https://events.example.com")
469
.build();
470
}
471
472
public Flux<ServerSentEvent<String>> subscribeToEvents(String topic) {
473
ParameterizedTypeReference<ServerSentEvent<String>> type =
474
new ParameterizedTypeReference<ServerSentEvent<String>>() {};
475
476
return webClient.get()
477
.uri("/events/{topic}", topic)
478
.accept(MediaType.TEXT_EVENT_STREAM)
479
.retrieve()
480
.bodyToFlux(type)
481
.doOnSubscribe(subscription -> log.info("Subscribed to events for topic: {}", topic))
482
.doOnNext(event -> log.debug("Received event: {}", event.data()))
483
.doOnError(throwable -> log.error("Error in event stream", throwable))
484
.doOnComplete(() -> log.info("Event stream completed for topic: {}", topic));
485
}
486
487
public Flux<String> subscribeToSimpleEvents(String topic) {
488
return webClient.get()
489
.uri("/events/{topic}/simple", topic)
490
.accept(MediaType.TEXT_EVENT_STREAM)
491
.retrieve()
492
.bodyToFlux(String.class);
493
}
494
}
495
```
496
497
### OAuth2 Integration
498
499
```java
500
@Service
501
public class OAuth2ApiService {
502
503
private final WebClient webClient;
504
private final OAuth2AuthorizedClientManager authorizedClientManager;
505
506
public OAuth2ApiService(WebClient.Builder builder,
507
OAuth2AuthorizedClientManager authorizedClientManager) {
508
this.authorizedClientManager = authorizedClientManager;
509
this.webClient = builder
510
.baseUrl("https://secure-api.example.com")
511
.filter(oauth2Filter())
512
.build();
513
}
514
515
private ExchangeFilterFunction oauth2Filter() {
516
return (request, next) -> {
517
return authorizedClient()
518
.map(client -> {
519
String token = client.getAccessToken().getTokenValue();
520
return ClientRequest.from(request)
521
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
522
.build();
523
})
524
.flatMap(next::exchange);
525
};
526
}
527
528
private Mono<OAuth2AuthorizedClient> authorizedClient() {
529
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
530
.withClientRegistrationId("my-client")
531
.principal("user")
532
.build();
533
534
return Mono.fromCallable(() -> authorizedClientManager.authorize(authorizeRequest))
535
.subscribeOn(Schedulers.boundedElastic());
536
}
537
538
public Mono<UserProfile> getUserProfile() {
539
return webClient.get()
540
.uri("/profile")
541
.retrieve()
542
.bodyToMono(UserProfile.class);
543
}
544
}
545
```
546
547
## WebClient Testing
548
549
```java
550
@ExtendWith(MockitoExtension.class)
551
class WebClientServiceTest {
552
553
@Mock
554
private WebClient.Builder webClientBuilder;
555
556
@Mock
557
private WebClient webClient;
558
559
@Mock
560
private WebClient.RequestHeadersUriSpec requestHeadersUriSpec;
561
562
@Mock
563
private WebClient.ResponseSpec responseSpec;
564
565
@InjectMocks
566
private ApiService apiService;
567
568
@Test
569
void shouldGetUser() {
570
// Given
571
String userId = "123";
572
User expectedUser = new User(userId, "John Doe");
573
574
when(webClientBuilder.baseUrl(anyString())).thenReturn(webClientBuilder);
575
when(webClientBuilder.build()).thenReturn(webClient);
576
when(webClient.get()).thenReturn(requestHeadersUriSpec);
577
when(requestHeadersUriSpec.uri("/users/{id}", userId)).thenReturn(requestHeadersUriSpec);
578
when(requestHeadersUriSpec.retrieve()).thenReturn(responseSpec);
579
when(responseSpec.bodyToMono(User.class)).thenReturn(Mono.just(expectedUser));
580
581
// When
582
Mono<User> result = apiService.getUser(userId);
583
584
// Then
585
StepVerifier.create(result)
586
.expectNext(expectedUser)
587
.verifyComplete();
588
}
589
}
590
```