Customize Spring WebClient with WebClientCustomizer

Last Updated:  August 3, 2021 | Published: March 4, 2020

Spring Once you use the Spring WebClient at multiple places in your application, providing a unified configuration with copy-pasting, e.g., common headers to all places is cumbersome. The Spring WebClient provides a mechanism to customize all instances using the WebClientCustomizer interface globally. This blog post demonstrates how to customize the Spring WebClient at a central place. Due to Spring Boot's autoconfiguration mechanism, there's almost nothing to set up in addition.

Spring WebClient Project Setup

The automatic registration of our WebClient customizations is done by Spring Boot's autoconfiguration. Therefore the demo application uses spring-boot-starter-web and spring-boot-start-webflux.

If your application uses Spring WebFlux without Spring Boot, you can still follow this article. In such cases, make sure to mirror the autoconfiguration of Spring Boot inside your application.

For this example, the WebFlux dependency would be enough. As most projects out there still use the embedded Tomcat and some minor parts from WebFlux, we're using the following project setup:

As part of the Spring Boot dependency, we get the following autoconfiguration class (WebClientAutoConfiguration):

This autoconfiguration exposes a pre-configured instance of WebClient.Builder. Within the constructor of this class, we see the injection of ObjectProvider<WebClientCustomizer>. This ObjectProvider is capable of returning all object instances of the WebClientCustomizer interface to customize the WebClient.Builder.

While exposing this WebClient.Builder instance in this autoconfiguration using @Bean, we might wonder what the @Scope("prototype") annotation is used. This overrides the default singleton scope to use the prototype scope. Spring won't share instances of these beans among the application like for singletons. For each injection point, a new instance is created and returned to the caller.

Defining Customizations with WebClientCustomizer

As we already saw how the autoconfiguration works internally, we now have to implement the WebClientCustomizer.

First, we're creating a global customization to ensure all WebClients include the same User-Agent HTTP header. In a distributed system architecture, this customization can help to understand communication patterns and to identify the origin of an HTTP request:

While implementing the WebClientCustomizer interface, we get access to the WebClient.Builder and can set multiple configurations like headers, cookies, exchange strategies, filters, and much more.

Next, let's provide a logging mechanism for all WebClient instances. Therefore, we can create another customization:

Please note the @Component annotation on top of each of these two classes. This is required for Spring to pick it up while scanning the project for all available beans (aka. component scanning). Without this annotation, the ObjectProvider<WebClientCustomizer> will we empty and won't know about our customization.

Using the Customized Spring WebClient

There are now basically two ways of using this pre-configured WebClient.

First, we can provide a central configuration with all our WebClient instances. For an application that communicates with a stock and random data API, this might look like the following:

Our business logic can then request such an instance by its name, e.g. @Autowired WebClient stockApiClient.

Second, we can also inject the WebClient.Builder into our classes directly and construct a WebClient instance there:

Please note: It's important to always inject and use the autoconfigured WebClient.Builder from Spring Boot. If we manually construct a WebClient using WebClient.builder(), we won't get the customizations out of the box.

Once the WebClient makes the remote call, we'll get the following output and can see all headers, including our custom User-Agent:

The source code for this Spring WebClient customization example is available on GitHub.

For more Spring WebClient related content, consider the following articles:

Have fun customizing your Spring WebClient,

Phil

    • You can configure basic authentication with a filter .filter(ExchangeFilterFunctions.basicAuthentication("rieckpil", UUID.randomUUID().toString())) when creating the WebClient. You can find further examples on how to use filters here.

  • Thanks for this detailed article.
    Do you know a way to get the customizations out-of-the-box without injecting the builder?
    I have a webClient generated by open-api so I can’t inject the builder by myself. I would force the open-api WebClient creation to inherit the auto-configurations.
    Thanks

    • Hi Val,

      you can find the “behind-the-scenes” inside Spring’s WebClientAutoConfiguration class. Take a look at the @Bean definition that creates the WebClient.Builder. This injects a list of customizers (ObjectProvider customizerProvider) to apply them to the builder. You could inject the same list to customize your WebClient.

      Let me know if that helps,
      Philip

        • Hi Val,

          as the functionality of my blog’s comment section is quite limited when it comes to paste large code examples and seamlessly interact, I’ve moved your question over to a GitHub issue. Make sure to at least add a comment on the issue so that you’ll be notified on new discussion.

          I hope this works for you and let’s move the discussion to Github.

          Kind regards,
          Philip

  • Is it possible to customize WebClient per http request?
    I have micro services that authenticate requests using cookies. So at the point of entry, I want to extract the cookie and add it as default cookie to the WebClient builder, so that all future requests will inherit it and use it when interacting with other micro services.
    Is that possible.
    The current implementation calls the customizer at application startup, but I want it to be called at the point of http request.

    Thanks in advance.

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >