Hello. We upgraded from Spring Boot 2.1.7.RELEASE to 2.3.12.RELEASE. In our workload we have to make http requests to a huge number of external services (10k+). We use a single WebClientBuilder to create a WebClient for each external service when it's needed. With an updated Spring Boot we noticed that heap usage and gc time grew significantly. Some top objects from a heap dump:

org.springframework.http.MediaType  3,531,788 (8.2%)    197,780,128 B (9.2%)  n/a
org.springframework.core.log.CompositeLog   1,724,850 (4%)  110,390,400 B (5.1%)    n/a
org.springframework.util.MimeType[] 2,053,150 (4.8%)    74,241,920 B (3.5%) n/a
etc.

Digging into the heap dump I discovered that with SpringBoot 2.1.7.RELEASE we had a single DefaultExchangeStrategiesBuilder object, that were refferenced by all WebClients, but now we have 1 builder for each client. Overridding WebClientCodecCustomizer to it's previous implementation helped, gc time decreased to the same level as it was in SpringBoot 2.1.7.RELEASE:

@Override
public void customize(WebClient.Builder webClientBuilder) {
    webClientBuilder.exchangeStrategies(ExchangeStrategies.builder()
        .codecs((codecs) -> this.codecCustomizers.forEach((customizer) -> customizer.customize(codecs)))
        .build());
}

As far as I understood, the root cause is that DefaultWebClientBuilder.initExchangeStrategies creates a new ExchangeStrategies.Builder for each build method call, if strategiesConfigurers list is not empty. Previous WebClientCodecCustomizer implementation used to set strategies field directly and thus used a single DefaultExchangeStrategiesBuilder object. If I'm not mistaken, a possible solution is to cache mutated ExchangeStrategies in initExchangeStrategies method.

Thanks.

Comment From: rstoyanchev

Thanks for the report.

We could store the result of this.strategies.mutate() but it would be used only when ExchangeStrategies is set and it looks like Boot's auto config doesn't do that anymore. Furthermore, such an optimization would be incomplete if we don't apply a similar change for the case where ExchangeStrategies isn't set, or otherwise we would call ExchangeStrategies.builder() every time, and if we do cache that too, it would lead to side effects. The same reasoning applies for other fields too that could be less than optimal for creating many WebClient instances.

Generally, creating 10K+ WebClient instances is somewhat unexpected, and an application might have to set the WebClient.Builder settings more explicitly to ensure a more optimal creation.

Comment From: DanilaVaratyntsev

Got you. Thanks for your response. Should I close the issue?

Comment From: rstoyanchev

No problem.