Spring Framework 6.2 introduces a buffer leak when using JettyClientHttpConnector with a Jetty's httpClient. This worked correctly in Spring Framework 6.1.

We tested this against all 6.2.x version and 6.1.x version. Always produce leaky buffers on 6.2.x. and it is always fine with versions 6.1.x.

In this example, we use ArrayByteBufferPool.Tracking to make it obvious there is a buffer leak, but in our actual project, we use the default implementation of ArrayByteBufferPool.

Here's a OneDrive link to an actual project but also the code here if you don't want to download external files:

# .\spring-memory-leak-test\src\main\java\com\test\spring\WebClientBuilderStoreExecutor.java
package com.test.spring;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.ArrayByteBufferPool.Tracking;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.springframework.http.client.reactive.JettyClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class WebClientBuilderStoreExecutor {
    public static void main(String[] args) throws Exception {
        ClientConnector clientConnector = new ClientConnector();

        HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));

        Tracking tracking = new Tracking();

        httpClient.setByteBufferPool(tracking);

        WebClient client =  WebClient.builder()
                        .clientConnector(new JettyClientHttpConnector(httpClient))
                        .build();


        Map<Integer, String> results = new ConcurrentHashMap<>();
        ExecutorService executor = Executors.newFixedThreadPool(50);

        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.submit(
                    () -> {
                        String block =
                                client.get()
                                        .uri("http://httpbin.org/anything")
                                        .retrieve()
                                        .bodyToMono(String.class)
                                        .block();
                        results.put(index, block);
                    });
        }

        executor.shutdown();
        executor.awaitTermination(60, TimeUnit.SECONDS);

        System.out.println(
                "Completed request. Results stored in HashMap with "
                        + results.size()
                        + " entries.");
        System.out.println("Leaks count: " + tracking.getLeaks().size());
        tracking.getLeaks().forEach(leaks -> System.out.println("Leaked buffers: " + leaks));
    }
}
# \spring-memory-leak-test\pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.test.spring</groupId>
    <artifactId>spring-leak-test</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name></name>
    <url>http://maven.apache.org</url>
    <properties>
        <jetty-reactive-httpclient.version>4.0.10</jetty-reactive-httpclient.version>
        <!-- <spring-boot.version>3.5.4</spring-boot.version>
        <spring.version>6.2.9</spring.version> -->
        <spring-boot.version>3.3.12</spring-boot.version>
        <spring.version>6.1.20</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-reactive-httpclient</artifactId>
            <version>${jetty-reactive-httpclient.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.test.spring.WebClientBuilderStore</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.test.spring.WebClientBuilderStore</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>