Skip to content

Conversation

spicydev
Copy link
Contributor

@spicydev spicydev commented Jul 20, 2025

Title

Implement custom BodyHandler for GZIP decompression in JdkClientHttpRequest

Description

Problem: JDK HTTP Client doesn't automatically decompress responses.

Solution: creating a custom BodyHandler that checks Content-Encoding and applies GZIPInputStream when necessary.
Key Changes:

  • Added new boolean field compressionEnabled to JdkClientHttpRequestFactory and JdkClientHttpRequest.
  • Modified buildRequest() method to add Accept-Encoding header with gzip value when compression is enabled.
  • Added a custom DecompressingBodyHandler that implements BodyHandler<InputStream>. The apply() method in the custom handler checks the Content-Encoding header.
  • If the encoding is gzip, it uses BodySubscribers.mapping to wrap the InputStream with GZIPInputStream.
    Otherwise, it uses the default BodySubscribers.ofInputStream().

Testing Steps:

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jul 20, 2025
Added new flag to JdkClientHttpRequest and created new DecompressingBodyHandler class to handle HttpClient response body decompression when encoded. Closes spring-projectsgh-35222

Signed-off-by: spicydev <vivek@mirchi.dev>
@spicydev spicydev force-pushed the issue/35222_jdk_http_client_compression_feature branch from d20e827 to 3015e29 Compare July 20, 2025 16:11
@bclozel
Copy link
Member

bclozel commented Jul 20, 2025

Should we also support the "deflate" compression algorithm?

Also, I am wondering if we really need an option that enables compression. Should we enable this no matter what?

What do you think?

@bclozel bclozel added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Jul 20, 2025
@spicydev
Copy link
Contributor Author

Hi @bclozel, First off let me start off by apologizing for raising the PR in such bad state. I’ll work on unit tests and follow style guide once we can finalize on the requirements.

coming to your questions, Yes we can add support for multiple encoding types but following other clients like apache httpclient & netty, they only support gzip by default out of the box. I think at-least for now we can add support for gzip to JDK http client.

I think it’s better to have the control to the users but by default we can enable compression as it causes no harm but improves performance for all users.

Let me know your thoughts, based on that i can go ahead with changes.

@bclozel
Copy link
Member

bclozel commented Jul 20, 2025

@spicydev no worries, we are here to help.

I think Apache HttpClient supports both. And so does Netty.

Added support for deflate encoded response using InflaterInputStream and added Unit Test cases.

Signed-off-by: spicydev <vivek@mirchi.dev>
@spicydev spicydev force-pushed the issue/35222_jdk_http_client_compression_feature branch from b4e19ea to e463d77 Compare July 26, 2025 16:19
@spicydev
Copy link
Contributor Author

Hi @bclozel , Need your help here. When testing the Inflater with JDK http client I'm seeing error like below
java.util.zip.ZipException: incorrect header check. Can you check the unit test once

@driverpt
Copy link

Good stuff!

Suggestion for the future for Spring Maintainers: Make Default Body Handler overridable from the outside, so we can implement workarounds

@bclozel bclozel self-assigned this Aug 20, 2025
@@ -98,7 +110,7 @@ protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body
CompletableFuture<HttpResponse<InputStream>> responseFuture = null;
try {
HttpRequest request = buildRequest(headers, body);
responseFuture = this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream());
responseFuture = this.httpClient.sendAsync(request, new DecompressingBodyHandler());
Copy link

@driverpt driverpt Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this Handler a parameter ? This will make Workarounds easier

@@ -43,6 +43,8 @@ public class JdkClientHttpRequestFactory implements ClientHttpRequestFactory {

private @Nullable Duration readTimeout;

private boolean compressionEnabled;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add Optional BodyRequestHandler parameter, defaulting to the new one you just created

@@ -43,6 +43,8 @@ public class JdkClientHttpRequestFactory implements ClientHttpRequestFactory {

private @Nullable Duration readTimeout;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Create JdkClientHttpRequestFactoryConfig with all parameters


@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
return new JdkClientHttpRequest(this.httpClient, uri, httpMethod, this.executor, this.readTimeout);
return new JdkClientHttpRequest(this.httpClient, uri, httpMethod, this.executor, this.readTimeout, this.compressionEnabled);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: With above suggestion, inject JdkClientHttpRequestFactoryConfig

else if(request.getTarget().startsWith("/compress/")) {
String encoding = request.getTarget().replace("/compress/","");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
if (encoding.equals("gzip")) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use improved switch?

@bclozel bclozel added this to the 7.0.0-M9 milestone Aug 25, 2025
@bclozel bclozel added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Aug 25, 2025
@bclozel bclozel changed the title Adds gzip handler to JDK http client based on header Add compression support in JdkClientHttpRequest Aug 25, 2025
@bclozel bclozel changed the title Add compression support in JdkClientHttpRequest Add compression support in JdkClientHttpRequestFactory Aug 25, 2025
bclozel pushed a commit that referenced this pull request Aug 25, 2025
This commit ensures that the "Accept-Encoding" header is present for HTTP
requests sent by the `JdkClientHttpRequestFactory`. Only "gzip" and
"deflate" encodings are supported.

This also adds a custom `BodyHandler` that decompresses HTTP response
bodies if the "Content-Encoding" header lists a supported variant.

This feature is enabled by default and can be disabled on the request
factory.

See gh-35225

Signed-off-by: spicydev <vivek@mirchi.dev>
[brian.clozel@broadcom.com: squash commits]
Signed-off-by: Brian Clozel <brian.clozel@broadcom.com>
@bclozel bclozel closed this in 18eb2a6 Aug 25, 2025
@bclozel
Copy link
Member

bclozel commented Aug 25, 2025

Thanks a lot @spicydev for your first contribution! Tests were initially failing because the test server was writing non-printable characters as a String instead of bytes directly.

@driverpt I don't think we should open the API more here. A BodyHandler is very much a JDK client concern. If a custom body handler is needed, developers should use the HttpClient directly.

@driverpt
Copy link

driverpt commented Aug 26, 2025

Will this be backported into current Spring Boot 3.5.x?

@driverpt I don't think we should open the API more here. A BodyHandler is very much a JDK client concern. If a custom body handler is needed, developers should use the HttpClient directly.

@bclozel, Actually IMHO you do, given that JdkClientHttpRequest Class is internal to Spring and there's no way of customizing it.

If you check the call

	@Override
	@SuppressWarnings("NullAway")
	protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException {
                // ....
			HttpRequest request = buildRequest(headers, body);
			responseFuture = this.httpClient.sendAsync(request, 
                                          new DecompressingBodyHandler() // <--- This part is not customizable
                         ); 
                // ....
        }

@bclozel
Copy link
Member

bclozel commented Aug 26, 2025

Yes, that's exactly the point 👍
We do not expose all Spring Framework classes as public on purpose. If your application needs to further customize BodyHandler, you should use HttpClient directly as our goal here is not to replace it, but rather provide a convenient common API for many HTTP clients.

@driverpt
Copy link

driverpt commented Aug 26, 2025

Agree, but IMHO you should make if configurable, defaulting to new DecompressingBodyHandler(). If we just want to add some additional flavor to the BodyHandler or apply a workaround for an issue, We would need to implement a whole JdkClientHttpFactory of our own. Which kind of goes against the purpose of re-usable Spring Components, this way you are making an opinionated choice, forcing everyone to use Spring's own implementation of a BodyHandler

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants