Spring WebFlux - WebClient

WebClient is a non-blocking, reactive client for performing HTTP requests in Spring WebFlux. It is designed to work efficiently in reactive applications and offers a more flexible and functional approach compared to traditional RestTemplate.

Key Components and Significance

  1. WebClient.Builder:

    • Used to configure and build WebClient instances.
    • Allows customization like setting base URL, default headers, and common request parameters.
  2. WebClient:

    • The core class used to initiate HTTP requests.
    • Supports asynchronous and reactive programming paradigms.
    • Can handle requests and responses in a non-blocking manner, leveraging Project Reactor.
  3. Request Specification:

    • Specifies HTTP method (GET, POST, PUT, DELETE, etc.).
    • Defines URI, headers, query parameters, and request body.
  4. Response Handling:

    • Provides methods to handle and process responses.
    • Supports extracting and mapping response data to different formats and types.

Implementation Details

Creating a WebClient Instance

WebClient webClient = WebClient.builder()
.baseUrl("https://api.example.com") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build();

Making a GET Request

Mono<String> response = webClient.get()
.uri("/data") .retrieve() .bodyToMono(String.class); response.subscribe(System.out::println);

Making a POST Request

Mono<String> response = webClient.post()
.uri("/submit") .bodyValue(new DataObject("example")) .retrieve() .bodyToMono(String.class); response.subscribe(System.out::println);


Creating and Configuring WebClient

Spring WebFlux provides WebClient as a non-blocking, reactive HTTP client for making HTTP requests. Here is a detailed guide on creating and configuring WebClient.

1. Creating a WebClient Instance

Basic Creation

You can create a WebClient instance using the create() method, which provides a default configuration.

WebClient webClient = WebClient.create();

Creation with Base URL

To create a WebClient instance with a specified base URL:

WebClient webClient = WebClient.create("https://api.example.com");

2. Configuring WebClient Using WebClient.Builder

For more advanced configurations, use WebClient.Builder. This allows you to set default headers, base URLs, custom codecs, and more.

Setting Base URL

WebClient webClient = WebClient.builder() .baseUrl("https://api.example.com") .build();

Adding Default Headers

WebClient webClient = WebClient.builder() .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) .build();

Adding Custom Headers

WebClient webClient = WebClient.builder() .defaultHeader("Custom-Header", "HeaderValue") .build();

Configuring Timeout

You can configure timeouts using ExchangeStrategies and custom ClientHttpConnector.


ClientHttpConnector connector = new ReactorClientHttpConnector( HttpClient.create().responseTimeout(Duration.ofSeconds(5)) ); WebClient webClient = WebClient.builder() .clientConnector(connector) .build();

3. Using WebClient for Requests

GET Request

Mono<String> response = webClient.get() .uri("/data") .retrieve() .bodyToMono(String.class); response.subscribe(System.out::println);

POST Request with Body

Mono<String> response = webClient.post() .uri("/submit") .bodyValue(new DataObject("example")) .retrieve() .bodyToMono(String.class); response.subscribe(System.out::println);

PUT Request with Headers

Mono<String> response = webClient.put() .uri("/update/{id}", 123) .header(HttpHeaders.AUTHORIZATION, "Bearer token") .bodyValue(new DataObject("updated example")) .retrieve() .bodyToMono(String.class); response.subscribe(System.out::println);

DELETE Request

Mono<Void> response = webClient.delete() .uri("/delete/{id}", 123) .retrieve() .bodyToMono(Void.class); response.subscribe(() -> System.out.println("Deleted successfully"));

4. Handling Responses

Handling Status Codes

You can handle different HTTP status codes using onStatus.

Mono<String> response = webClient.get() .uri("/data") .retrieve() .onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Client Error")) ) .onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Server Error")) ) .bodyToMono(String.class); response.subscribe(System.out::println, System.err::println);

Extracting Response Headers

Mono<String> response = webClient.get() .uri("/data") .retrieve() .toEntity(String.class) .flatMap(entity -> { HttpHeaders headers = entity.getHeaders(); String customHeaderValue = headers.getFirst("Custom-Header"); return Mono.just(entity.getBody()); }); response.subscribe(System.out::println);

5. Error Handling

Mono<String> response = webClient.get() .uri("/data") .retrieve() .onStatus(HttpStatus::is4xxClientError, clientResponse -> clientResponse.bodyToMono(String.class) .flatMap(errorBody -> Mono.error(new CustomClientException(errorBody))) ) .onStatus(HttpStatus::is5xxServerError, clientResponse -> clientResponse.bodyToMono(String.class) .flatMap(errorBody -> Mono.error(new CustomServerException(errorBody))) ) .bodyToMono(String.class); response.subscribe(System.out::println, System.err::println);


Mono vs Flux

  • Mono:
    • Represents a single value or an empty result.
    • Used for scenarios where you expect at most one item.
  • Flux:
    • Represents a stream of multiple values.
    • Used for scenarios where you expect zero or more items.

Scenarios

Mono Example

  1. Fetching a Single Item:

    Mono<User> userMono = webClient.get()
    .uri("/user/{id}", userId) .retrieve() .bodyToMono(User.class); userMono.subscribe(user -> System.out.println(user.getName()));
  2. Processing a Single Response:

    Mono<ResponseEntity<String>> response = webClient.post()
    .uri("/process") .bodyValue(new ProcessRequest(data)) .retrieve() .toEntity(String.class); response.subscribe(entity -> { System.out.println("Status Code: " + entity.getStatusCode()); System.out.println("Body: " + entity.getBody()); });

Flux Example

  1. Fetching Multiple Items:

    Flux<User> userFlux = webClient.get()
    .uri("/users") .retrieve() .bodyToFlux(User.class); userFlux.subscribe(user -> System.out.println(user.getName()));
  2. Streaming Data:

    Flux<ServerSentEvent<String>> eventFlux = webClient.get()
    .uri("/stream") .retrieve() .bodyToFlux(new ParameterizedTypeReference<ServerSentEvent<String>>() {}); eventFlux.subscribe(event -> System.out.println("Received event: " + event.data()));

Mono

  • Definition: Represents a single or empty result. Think of it as a CompletableFuture that either completes successfully with a value or completes empty.
  • Use Cases:
    • When expecting a single item from a service, database, or external API.
    • For operations like fetching a specific entity by ID.
    • For handling completion signals or one-time events.

Examples

  1. Fetching a Single Item:

    Mono<User> userMono = webClient.get() .uri("/user/{id}", userId) .retrieve() .bodyToMono(User.class); userMono.subscribe(user -> System.out.println(user.getName()));
  2. Processing a Single Response:

    Mono<ResponseEntity<String>> response = webClient.post() .uri("/process") .bodyValue(new ProcessRequest(data)) .retrieve() .toEntity(String.class); response.subscribe(entity -> { System.out.println("Status Code: " + entity.getStatusCode()); System.out.println("Body: " + entity.getBody()); });
  3. Handling Completion:

    Mono<Void> completionMono = someAsyncOperation(); completionMono.subscribe( null, error -> System.err.println("Error: " + error), () -> System.out.println("Operation completed") );

Flux

  • Definition: Represents a sequence of 0 to N items. It can be thought of as a Stream that can emit multiple values over time.
  • Use Cases:
    • When expecting a collection or stream of items.
    • For operations like retrieving a list of entities from a database.
    • For handling continuous streams of data, like real-time updates or event streams.

Examples

  1. Fetching Multiple Items:

    Flux<User> userFlux = webClient.get() .uri("/users") .retrieve() .bodyToFlux(User.class); userFlux.subscribe(user -> System.out.println(user.getName()));
  2. Streaming Data:

    Flux<ServerSentEvent<String>> eventFlux = webClient.get() .uri("/stream") .retrieve() .bodyToFlux(new ParameterizedTypeReference<ServerSentEvent<String>>() {}); eventFlux.subscribe(event -> System.out.println("Received event: " + event.data()));
  3. Handling Multiple Items:

    Flux<String> itemsFlux = someAsyncItemProvider(); itemsFlux.subscribe( item -> System.out.println("Received item: " + item), error -> System.err.println("Error: " + error), () -> System.out.println("All items received") );

Conclusion

WebClient in Spring WebFlux offers a powerful, flexible, and efficient way to handle HTTP requests in a reactive programming style. By understanding and utilizing its key components and the differences between Mono and Flux, developers can build highly responsive and scalable applications.

Post a Comment

Previous Post Next Post