Spring’s WebClient testing + findings from Async/RestTemplate.

Several weeks ago I was working on a task where I have to do millions of REST HTTP calls from java app to external service. First I tried to use Spring’s RestTemplate, but it was too slow for my case because it’s synchronous(lets say server response rate 30ms and I have to make 2M requests, which leads us to approx 16 hours, in single thread mode). Then I started looking for something asynchronous and luckily Spring has AsyncRestTemplate. It has similar functionality as RestTemplate, but only difference is that it returns ListenableFuture.

I found 3 cons in usage of AsyncRestTemplate class:

1. You have to configure thread pool. There is no universal formula for determining a correct size of a thread pool, so you have to spend time testing what will fit your application. In case of AsyncRestTemplate if you don’t setup thread pool size, potentially you could run out of resources because by default it will create a large thread pool.
2. It’s heavy on resources. Each thread and callback consumes your RAM and CPU.
3. AsyncRestTemplate deprecated. Since Spring 5 AsyncRestTemplate was deprecated in favour of WebClient. IMHO it’s a bad practice to use deprecated code even if it’s still supported.

So after that, I switched to WebClient. Doc says that WebClient is:

A non-blocking, reactive client for performing HTTP requests with Reactive Streams back pressure.

I spent some time looking for examples and reading the documentation. I tested it and can say, that WebClient performed well and was very light on resources. I found one downside to it, is testing, because it has a functional style of API. I tried different approaches and it didn’t work for me, so I decided to use WireMock.

Here is my way of testing WebClient with WireMock:


package com.example.demo;

import com.github.tomakehurst.wiremock.junit.WireMockRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.Assert.assertTrue;

public class WebClientTest {

    private WebClient webClient;

    //WireMock specific code, we create a "server" working on port 8888
    @Rule
    public WireMockRule wireMockRule = new WireMockRule(options().port(8888));

    @Before
    public void setUp() throws Exception {
        webClient = WebClient.create();

        //WireMock specific code, we specify to listen for "/getUser" URI and 
        // reply with HTTP 200 response to every request
        stubFor(
                post(urlEqualTo("/getUser")).willReturn(aResponse().withStatus(200))
        );
    }

    @Test
    public void verify2XXResponseReceived() {
        Mono<ClientResponse> clientResponseMono =
                webClient
                        .get()
                        .uri("http://localhost:8888/getUser")
                        .exchange();

        //Once external server will processes our request it will shoot back response,
        //but meanwhile we are subscribing to his response
        clientResponseMono.subscribe(
                res -> assertTrue(res.statusCode().is2xxSuccessful()),
                error -> error.getMessage()
        );
    }
}

Leave a Reply

Your email address will not be published.