Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot WebClient.Builder bean usage in traditional servlet multi threaded application

I would like to have a http client to call other microservice from Spring Boot not reactive application. Because of RestTemplate will be deprecated I tried to use WebClient.Builder and WebClient. Though I not sure about thread safety. Here example:

@Service
public class MyService{
    @Autowired
    WebClient.Builder webClientBuilder;

    public VenueDTO serviceMethod(){
        //!!! This is not thread safe !!!
        WebClient webClient = webClientBuilder.baseUrl("http://localhost:8000").build();

        VenueDTO venueDTO = webClient.get().uri("/api/venue/{id}", bodDTO.getBusinessOutletId()).
                retrieve().bodyToMono(VenueDTO.class).
                blockOptional(Duration.ofMillis(1000)).
                orElseThrow(() -> new BadRequestException(venueNotFound));
                return VenueDTO;
    }
}

serviceMethod() in this example will be called from few threads, and webClientBuilder is a single bean instance. The WebClient.Builder class contains state: baseUrl, and this seems not thread safe as few threads could call this state update simultaneously. Meanwhile WebClient itself seems is thread safe as mentioned in answer at Right way to use Spring WebClient in multi-thread environment

Should I use WebClient.Builder bean as mentioned in https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-webclient.html

Spring Boot creates and pre-configures a WebClient.Builder for you; it is strongly advised to inject it in your components and use it to create WebClient instances.

One of workaround options I see is to create WebClient without any state passed to builder so instead of:

WebClient webClient = webClientBuilder.baseUrl("http://localhost:8000").build();

I will do:

WebClient webClient = webClientBuilder.build();

and pass full url with protocol and port in uri method call:

webClient.get().uri("full url here", MyDTO.class)

What is the proper way to use it in my case?

like image 974
Pavlo Morozov Avatar asked Jan 10 '19 19:01

Pavlo Morozov


People also ask

Is WebClient multithreaded?

Because WebClient is immutable it is thread-safe. WebClient is meant to be used in a reactive environment, where nothing is tied to a particular thread (this doesn't mean you cannot use in a traditional Servlet application).

What is the difference between WebClient and RestTemplate?

RestTemplate uses Java Servlet API and is therefore synchronous and blocking. Conversely, WebClient is asynchronous and will not block the executing thread while waiting for the response to come back. The notification will be produced only when the response is ready. RestTemplate will still be used.

What is the use of WebClient and WebTestClient?

WebTestClient is a thin shell around WebClient, using it to perform requests and exposing a dedicated, fluent API for verifying responses. WebTestClient binds to a WebFlux application by using a mock request and response, or it can test any web server over an HTTP connection.


1 Answers

You're right, WebClient.Builder is not thread-safe.

Spring Boot is creating WebClient.Builder as a prototype bean, so you'll get a new instance for each injection point. In your case, your component seems a bit strange in my opinion.

It should rather look like this:

@Service
public class MyService{

    private final WebClient webClient;

    public MyService(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("http://localhost:8000").build();
    }

    public VenueDTO serviceMethod(){
        VenueDTO venueDTO = webClient.get().uri("/api/venue/{id}", bodDTO.getBusinessOutletId()).
                retrieve().bodyToMono(VenueDTO.class).
                blockOptional(Duration.ofMillis(1000)).
                orElseThrow(() -> new BadRequestException(venueNotFound));
                return VenueDTO;
    }
}

Now I guess this is a code snippet and your application may have different constraints.

If your application needs to change the base URL often, then I think you should stop configuring it on the builder and pass the full URL as mentioned in your question. If your application has other needs (custom headers for authentication, etc), then you can also do that on the builder or on a per request basis.

In general, you should try and build a single WebClient instance per component, as recreating it for each request is quite wasteful.

In case your application has very specific constraints and really needs to create different instances, then you can always call webClientBuilder.clone() and get a new instance of the builder that you can mutate, without the thread safety issues.

like image 116
Brian Clozel Avatar answered Sep 22 '22 20:09

Brian Clozel