JerseyClient with gzipEnabled=true double-decompresses gzip responses → ZipException (HttpClient5 + GZipDecoder)
Version: Dropwizard 5.0.0 (dropwizard-client)
What happens
With JerseyClientConfiguration.gzipEnabled = true (the default), calling readEntity on a response that the upstream gzipped throws java.util.zip.ZipException: Not in GZIP format:
java.util.zip.ZipException: Not in GZIP format
at java.base/java.util.zip.GZIPInputStream.readHeader(GZIPInputStream.java:199)
at io.dropwizard.jersey.gzip.GZipDecoder.aroundReadFrom(GZipDecoder.java:36)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(...)
at org.glassfish.jersey.client.ClientResponse.readEntity(ClientResponse.java:307)
...
at io.dropwizard.client.DropwizardApacheConnector.apply(DropwizardApacheConnector.java)Cause
With gzipEnabled = true, JerseyClientBuilder:
- calls
HttpClientBuilder.disableContentCompression(false)— so Apache HttpClient 5'sContentCompressionExecadvertisesAccept-Encodingand transparently decompresses the response entity; and - registers Jersey's
GZipDecoderreader interceptor.
So two decoders are active. DropwizardApacheConnector.apply() copies the raw HttpClient response headers — including Content-Encoding: gzip — into the Jersey ClientResponse, while handing over the entity stream HttpClient has already decompressed. GZipDecoder then sees the stale Content-Encoding header and wraps the already-plain stream in a GZIPInputStream, so readEntity fails.
Both the HttpClient decoder and GZipDecoder are gated on the single gzipEnabled flag, so there's no configuration that yields "one decoder while still negotiating gzip on the wire": it's either both (double decode) or neither (gzipEnabled: false, no compression at all).
Expected
Exactly one decode. When HttpClient's transparent content decompression is active, DropwizardApacheConnector should drop the now-inconsistent Content-Encoding (and Content-Length) headers before they reach Jersey — the standard behavior of an HTTP client that auto-decompresses — so that GZipDecoder correctly no-ops.
Reproduction
- Build a
JerseyClientviaJerseyClientBuilderwithgzipEnabled = true. - GET an endpoint that returns a gzipped body with
Content-Encoding: gzip(e.g. any Jetty/Dropwizard upstream over itsminimumEntitySize, or WireMock's auto-gzip). response.readEntity(String.class)throwsProcessingExceptioncaused byZipException: Not in GZIP format.
Workaround
Per outbound call, request identity encoding: .request().acceptEncoding("identity"). This keeps the response uncompressed so neither decoder engages — but it opts out of compression per call site rather than fixing the double-decode.
Source: dropwizard/dropwizard