or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotation-controllers.mdconfiguration.mderror-handling.mdfunctional-routing.mdindex.mdserver-configuration.mdtesting.mdwebclient.md

webclient.mddocs/

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

```