Friendly Webflux: Chapter 5. Calling external APIs with the WebClient

From a technical point of view, we distinguish between blocking and non-blocking IO. Prior to the Spring WebFlux, Spring ecosystem relied on Apache HttpClient library as an underlying implementation to work with HTTP. Blocking means, that a system call halts the execution flow until it receives an reply. Opposite to it stands non-blocking IO. In that case, execution continues and it deals with the result later. That principle lies in the idea of reactive programming. Spring 5 introduces among other async tools new WebClient component, that is a higher-level abstraction to HTTP client implemetations.

Create a new WebClient instance

First, we need to obtain an instance of WebClient. Basically, we can do it using one of three approaches:

  • WebClient.create() is a static method without arguments, that returns a client with default settings, which you can customize later
  • WebClient.create(baseUrl) is another static method, but it accepts a base url as an argument. This means that your requests are executed against this base url as a root
  • Using the builder pattern WebClient.builder() allows to customize a client instance in a flexible way

Take a look on the following code snippet below, that demonstrates usages of aforesaid methods:

(Code snippet 5-1)

WebClient basicClient = WebClient.create();

WebClient rootClient = WebClient.create("https://myserver.com");

WebClient builderClient = WebClient.builder()

    .baseUrl("https://myserver.com")

    .defaultCookie("key", "value")

    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)

    .build();

Once created, we can reuse the HttpClient instance in code.

Do you want to know more about Spring 5 and Webflux?

Hope you enjoyed so far! If you are interested in developing reactive apps with Spring 5, Project Reactor and Spring Webflux, I recommend you to purchase my book “Friendly Webflux“.

Retrieve data

In this subsection we will review how to perform a simple GET request and to access response body as Java type. Like in every case with Java, you first need to prepare a data model, that will be used for a deserialization.

The Webflux HttpClient operates two main categories – a request and a response. In order to prepare a request there are several approaches:

  • Use methods get(), head(), delete() to obtain the RequestHeadersUriSpec instance, which allows to specify request headers and URI
  • Use a function method() which takes a desired http method and returns the RequestBodyUriSpec instance, that goes for specifying request headers, body and URI for a request
  • Use methods put(), post() that creates an instance of RequestBodyUriSpec to define headers, body and URI for a request.

On the other side, the response is defined by the only ResponseSpec interface, which encapsulates the response data. That instance can be mapped to a body or the ResponseEntity object, which contains all information about the response, including response body, status code, headers, method etc. Both operations return a reactive type, that allows to subscribe for a result.

To start, let have a look on how to perform a get request, that emits a single entity in a body and to map this entity to a Java type.

(Code snippet 5-2)

@Test

void getOnePostRequestTest(){

    Mono<PostModel> result = client.get()

            .uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))

            .accept(MediaType.APPLICATION_JSON)

            .retrieve()

            .bodyToMono(PostModel.class);

    PostModel post = result.block();

    Assertions.assertThat(post)

            .hasFieldOrPropertyWithValue("userId", 1);

}

The example code call a remote endpoint and then retrieves a body payload as a Java class. This does not allow to get other data, than payload; so in the case, you need to access a response, use the toEntity() method.

(Code snippet 5-3)

@Test

void getRequestDataTest(){

    Mono<ResponseEntity<PostModel>> result = client.get()

            .uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))

            .accept(MediaType.APPLICATION_JSON)

            .retrieve()

            .toEntity(PostModel.class);

    ResponseEntity<PostModel> response = result.block();

    Assertions.assertThat(response.getStatusCode())

        .isEqualTo(HttpStatus.OK);

    Assertions.assertThat(response.hasBody()).isTrue();

}

In both cases we subscribed for a Mono publisher. In a situation, when you expect a sequence of entities, you can subscribe for a Flux with the bodyToFlux() function. Please note, that in this case, you will access a Flux, which emits entities and not the response data.

(Code snippet 5-4)

@Test

void getPostsTest(){

    Flux<PostModel> result = client.get()

            .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))

            .accept(MediaType.APPLICATION_JSON)

            .retrieve()

            .bodyToFlux(PostModel.class);

    Iterable<PostModel> posts = result.toIterable();

    Assertions.assertThat(posts).hasSize(100);

}

That is about retrieving data using GET requests. Another important aspect is how to actually send data to the remote server using POST and PUT HTTP requests.

Send data

We have mentioned in the previous subsection, that for PUT and POST requests we can utilize corresponding methods in order to define headers, body payload and URI. Take a look on the following code sample, where we perform a POST operation to send a new post entity to the remote API.

(Code snippet 5-5)

@Test

void postRequestTest(){

    PostModel payload = new PostModel("body", "title", 101, 101);

    Mono<PostModel> result = client.post()

            .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))

            .accept(MediaType.APPLICATION_JSON)

            .bodyValue(payload)

            .retrieve()

            .bodyToMono(PostModel.class);

    PostModel post = result.block();

    Assertions.assertThat(post).isEqualTo(payload);

}

The difference with previous examples is that we provide a body payload using the bodyValue() function. Namely, we can use two main approaches to define a payload, depending on its “availability”:

  • bodyValue() accepts an entity directly
  • body() accepts a publisher that allows to subscribe to Mono that will emit an actual data entity

In other words, I can recommend you to use the bodyValue() method if you already have an actual payload (it passed as an argument for your client component), otherwise provide a publisher that will permit to subscribe for a result of an async computation.

Exchange() vs retrieve()

The last point, which I would like to address in this section is a difference between two methods – exchange() and retrieve(). From a technical point of view, they both are used in order to execute a request, but they return different type of response:

  • Mono<ClientResponse> exchange() returns a Mono that allows to subscribe for the ClientResponse item. It provides access to the response status and headers, and also methods to consume the response body
  • WebClient.ResponseSpec retrieve() allows to use the ResponseSpec object. This allows to declare how to extract the response

Please note, that this subsection is provided here for general knowledge (or historical purposes). Starting the Spring version 5.3, the exchange() method is marked as deprecated due to the possibility to leak memory and/or connections. Thefore it is advised to use the retrieve() function instead.

Summary

In this section we did an overview of the Webflux WebClient. It is intended to replace blocking RestTemplate component, that was used to perform HTTP calls before and relied on Apache HttpClient library. The new component allows to execute requests in non-blocking way and to subscribe to responses with reactive publishers – Mono and Flux.

Do you want to know more about Spring 5 and Webflux?

Hope you enjoyed so far! If you are interested in developing reactive apps with Spring 5, Project Reactor and Spring Webflux, I recommend you to purchase my book “Friendly Webflux“.