diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml
index 6a59bde6c..2586cf3c6 100644
--- a/.github/workflows/builds.yml
+++ b/.github/workflows/builds.yml
@@ -5,34 +5,50 @@ on:
     - cron: '0 12 * * *'
 
 jobs:
+  Verify:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Grant Permission
+        run: chmod +x ./mvnw
+      - uses: actions/setup-java@v4
+        with:
+          distribution: 'corretto'
+          java-version: '11'
+      - name: Verify
+        run: ./mvnw -B -ntp clean verify -DskipTests -Dgpg.skip=true
+
   RunOnLinux:
     runs-on: ubuntu-latest
+    needs: Verify
     steps:
       - uses: actions/checkout@v4
       - name: Grant Permission
-        run: sudo chmod +x ./mvnw
+        run: chmod +x ./mvnw
       - uses: actions/setup-java@v4
         with:
           distribution: 'corretto'
           java-version: '11'
       - name: Run Tests
-        run: ./mvnw -B -ntp clean test
+        run: ./mvnw -B -ntp test
 
   RunOnMacOs:
     runs-on: macos-latest
+    needs: Verify
     steps:
       - uses: actions/checkout@v4
       - name: Grant Permission
-        run: sudo chmod +x ./mvnw
+        run: chmod +x ./mvnw
       - uses: actions/setup-java@v4
         with:
           distribution: 'corretto'
           java-version: '11'
       - name: Run Tests
-        run: ./mvnw -B -ntp clean test
+        run: ./mvnw -B -ntp test
 
   RunOnWindows:
     runs-on: windows-latest
+    needs: Verify
     steps:
       - uses: actions/checkout@v4
       - uses: actions/setup-java@v4
@@ -40,4 +56,4 @@ jobs:
           distribution: 'corretto'
           java-version: '11'
       - name: Run Tests
-        run: ./mvnw.cmd -B -ntp clean test
+        run: ./mvnw.cmd -B -ntp test
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 51dc38f90..b175fa865 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -27,7 +27,7 @@ jobs:
         run: rm -f /home/runner/.m2/settings.xml
 
       - name: Maven Settings
-        uses: s4u/maven-settings-action@v2.2.0
+        uses: s4u/maven-settings-action@v3.1.0
         with:
           servers: |
             [{
@@ -37,7 +37,7 @@ jobs:
             }]
 
       - name: Import GPG
-        uses: crazy-max/ghaction-import-gpg@v5.2.0
+        uses: crazy-max/ghaction-import-gpg@v6.3.0
         with:
           gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
           passphrase: ${{ secrets.GPG_PASSPHRASE }}
diff --git a/LICENSE.txt b/LICENSE.txt
index d8e4ed073..85a16d3d0 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-   Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+   Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/LICENSES/LICENSE.zstd-jni.txt b/LICENSES/LICENSE.zstd-jni.txt
new file mode 100644
index 000000000..66abb8ae7
--- /dev/null
+++ b/LICENSES/LICENSE.zstd-jni.txt
@@ -0,0 +1,26 @@
+Zstd-jni: JNI bindings to Zstd Library
+
+Copyright (c) 2015-present, Luben Karavelov/ All rights reserved.
+
+BSD License
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice, this
+  list of conditions and the following disclaimer in the documentation and/or
+  other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
index 111dc9643..0272134ed 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Maven:
     <dependency>
         <groupId>org.asynchttpclient</groupId>
         <artifactId>async-http-client</artifactId>
-        <version>3.0.0.Beta3</version>
+        <version>3.0.2</version>
     </dependency>
 </dependencies>
 ```
@@ -28,24 +28,10 @@ Maven:
 Gradle:
 ```groovy
 dependencies {
-    implementation 'org.asynchttpclient:async-http-client:3.0.0.Beta3'
+    implementation 'org.asynchttpclient:async-http-client:3.0.2'
 }
 ```
 
-## Version
-
-AHC doesn't use SEMVER, and won't.
-
-* MAJOR = huge refactoring
-* MINOR = new features and minor API changes, upgrading should require 1 hour of work to adapt sources
-* FIX = no API change, just bug fixes, only those are source and binary compatible with same minor version
-
-Check CHANGES.md for migration path between versions.
-
-## Basics
-
-Feel free to check the [Javadoc](http://www.javadoc.io/doc/org.asynchttpclient/async-http-client/) or the code for more information.
-
 ### Dsl
 
 Import the Dsl helpers to use convenient methods to bootstrap components:
@@ -112,7 +98,7 @@ This body can be of type:
 * `String`
 * `java.nio.ByteBuffer`
 * `java.io.InputStream`
-* `Publisher<io.netty.bufferByteBuf>`
+* `Publisher<io.netty.buffer.ByteBuf>`
 * `org.asynchttpclient.request.body.generator.BodyGenerator`
 
 `BodyGenerator` is a generic abstraction that let you create request bodies on the fly.
@@ -244,75 +230,34 @@ Async Http Client also supports WebSocket.
 You need to pass a `WebSocketUpgradeHandler` where you would register a `WebSocketListener`.
 
 ```java
-WebSocket websocket=c.prepareGet("ws://demos.kaazing.com/echo")
+WebSocket websocket = c.prepareGet("ws://demos.kaazing.com/echo")
         .execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(
-        new WebSocketListener(){
-
-            @Override
-            public void onOpen(WebSocket websocket){
-                websocket.sendTextFrame("...").sendTextFrame("...");
-            }
-
-            @Override
-            public void onClose(WebSocket websocket) {
-                // ...
-            }
-
-            @Override
-            public void onTextFrame(String payload,boolean finalFragment,int rsv){
-                 System.out.println(payload);
-            }
-
-            @Override
-            public void onError(Throwable t){
-                t.printStackTrace();
-            }
-        }).build()).get();
+                new WebSocketListener() {
+
+                  @Override
+                  public void onOpen(WebSocket websocket) {
+                    websocket.sendTextFrame("...").sendTextFrame("...");
+                  }
+
+                  @Override
+                  public void onClose(WebSocket websocket) {
+                    // ...
+                  }
+
+                  @Override
+                  public void onTextFrame(String payload, boolean finalFragment, int rsv) {
+                    System.out.println(payload);
+                  }
+
+                  @Override
+                  public void onError(Throwable t) {
+                    t.printStackTrace();
+                  }
+                }).build()).get();
 ```
 
-## WebDAV
-
-AsyncHttpClient has build in support for the WebDAV protocol.
-The API can be used the same way normal HTTP request are made:
-
-```java
-        Request mkcolRequest=new RequestBuilder("MKCOL").setUrl("http://host:port/folder1").build();
-        Response response=c.executeRequest(mkcolRequest).get();
-```
-
-or
-
-```java
-        Request propFindRequest=new RequestBuilder("PROPFIND").setUrl("http://host:port").build();
-        Response response=c.executeRequest(propFindRequest,new AsyncHandler() {
-            // ...
-        }).get();
-```
-
-## More
-
-You can find more information on Jean-François Arcand's blog. Jean-François is the original author of this library.
-Code is sometimes not up-to-date but gives a pretty good idea of advanced features.
-
-* http://web.archive.org/web/20111224171448/http://jfarcand.wordpress.com/2011/01/12/going-asynchronous-using-asynchttpclient-for-dummies/
-* http://web.archive.org/web/20111224171241/http://jfarcand.wordpress.com/2010/12/21/going-asynchronous-using-asynchttpclient-the-basic/
-* http://web.archive.org/web/20111224162752/http://jfarcand.wordpress.com/2011/01/04/going-asynchronous-using-asynchttpclient-the-complex/
-* http://web.archive.org/web/20120218183108/http://jfarcand.wordpress.com/2011/12/21/writing-websocket-clients-using-asynchttpclient/
-
 ## User Group
 
 Keep up to date on the library development by joining the Asynchronous HTTP Client discussion group
 
 [GitHub Discussions](https://github.com/AsyncHttpClient/async-http-client/discussions)
-
-## Contributing
-
-Of course, Pull Requests are welcome.
-
-Here are the few rules we'd like you to respect if you do so:
-
-* Only edit the code related to the suggested change, so DON'T automatically format the classes you've edited.
-* Use IntelliJ default formatting rules.
-* Regarding licensing:
-    * You must be the original author of the code you suggest.
-    * You must give the copyright to "the AsyncHttpClient Project"
diff --git a/client/pom.xml b/client/pom.xml
index 7a7efb499..6e56bf9fc 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>org.asynchttpclient</groupId>
         <artifactId>async-http-client-project</artifactId>
-        <version>3.0.0.Beta3</version>
+        <version>3.0.2</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
@@ -30,20 +30,28 @@
     <properties>
         <javaModuleName>org.asynchttpclient.client</javaModuleName>
 
-        <jetty.version>11.0.16</jetty.version>
-        <tomcat.version>10.1.19</tomcat.version>
-        <commons-io.version>2.11.0</commons-io.version>
+        <jetty.version>11.0.24</jetty.version>
+        <tomcat.version>10.1.42</tomcat.version>
+        <commons-io.version>2.18.0</commons-io.version>
         <mockito.version>4.11.0</mockito.version>
-        <hamcrest.version>2.2</hamcrest.version>
-        <kerby.version>2.0.2</kerby.version>
+        <hamcrest.version>3.0</hamcrest.version>
+        <kerby.version>2.1.0</kerby.version>
     </properties>
 
+    <developers>
+        <developer>
+            <id>hyperxpro</id>
+            <name>Aayush Atharva</name>
+            <email>aayush@shieldblaze.com</email>
+        </developer>
+    </developers>
+
     <dependencies>
         <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
         <dependency>
             <groupId>commons-fileupload</groupId>
             <artifactId>commons-fileupload</artifactId>
-            <version>1.5</version>
+            <version>1.6.0</version>
             <scope>test</scope>
         </dependency>
 
diff --git a/client/src/main/java/org/asynchttpclient/AsyncHandler.java b/client/src/main/java/org/asynchttpclient/AsyncHandler.java
index a912ad9c5..22451fe09 100644
--- a/client/src/main/java/org/asynchttpclient/AsyncHandler.java
+++ b/client/src/main/java/org/asynchttpclient/AsyncHandler.java
@@ -116,7 +116,8 @@ default State onTrailingHeadersReceived(HttpHeaders headers) throws Exception {
      * @return T Value that will be returned by the associated {@link Future}
      * @throws Exception if something wrong happens
      */
-    @Nullable T onCompleted() throws Exception;
+    @Nullable
+    T onCompleted() throws Exception;
 
     /**
      * Notify the callback before hostname resolution
diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java
index 484cd0029..954628b3d 100644
--- a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java
+++ b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java
@@ -160,7 +160,8 @@ public interface AsyncHttpClientConfig {
      * @return the {@link ThreadFactory} an {@link AsyncHttpClient} use for handling asynchronous response. If no {@link ThreadFactory} has been explicitly
      * provided, this method will return {@code null}
      */
-    @Nullable ThreadFactory getThreadFactory();
+    @Nullable
+    ThreadFactory getThreadFactory();
 
     /**
      * An instance of {@link ProxyServer} used by an {@link AsyncHttpClient}
@@ -174,14 +175,16 @@ public interface AsyncHttpClientConfig {
      *
      * @return an instance of {@link SslContext} used for SSL connection.
      */
-    @Nullable SslContext getSslContext();
+    @Nullable
+    SslContext getSslContext();
 
     /**
      * Return the current {@link Realm}
      *
      * @return the current {@link Realm}
      */
-    @Nullable Realm getRealm();
+    @Nullable
+    Realm getRealm();
 
     /**
      * Return the list of {@link RequestFilter}
@@ -260,12 +263,14 @@ public interface AsyncHttpClientConfig {
     /**
      * @return the array of enabled protocols
      */
-    @Nullable String[] getEnabledProtocols();
+    @Nullable
+    String[] getEnabledProtocols();
 
     /**
      * @return the array of enabled cipher suites
      */
-    @Nullable String[] getEnabledCipherSuites();
+    @Nullable
+    String[] getEnabledCipherSuites();
 
     /**
      * @return if insecure cipher suites must be filtered out (only used when not explicitly passing enabled cipher suites)
@@ -294,7 +299,8 @@ public interface AsyncHttpClientConfig {
 
     int getHandshakeTimeout();
 
-    @Nullable SslEngineFactory getSslEngineFactory();
+    @Nullable
+    SslEngineFactory getSslEngineFactory();
 
     int getChunkedFileChunkSize();
 
@@ -310,23 +316,29 @@ public interface AsyncHttpClientConfig {
 
     Map<ChannelOption<Object>, Object> getChannelOptions();
 
-    @Nullable EventLoopGroup getEventLoopGroup();
+    @Nullable
+    EventLoopGroup getEventLoopGroup();
 
     boolean isUseNativeTransport();
 
     boolean isUseOnlyEpollNativeTransport();
 
-    @Nullable Consumer<Channel> getHttpAdditionalChannelInitializer();
+    @Nullable
+    Consumer<Channel> getHttpAdditionalChannelInitializer();
 
-    @Nullable Consumer<Channel> getWsAdditionalChannelInitializer();
+    @Nullable
+    Consumer<Channel> getWsAdditionalChannelInitializer();
 
     ResponseBodyPartFactory getResponseBodyPartFactory();
 
-    @Nullable ChannelPool getChannelPool();
+    @Nullable
+    ChannelPool getChannelPool();
 
-    @Nullable ConnectionSemaphoreFactory getConnectionSemaphoreFactory();
+    @Nullable
+    ConnectionSemaphoreFactory getConnectionSemaphoreFactory();
 
-    @Nullable Timer getNettyTimer();
+    @Nullable
+    Timer getNettyTimer();
 
     /**
      * @return the duration between tick of {@link HashedWheelTimer}
@@ -358,10 +370,18 @@ public interface AsyncHttpClientConfig {
 
     int getSoRcvBuf();
 
-    @Nullable ByteBufAllocator getAllocator();
+    @Nullable
+    ByteBufAllocator getAllocator();
 
     int getIoThreadsCount();
 
+    /**
+     * Indicates whether the Authorization header should be stripped during redirects to a different domain.
+     *
+     * @return true if the Authorization header should be stripped, false otherwise.
+     */
+    boolean isStripAuthorizationOnRedirect();
+
     enum ResponseBodyPartFactory {
 
         EAGER {
diff --git a/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java b/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java
index c2e2bce29..99b3cc5d0 100644
--- a/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java
+++ b/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/ClientStats.java b/client/src/main/java/org/asynchttpclient/ClientStats.java
index 7d450ad96..eef529221 100644
--- a/client/src/main/java/org/asynchttpclient/ClientStats.java
+++ b/client/src/main/java/org/asynchttpclient/ClientStats.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java
index fb5dad6ff..3b417a5a3 100644
--- a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java
+++ b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java
@@ -24,8 +24,8 @@
 import org.asynchttpclient.channel.ChannelPool;
 import org.asynchttpclient.cookie.CookieEvictionTask;
 import org.asynchttpclient.cookie.CookieStore;
-import org.asynchttpclient.filter.FilterContext;
 import org.asynchttpclient.exception.FilterException;
+import org.asynchttpclient.filter.FilterContext;
 import org.asynchttpclient.filter.RequestFilter;
 import org.asynchttpclient.handler.resumable.ResumableAsyncHandler;
 import org.asynchttpclient.netty.channel.ChannelManager;
@@ -235,7 +235,7 @@ public <T> ListenableFuture<T> executeRequest(Request request, AsyncHandler<T> h
                 if (!cookies.isEmpty()) {
                     RequestBuilder requestBuilder = request.toBuilder();
                     for (Cookie cookie : cookies) {
-                        requestBuilder.addOrReplaceCookie(cookie);
+                        requestBuilder.addCookieIfUnset(cookie);
                     }
                     request = requestBuilder.build();
                 }
diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java
index 0357592bd..1c7dbf37f 100644
--- a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java
+++ b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java
@@ -127,6 +127,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
     private final boolean keepEncodingHeader;
     private final ProxyServerSelector proxyServerSelector;
     private final boolean validateResponseHeaders;
+    private final boolean stripAuthorizationOnRedirect;
 
     // websockets
     private final boolean aggregateWebSocketFrameFragments;
@@ -219,6 +220,7 @@ private DefaultAsyncHttpClientConfig(// http
                                          boolean validateResponseHeaders,
                                          boolean aggregateWebSocketFrameFragments,
                                          boolean enablewebSocketCompression,
+                                         boolean stripAuthorizationOnRedirect,
 
                                          // timeouts
                                          Duration connectTimeout,
@@ -307,6 +309,7 @@ private DefaultAsyncHttpClientConfig(// http
         this.keepEncodingHeader = keepEncodingHeader;
         this.proxyServerSelector = proxyServerSelector;
         this.validateResponseHeaders = validateResponseHeaders;
+        this.stripAuthorizationOnRedirect = stripAuthorizationOnRedirect;
 
         // websocket
         this.aggregateWebSocketFrameFragments = aggregateWebSocketFrameFragments;
@@ -564,6 +567,11 @@ public boolean isValidateResponseHeaders() {
         return validateResponseHeaders;
     }
 
+    @Override
+    public boolean isStripAuthorizationOnRedirect() {
+        return stripAuthorizationOnRedirect;
+    }
+
     // ssl
     @Override
     public boolean isUseOpenSsl() {
@@ -800,6 +808,7 @@ public static class Builder {
         private boolean useProxySelector = defaultUseProxySelector();
         private boolean useProxyProperties = defaultUseProxyProperties();
         private boolean validateResponseHeaders = defaultValidateResponseHeaders();
+        private boolean stripAuthorizationOnRedirect = false; // default value
 
         // websocket
         private boolean aggregateWebSocketFrameFragments = defaultAggregateWebSocketFrameFragments();
@@ -890,6 +899,8 @@ public Builder(AsyncHttpClientConfig config) {
             disableZeroCopy = config.isDisableZeroCopy();
             keepEncodingHeader = config.isKeepEncodingHeader();
             proxyServerSelector = config.getProxyServerSelector();
+            validateResponseHeaders = config.isValidateResponseHeaders();
+            stripAuthorizationOnRedirect = config.isStripAuthorizationOnRedirect();
 
             // websocket
             aggregateWebSocketFrameFragments = config.isAggregateWebSocketFrameFragments();
@@ -907,15 +918,19 @@ public Builder(AsyncHttpClientConfig config) {
             // keep-alive
             keepAlive = config.isKeepAlive();
             pooledConnectionIdleTimeout = config.getPooledConnectionIdleTimeout();
+            connectionPoolCleanerPeriod = config.getConnectionPoolCleanerPeriod();
             connectionTtl = config.getConnectionTtl();
             maxConnections = config.getMaxConnections();
             maxConnectionsPerHost = config.getMaxConnectionsPerHost();
             channelPool = config.getChannelPool();
             connectionSemaphoreFactory = config.getConnectionSemaphoreFactory();
             keepAliveStrategy = config.getKeepAliveStrategy();
+            acquireFreeChannelTimeout = config.getAcquireFreeChannelTimeout();
 
             // ssl
+            useOpenSsl = config.isUseOpenSsl();
             useInsecureTrustManager = config.isUseInsecureTrustManager();
+            disableHttpsEndpointIdentificationAlgorithm = config.isDisableHttpsEndpointIdentificationAlgorithm();
             handshakeTimeout = config.getHandshakeTimeout();
             enabledProtocols = config.getEnabledProtocols();
             enabledCipherSuites = config.getEnabledCipherSuites();
@@ -930,6 +945,10 @@ public Builder(AsyncHttpClientConfig config) {
             responseFilters.addAll(config.getResponseFilters());
             ioExceptionFilters.addAll(config.getIoExceptionFilters());
 
+            // cookie store
+            cookieStore = config.getCookieStore();
+            expiredCookieEvictionDelay = config.expiredCookieEvictionDelay();
+
             // tuning
             tcpNoDelay = config.isTcpNoDelay();
             soReuseAddress = config.isSoReuseAddress();
@@ -943,6 +962,7 @@ public Builder(AsyncHttpClientConfig config) {
             httpClientCodecMaxInitialLineLength = config.getHttpClientCodecMaxInitialLineLength();
             httpClientCodecMaxHeaderSize = config.getHttpClientCodecMaxHeaderSize();
             httpClientCodecMaxChunkSize = config.getHttpClientCodecMaxChunkSize();
+            httpClientCodecInitialBufferSize = config.getHttpClientCodecInitialBufferSize();
             chunkedFileChunkSize = config.getChunkedFileChunkSize();
             channelOptions.putAll(config.getChannelOptions());
             eventLoopGroup = config.getEventLoopGroup();
@@ -1069,6 +1089,11 @@ public Builder setUseProxyProperties(boolean useProxyProperties) {
             return this;
         }
 
+        public Builder setStripAuthorizationOnRedirect(boolean value) {
+            stripAuthorizationOnRedirect = value;
+            return this;
+        }
+
         // websocket
         public Builder setAggregateWebSocketFrameFragments(boolean aggregateWebSocketFrameFragments) {
             this.aggregateWebSocketFrameFragments = aggregateWebSocketFrameFragments;
@@ -1434,6 +1459,7 @@ public DefaultAsyncHttpClientConfig build() {
                     validateResponseHeaders,
                     aggregateWebSocketFrameFragments,
                     enablewebSocketCompression,
+                    stripAuthorizationOnRedirect,
                     connectTimeout,
                     requestTimeout,
                     readTimeout,
diff --git a/client/src/main/java/org/asynchttpclient/DefaultRequest.java b/client/src/main/java/org/asynchttpclient/DefaultRequest.java
index 4170c33e2..09c615d2a 100644
--- a/client/src/main/java/org/asynchttpclient/DefaultRequest.java
+++ b/client/src/main/java/org/asynchttpclient/DefaultRequest.java
@@ -15,6 +15,7 @@
  */
 package org.asynchttpclient;
 
+import io.netty.buffer.ByteBuf;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.cookie.Cookie;
 import io.netty.resolver.NameResolver;
@@ -51,6 +52,7 @@ public class DefaultRequest implements Request {
     private final @Nullable List<byte[]> compositeByteData;
     private final @Nullable String stringData;
     private final @Nullable ByteBuffer byteBufferData;
+    private final @Nullable ByteBuf byteBufData;
     private final @Nullable InputStream streamData;
     private final @Nullable BodyGenerator bodyGenerator;
     private final List<Param> formParams;
@@ -79,6 +81,7 @@ public DefaultRequest(String method,
                           @Nullable List<byte[]> compositeByteData,
                           @Nullable String stringData,
                           @Nullable ByteBuffer byteBufferData,
+                          @Nullable ByteBuf byteBufData,
                           @Nullable InputStream streamData,
                           @Nullable BodyGenerator bodyGenerator,
                           List<Param> formParams,
@@ -104,6 +107,7 @@ public DefaultRequest(String method,
         this.compositeByteData = compositeByteData;
         this.stringData = stringData;
         this.byteBufferData = byteBufferData;
+        this.byteBufData = byteBufData;
         this.streamData = streamData;
         this.bodyGenerator = bodyGenerator;
         this.formParams = formParams;
@@ -176,6 +180,11 @@ public List<Cookie> getCookies() {
         return byteBufferData;
     }
 
+    @Override
+    public @Nullable ByteBuf getByteBufData() {
+        return byteBufData;
+    }
+
     @Override
     public @Nullable InputStream getStreamData() {
         return streamData;
diff --git a/client/src/main/java/org/asynchttpclient/HostStats.java b/client/src/main/java/org/asynchttpclient/HostStats.java
index 9ec52805a..3470ea4e1 100644
--- a/client/src/main/java/org/asynchttpclient/HostStats.java
+++ b/client/src/main/java/org/asynchttpclient/HostStats.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java b/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java
index 0be4dedb5..0df78f7b2 100644
--- a/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java
+++ b/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java
@@ -15,6 +15,8 @@
  */
 package org.asynchttpclient;
 
+import io.netty.buffer.ByteBuf;
+
 import java.nio.ByteBuffer;
 
 /**
@@ -44,6 +46,12 @@ protected HttpResponseBodyPart(boolean last) {
      */
     public abstract ByteBuffer getBodyByteBuffer();
 
+    /**
+     * @return the {@link ByteBuf} of the bytes read from the response's chunk.
+     * The {@link ByteBuf}'s capacity is equal to the number of bytes available.
+     */
+    public abstract ByteBuf getBodyByteBuf();
+
     /**
      * @return true if this is the last part.
      */
diff --git a/client/src/main/java/org/asynchttpclient/Param.java b/client/src/main/java/org/asynchttpclient/Param.java
index cbc35e196..4f7a5530a 100644
--- a/client/src/main/java/org/asynchttpclient/Param.java
+++ b/client/src/main/java/org/asynchttpclient/Param.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/Request.java b/client/src/main/java/org/asynchttpclient/Request.java
index 64977cf71..1d95016b3 100644
--- a/client/src/main/java/org/asynchttpclient/Request.java
+++ b/client/src/main/java/org/asynchttpclient/Request.java
@@ -16,6 +16,7 @@
  */
 package org.asynchttpclient;
 
+import io.netty.buffer.ByteBuf;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.cookie.Cookie;
 import io.netty.resolver.NameResolver;
@@ -66,12 +67,14 @@ public interface Request {
     /**
      * @return the InetAddress to be used to bypass uri's hostname resolution
      */
-    @Nullable InetAddress getAddress();
+    @Nullable
+    InetAddress getAddress();
 
     /**
      * @return the local address to bind from
      */
-    @Nullable InetAddress getLocalAddress();
+    @Nullable
+    InetAddress getLocalAddress();
 
     /**
      * @return the HTTP headers
@@ -91,27 +94,38 @@ public interface Request {
     /**
      * @return the request's body array of byte arrays (only non-null if it was set this way)
      */
-    @Nullable List<byte[]> getCompositeByteData();
+    @Nullable
+    List<byte[]> getCompositeByteData();
 
     /**
      * @return the request's body string (only non-null if it was set this way)
      */
-    @Nullable String getStringData();
+    @Nullable
+    String getStringData();
 
     /**
      * @return the request's body ByteBuffer (only non-null if it was set this way)
      */
-    @Nullable ByteBuffer getByteBufferData();
+    @Nullable
+    ByteBuffer getByteBufferData();
+
+    /**
+     * @return the request's body ByteBuf (only non-null if it was set this way)
+     */
+    @Nullable
+    ByteBuf getByteBufData();
 
     /**
      * @return the request's body InputStream (only non-null if it was set this way)
      */
-    @Nullable InputStream getStreamData();
+    @Nullable
+    InputStream getStreamData();
 
     /**
      * @return the request's body BodyGenerator (only non-null if it was set this way)
      */
-    @Nullable BodyGenerator getBodyGenerator();
+    @Nullable
+    BodyGenerator getBodyGenerator();
 
     /**
      * @return the request's form parameters
@@ -126,7 +140,8 @@ public interface Request {
     /**
      * @return the virtual host to connect to
      */
-    @Nullable String getVirtualHost();
+    @Nullable
+    String getVirtualHost();
 
     /**
      * @return the query params resolved from the url/uri
@@ -136,22 +151,26 @@ public interface Request {
     /**
      * @return the proxy server to be used to perform this request (overrides the one defined in config)
      */
-    @Nullable ProxyServer getProxyServer();
+    @Nullable
+    ProxyServer getProxyServer();
 
     /**
      * @return the realm to be used to perform this request (overrides the one defined in config)
      */
-    @Nullable Realm getRealm();
+    @Nullable
+    Realm getRealm();
 
     /**
      * @return the file to be uploaded
      */
-    @Nullable File getFile();
+    @Nullable
+    File getFile();
 
     /**
      * @return if this request is to follow redirects. Non null values means "override config value".
      */
-    @Nullable Boolean getFollowRedirect();
+    @Nullable
+    Boolean getFollowRedirect();
 
     /**
      * @return the request timeout. Non zero values means "override config value".
@@ -171,7 +190,8 @@ public interface Request {
     /**
      * @return the charset value used when decoding the request's body.
      */
-    @Nullable Charset getCharset();
+    @Nullable
+    Charset getCharset();
 
     /**
      * @return the strategy to compute ChannelPool's keys
diff --git a/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java
index dbfd58f5b..dbc5e4144 100644
--- a/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java
+++ b/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java
@@ -15,6 +15,7 @@
  */
 package org.asynchttpclient;
 
+import io.netty.buffer.ByteBuf;
 import io.netty.handler.codec.http.DefaultHttpHeaders;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.cookie.Cookie;
@@ -76,6 +77,7 @@ public abstract class RequestBuilderBase<T extends RequestBuilderBase<T>> {
     protected @Nullable List<byte[]> compositeByteData;
     protected @Nullable String stringData;
     protected @Nullable ByteBuffer byteBufferData;
+    protected @Nullable ByteBuf byteBufData;
     protected @Nullable InputStream streamData;
     protected @Nullable BodyGenerator bodyGenerator;
     protected @Nullable List<Param> formParams;
@@ -121,6 +123,7 @@ protected RequestBuilderBase(Request prototype, boolean disableUrlEncoding, bool
         compositeByteData = prototype.getCompositeByteData();
         stringData = prototype.getStringData();
         byteBufferData = prototype.getByteBufferData();
+        byteBufData = prototype.getByteBufData();
         streamData = prototype.getStreamData();
         bodyGenerator = prototype.getBodyGenerator();
         if (isNonEmpty(prototype.getFormParams())) {
@@ -320,6 +323,21 @@ public T addCookie(Cookie cookie) {
      * @return this
      */
     public T addOrReplaceCookie(Cookie cookie) {
+        return maybeAddOrReplaceCookie(cookie, true);
+    }
+
+    /**
+     * Add a cookie based on its name, if it does not exist yet. Cookies that
+     * are already set will be ignored.
+     *
+     * @param cookie the new cookie
+     * @return this
+     */
+    public T addCookieIfUnset(Cookie cookie) {
+        return maybeAddOrReplaceCookie(cookie, false);
+    }
+
+    private T maybeAddOrReplaceCookie(Cookie cookie, boolean allowReplace) {
         String cookieKey = cookie.name();
         boolean replace = false;
         int index = 0;
@@ -332,10 +350,10 @@ public T addOrReplaceCookie(Cookie cookie) {
 
             index++;
         }
-        if (replace) {
-            cookies.set(index, cookie);
-        } else {
+        if (!replace) {
             cookies.add(cookie);
+        } else if (allowReplace) {
+            cookies.set(index, cookie);
         }
         return asDerivedType();
     }
@@ -361,6 +379,7 @@ public void resetNonMultipartData() {
         byteData = null;
         compositeByteData = null;
         byteBufferData = null;
+        byteBufData = null;
         stringData = null;
         streamData = null;
         bodyGenerator = null;
@@ -405,6 +424,12 @@ public T setBody(ByteBuffer data) {
         return asDerivedType();
     }
 
+    public T setBody(ByteBuf data) {
+        resetBody();
+        byteBufData = data;
+        return asDerivedType();
+    }
+
     public T setBody(InputStream stream) {
         resetBody();
         streamData = stream;
@@ -586,6 +611,7 @@ private RequestBuilderBase<?> executeSignatureCalculator() {
         rb.compositeByteData = compositeByteData;
         rb.stringData = stringData;
         rb.byteBufferData = byteBufferData;
+        rb.byteBufData = byteBufData;
         rb.streamData = streamData;
         rb.bodyGenerator = bodyGenerator;
         rb.virtualHost = virtualHost;
@@ -647,6 +673,7 @@ public Request build() {
                 rb.compositeByteData,
                 rb.stringData,
                 rb.byteBufferData,
+                rb.byteBufData,
                 rb.streamData,
                 rb.bodyGenerator,
                 formParamsCopy,
diff --git a/client/src/main/java/org/asynchttpclient/Response.java b/client/src/main/java/org/asynchttpclient/Response.java
index 8b9c9a6f1..220d989b0 100644
--- a/client/src/main/java/org/asynchttpclient/Response.java
+++ b/client/src/main/java/org/asynchttpclient/Response.java
@@ -16,6 +16,7 @@
  */
 package org.asynchttpclient;
 
+import io.netty.buffer.ByteBuf;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.cookie.Cookie;
 import org.asynchttpclient.netty.NettyResponse;
@@ -61,6 +62,13 @@ public interface Response {
      */
     ByteBuffer getResponseBodyAsByteBuffer();
 
+    /**
+     * Return the entire response body as a ByteBuf.
+     *
+     * @return the entire response body as a ByteBuf.
+     */
+    ByteBuf getResponseBodyAsByteBuf();
+
     /**
      * Returns an input stream for the response body. Note that you should not try to get this more than once, and that you should not close the stream.
      *
diff --git a/client/src/main/java/org/asynchttpclient/SslEngineFactory.java b/client/src/main/java/org/asynchttpclient/SslEngineFactory.java
index d007106f7..15ec9748e 100644
--- a/client/src/main/java/org/asynchttpclient/SslEngineFactory.java
+++ b/client/src/main/java/org/asynchttpclient/SslEngineFactory.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java b/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java
index 1b38f09a3..4f2bc3b9b 100755
--- a/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java
+++ b/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -38,7 +38,8 @@ public interface ChannelPool {
      * @param partitionKey the partition used when invoking offer
      * @return the channel associated with the uri
      */
-    @Nullable Channel poll(Object partitionKey);
+    @Nullable
+    Channel poll(Object partitionKey);
 
     /**
      * Remove all channels from the cache. A channel might have been associated
diff --git a/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java b/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java
index fd9a51b23..c91ed6bda 100644
--- a/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java
+++ b/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -15,13 +15,13 @@
  */
 package org.asynchttpclient.channel;
 
+import java.util.Objects;
+
 import org.asynchttpclient.proxy.ProxyServer;
 import org.asynchttpclient.proxy.ProxyType;
 import org.asynchttpclient.uri.Uri;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Objects;
-
 @FunctionalInterface
 public interface ChannelPoolPartitioning {
 
@@ -111,7 +111,8 @@ public String toString() {
                     ", virtualHost=" + virtualHost +
                     ", proxyHost=" + proxyHost +
                     ", proxyPort=" + proxyPort +
-                    ", proxyType=" + proxyType;
+                    ", proxyType=" + proxyType +
+                    ")";
         }
     }
 }
diff --git a/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java b/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java
index 106799ddb..e72cc8c13 100644
--- a/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java
+++ b/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java b/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java
index 3a5b2e93d..ae3aab81a 100644
--- a/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java
+++ b/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@ public enum NoopChannelPool implements ChannelPool {
     INSTANCE;
 
     /**
-     *
      * @return always false since this is a {@link NoopChannelPool}
      */
     @Override
@@ -39,7 +38,6 @@ public boolean offer(Channel channel, Object partitionKey) {
     }
 
     /**
-     *
      * @return always null since this is a {@link NoopChannelPool}
      */
     @Override
@@ -48,7 +46,6 @@ public boolean offer(Channel channel, Object partitionKey) {
     }
 
     /**
-     *
      * @return always false since this is a {@link NoopChannelPool}
      */
     @Override
@@ -57,7 +54,6 @@ public boolean removeAll(Channel channel) {
     }
 
     /**
-     *
      * @return always true since this is a {@link NoopChannelPool}
      */
     @Override
@@ -66,7 +62,6 @@ public boolean isOpen() {
     }
 
     /**
-     *
      * Does nothing since this is a {@link NoopChannelPool}
      */
     @Override
@@ -74,7 +69,6 @@ public void destroy() {
     }
 
     /**
-     *
      * Does nothing since this is a {@link NoopChannelPool}
      */
     @Override
@@ -82,7 +76,6 @@ public void flushPartitions(Predicate<Object> predicate) {
     }
 
     /**
-     *
      * @return always {@link Collections#emptyMap()} since this is a {@link NoopChannelPool}
      */
     @Override
diff --git a/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java
index 3d4cb6106..3596c67a9 100644
--- a/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java
+++ b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java b/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java
index 35a620e31..5832185cc 100644
--- a/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java
+++ b/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java
@@ -229,7 +229,7 @@ private void removeExpired() {
 
         cookieJar.values()
                 .forEach(cookieMap -> removed[0] |= cookieMap.entrySet()
-                .removeIf(v -> hasCookieExpired(v.getValue().cookie, v.getValue().createdAt)));
+                        .removeIf(v -> hasCookieExpired(v.getValue().cookie, v.getValue().createdAt)));
 
         if (removed[0]) {
             cookieJar.entrySet().removeIf(entry -> entry.getValue() == null || entry.getValue().isEmpty());
diff --git a/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java b/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java
index cf09bc742..993f87d93 100644
--- a/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java
+++ b/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java b/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java
index b8ca2bd55..798312257 100644
--- a/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java
+++ b/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java b/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java
index 44045e80b..8247379b0 100755
--- a/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java
+++ b/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
 
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
 import org.asynchttpclient.HttpResponseBodyPart;
 
 import java.nio.ByteBuffer;
@@ -53,4 +54,9 @@ public int length() {
     public ByteBuffer getBodyByteBuffer() {
         return ByteBuffer.wrap(bytes);
     }
+
+    @Override
+    public ByteBuf getBodyByteBuf() {
+        return Unpooled.wrappedBuffer(bytes);
+    }
 }
diff --git a/client/src/main/java/org/asynchttpclient/netty/LazyResponseBodyPart.java b/client/src/main/java/org/asynchttpclient/netty/LazyResponseBodyPart.java
index 3732669d9..e47277006 100755
--- a/client/src/main/java/org/asynchttpclient/netty/LazyResponseBodyPart.java
+++ b/client/src/main/java/org/asynchttpclient/netty/LazyResponseBodyPart.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -33,7 +33,8 @@ public LazyResponseBodyPart(ByteBuf buf, boolean last) {
         this.buf = buf;
     }
 
-    public ByteBuf getBuf() {
+    @Override
+    public ByteBuf getBodyByteBuf() {
         return buf;
     }
 
diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java
index 179d796c1..61fb15161 100755
--- a/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java
+++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -15,6 +15,9 @@
  */
 package org.asynchttpclient.netty;
 
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.CompositeByteBuf;
 import io.netty.handler.codec.http.EmptyHttpHeaders;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.cookie.ClientCookieDecoder;
@@ -192,6 +195,15 @@ public ByteBuffer getResponseBodyAsByteBuffer() {
         return target;
     }
 
+    @Override
+    public ByteBuf getResponseBodyAsByteBuf() {
+        CompositeByteBuf compositeByteBuf = ByteBufAllocator.DEFAULT.compositeBuffer(bodyParts.size());
+        for (HttpResponseBodyPart part : bodyParts) {
+            compositeByteBuf.addComponent(true, part.getBodyByteBuf());
+        }
+        return compositeByteBuf;
+    }
+
     @Override
     public String getResponseBody() {
         return getResponseBody(withDefault(extractContentTypeCharsetAttribute(getContentType()), UTF_8));
diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java
index f66c9b9d7..c29c0f33d 100755
--- a/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java
+++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -187,10 +187,10 @@ public boolean cancel(boolean force) {
             return false;
         }
 
-        // cancel could happen before channel was attached
-        if (channel != null) {
-            Channels.setDiscard(channel);
-            Channels.silentlyCloseChannel(channel);
+        final Channel ch = channel; //atomic read, so that it won't end up in TOCTOU
+        if (ch != null) {
+            Channels.setDiscard(ch);
+            Channels.silentlyCloseChannel(ch);
         }
 
         if (ON_THROWABLE_CALLED_FIELD.getAndSet(this, 1) == 0) {
diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java
index 49af6b160..567432af3 100755
--- a/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java
+++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java b/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java
index e72a5c2be..15c0c9617 100644
--- a/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java
+++ b/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java
index fe85734c7..c5c94c551 100755
--- a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/Channels.java b/client/src/main/java/org/asynchttpclient/netty/channel/Channels.java
index e0d4acede..c56a05ba5 100755
--- a/client/src/main/java/org/asynchttpclient/netty/channel/Channels.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/Channels.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java
index 0ae2f68f7..c4042fdfc 100755
--- a/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java b/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java
index 86d48e451..719733f8a 100755
--- a/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java b/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java
index 919bd3cfa..28a0f359d 100755
--- a/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java
+++ b/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/AsyncHttpClientHandler.java b/client/src/main/java/org/asynchttpclient/netty/handler/AsyncHttpClientHandler.java
index 71d48f408..aeecbef55 100755
--- a/client/src/main/java/org/asynchttpclient/netty/handler/AsyncHttpClientHandler.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/AsyncHttpClientHandler.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java b/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java
index 5990cac42..99a23c7e9 100755
--- a/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 import io.netty.handler.codec.DecoderResultProvider;
 import io.netty.handler.codec.http.HttpContent;
 import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpMethod;
 import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.HttpResponse;
 import io.netty.handler.codec.http.LastHttpContent;
@@ -32,6 +33,7 @@
 import org.asynchttpclient.netty.NettyResponseStatus;
 import org.asynchttpclient.netty.channel.ChannelManager;
 import org.asynchttpclient.netty.request.NettyRequestSender;
+import org.asynchttpclient.util.HttpConstants.ResponseStatusCodes;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
@@ -43,8 +45,11 @@ public HttpHandler(AsyncHttpClientConfig config, ChannelManager channelManager,
         super(config, channelManager, requestSender);
     }
 
-    private static boolean abortAfterHandlingStatus(AsyncHandler<?> handler, NettyResponseStatus status) throws Exception {
-        return handler.onStatusReceived(status) == State.ABORT;
+    private static boolean abortAfterHandlingStatus(AsyncHandler<?> handler, HttpMethod httpMethod, NettyResponseStatus status) throws Exception {
+        // For non-200 response of a CONNECT request, it's still unconnected.
+        // We need to either close the connection or reuse it but send CONNECT request again.
+        // The former one is easier or we have to attach more state to Channel.
+        return handler.onStatusReceived(status) == State.ABORT || httpMethod == HttpMethod.CONNECT && status.getStatusCode() != ResponseStatusCodes.OK_200;
     }
 
     private static boolean abortAfterHandlingHeaders(AsyncHandler<?> handler, HttpHeaders responseHeaders) throws Exception {
@@ -61,7 +66,7 @@ private void handleHttpResponse(final HttpResponse response, final Channel chann
         HttpHeaders responseHeaders = response.headers();
 
         if (!interceptors.exitAfterIntercept(channel, future, handler, response, status, responseHeaders)) {
-            boolean abort = abortAfterHandlingStatus(handler, status) || abortAfterHandlingHeaders(handler, responseHeaders);
+            boolean abort = abortAfterHandlingStatus(handler, httpRequest.method(), status) || abortAfterHandlingHeaders(handler, responseHeaders);
             if (abort) {
                 finishUpdate(future, channel, true);
             }
diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java b/client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java
index 9d26c49b2..1cf19d0ef 100755
--- a/client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java
index 51e7c8a9b..40628a7e5 100644
--- a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java
@@ -35,7 +35,6 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
 import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION;
@@ -73,11 +72,13 @@ public class Redirect30xInterceptor {
     private final AsyncHttpClientConfig config;
     private final NettyRequestSender requestSender;
     private final MaxRedirectException maxRedirectException;
+    private final boolean stripAuthorizationOnRedirect;
 
     Redirect30xInterceptor(ChannelManager channelManager, AsyncHttpClientConfig config, NettyRequestSender requestSender) {
         this.channelManager = channelManager;
         this.config = config;
         this.requestSender = requestSender;
+        stripAuthorizationOnRedirect = config.isStripAuthorizationOnRedirect(); // New flag
         maxRedirectException = unknownStackTrace(new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()),
                 Redirect30xInterceptor.class, "exitAfterHandlingRedirect");
     }
@@ -127,7 +128,7 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
                     }
                 }
 
-                requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody));
+                requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody, stripAuthorizationOnRedirect));
 
                 // in case of a redirect from HTTP to HTTPS, future
                 // attributes might change
@@ -142,11 +143,8 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
                 CookieStore cookieStore = config.getCookieStore();
                 if (cookieStore != null) {
                     // Update request's cookies assuming that cookie store is already updated by Interceptors
-                    List<Cookie> cookies = cookieStore.get(newUri);
-                    if (!cookies.isEmpty()) {
-                        for (Cookie cookie : cookies) {
-                            requestBuilder.addOrReplaceCookie(cookie);
-                        }
+                    for (Cookie cookie : cookieStore.get(newUri)) {
+                        requestBuilder.addCookieIfUnset(cookie);
                     }
                 }
 
@@ -183,7 +181,7 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
         return false;
     }
 
-    private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody) {
+    private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody, boolean stripAuthorization) {
         HttpHeaders headers = request.getHeaders()
                 .remove(HOST)
                 .remove(CONTENT_LENGTH);
@@ -192,7 +190,7 @@ private static HttpHeaders propagatedHeaders(Request request, Realm realm, boole
             headers.remove(CONTENT_TYPE);
         }
 
-        if (realm != null && realm.getScheme() == AuthScheme.NTLM) {
+        if (stripAuthorization || (realm != null && realm.getScheme() == AuthScheme.NTLM)) {
             headers.remove(AUTHORIZATION)
                     .remove(PROXY_AUTHORIZATION);
         }
diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ResponseFiltersInterceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ResponseFiltersInterceptor.java
index 19b41f502..5f905d94f 100644
--- a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ResponseFiltersInterceptor.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ResponseFiltersInterceptor.java
@@ -20,8 +20,8 @@
 import org.asynchttpclient.AsyncHandler;
 import org.asynchttpclient.AsyncHttpClientConfig;
 import org.asynchttpclient.HttpResponseStatus;
-import org.asynchttpclient.filter.FilterContext;
 import org.asynchttpclient.exception.FilterException;
+import org.asynchttpclient.filter.FilterContext;
 import org.asynchttpclient.filter.ResponseFilter;
 import org.asynchttpclient.netty.NettyResponseFuture;
 import org.asynchttpclient.netty.request.NettyRequestSender;
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java
index 785ffad6a..71cc658a0 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java
index 07d1f76a2..67d9a67be 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import io.netty.handler.codec.compression.Brotli;
+import io.netty.handler.codec.compression.Zstd;
 import io.netty.handler.codec.http.DefaultFullHttpRequest;
 import io.netty.handler.codec.http.DefaultHttpRequest;
 import io.netty.handler.codec.http.HttpHeaderValues;
@@ -32,6 +33,7 @@
 import org.asynchttpclient.netty.request.body.NettyBody;
 import org.asynchttpclient.netty.request.body.NettyBodyBody;
 import org.asynchttpclient.netty.request.body.NettyByteArrayBody;
+import org.asynchttpclient.netty.request.body.NettyByteBufBody;
 import org.asynchttpclient.netty.request.body.NettyByteBufferBody;
 import org.asynchttpclient.netty.request.body.NettyCompositeByteArrayBody;
 import org.asynchttpclient.netty.request.body.NettyDirectBody;
@@ -66,6 +68,7 @@
 import static org.asynchttpclient.util.HttpUtils.ACCEPT_ALL_HEADER_VALUE;
 import static org.asynchttpclient.util.HttpUtils.GZIP_DEFLATE;
 import static org.asynchttpclient.util.HttpUtils.filterOutBrotliFromAcceptEncoding;
+import static org.asynchttpclient.util.HttpUtils.filterOutZstdFromAcceptEncoding;
 import static org.asynchttpclient.util.HttpUtils.hostHeader;
 import static org.asynchttpclient.util.HttpUtils.originHeader;
 import static org.asynchttpclient.util.HttpUtils.urlEncodeFormParams;
@@ -96,6 +99,8 @@ private NettyBody body(Request request) {
             nettyBody = new NettyByteBufferBody(StringUtils.charSequence2ByteBuffer(request.getStringData(), bodyCharset));
         } else if (request.getByteBufferData() != null) {
             nettyBody = new NettyByteBufferBody(request.getByteBufferData());
+        } else if (request.getByteBufData() != null) {
+            nettyBody = new NettyByteBufBody(request.getByteBufData());
         } else if (request.getStreamData() != null) {
             nettyBody = new NettyInputStreamBody(request.getStreamData());
         } else if (isNonEmpty(request.getFormParams())) {
@@ -179,6 +184,11 @@ public NettyRequest newNettyRequest(Request request, boolean performConnectReque
                         // For manual decompression by user, any encoding may suite, so leave untouched
                         headers.set(ACCEPT_ENCODING, filterOutBrotliFromAcceptEncoding(userDefinedAcceptEncoding));
                     }
+                    if (!Zstd.isAvailable()) {
+                        // zstd is not available.
+                        // For manual decompression by user, any encoding may suit, so leave untouched
+                        headers.set(ACCEPT_ENCODING, filterOutZstdFromAcceptEncoding(userDefinedAcceptEncoding));
+                    }
                 }
             } else if (config.isCompressionEnforced()) {
                 // Add Accept Encoding header if compression is enforced
@@ -186,6 +196,9 @@ public NettyRequest newNettyRequest(Request request, boolean performConnectReque
                 if (Brotli.isAvailable()) {
                     headers.add(ACCEPT_ENCODING, HttpHeaderValues.BR);
                 }
+                if (Zstd.isAvailable()) {
+                    headers.add(ACCEPT_ENCODING, HttpHeaderValues.ZSTD);
+                }
             }
         }
 
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java
index 2c0314325..b66dd713d 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -36,10 +36,10 @@
 import org.asynchttpclient.Realm;
 import org.asynchttpclient.Realm.AuthScheme;
 import org.asynchttpclient.Request;
+import org.asynchttpclient.exception.FilterException;
 import org.asynchttpclient.exception.PoolAlreadyClosedException;
 import org.asynchttpclient.exception.RemotelyClosedException;
 import org.asynchttpclient.filter.FilterContext;
-import org.asynchttpclient.exception.FilterException;
 import org.asynchttpclient.filter.IOExceptionFilter;
 import org.asynchttpclient.handler.TransferCompletionHandler;
 import org.asynchttpclient.netty.NettyResponseFuture;
@@ -97,6 +97,13 @@ public NettyRequestSender(AsyncHttpClientConfig config, ChannelManager channelMa
         requestFactory = new NettyRequestFactory(config);
     }
 
+    // needConnect returns true if the request is secure/websocket and a HTTP proxy is set
+    private boolean needConnect(final Request request, final ProxyServer proxyServer) {
+        return proxyServer != null
+                && proxyServer.getProxyType().isHttp()
+                && (request.getUri().isSecured() || request.getUri().isWebSocket());
+    }
+
     public <T> ListenableFuture<T> sendRequest(final Request request, final AsyncHandler<T> asyncHandler, NettyResponseFuture<T> future) {
         if (isClosed()) {
             throw new IllegalStateException("Closed");
@@ -106,9 +113,7 @@ public <T> ListenableFuture<T> sendRequest(final Request request, final AsyncHan
         ProxyServer proxyServer = getProxyServer(config, request);
 
         // WebSockets use connect tunneling to work with proxies
-        if (proxyServer != null && proxyServer.getProxyType().isHttp() &&
-                (request.getUri().isSecured() || request.getUri().isWebSocket()) &&
-                !isConnectAlreadyDone(request, future)) {
+        if (needConnect(request, proxyServer) && !isConnectAlreadyDone(request, future)) {
             // Proxy with HTTPS or WebSocket: CONNECT for sure
             if (future != null && future.isConnectAllowed()) {
                 // Perform CONNECT
@@ -125,6 +130,8 @@ public <T> ListenableFuture<T> sendRequest(final Request request, final AsyncHan
 
     private static boolean isConnectAlreadyDone(Request request, NettyResponseFuture<?> future) {
         return future != null
+                // If the channel can't be reused or closed, a CONNECT is still required
+                && future.isReuseChannel() && Channels.isChannelActive(future.channel())
                 && future.getNettyRequest() != null
                 && future.getNettyRequest().getHttpRequest().method() == HttpMethod.CONNECT
                 && !request.getMethod().equals(CONNECT);
@@ -137,11 +144,19 @@ private static boolean isConnectAlreadyDone(Request request, NettyResponseFuture
      */
     private <T> ListenableFuture<T> sendRequestWithCertainForceConnect(Request request, AsyncHandler<T> asyncHandler, NettyResponseFuture<T> future,
                                                                        ProxyServer proxyServer, boolean performConnectRequest) {
-        NettyResponseFuture<T> newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, proxyServer, performConnectRequest);
         Channel channel = getOpenChannel(future, request, proxyServer, asyncHandler);
-        return Channels.isChannelActive(channel)
-                ? sendRequestWithOpenChannel(newFuture, asyncHandler, channel)
-                : sendRequestWithNewChannel(request, proxyServer, newFuture, asyncHandler);
+        if (Channels.isChannelActive(channel)) {
+            NettyResponseFuture<T> newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future,
+                    proxyServer, performConnectRequest);
+            return sendRequestWithOpenChannel(newFuture, asyncHandler, channel);
+        } else {
+            // A new channel is not expected when performConnectRequest is false. We need to
+            // revisit the condition of sending
+            // the CONNECT request to the new channel.
+            NettyResponseFuture<T> newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future,
+                    proxyServer, needConnect(request, proxyServer));
+            return sendRequestWithNewChannel(request, proxyServer, newFuture, asyncHandler);
+        }
     }
 
     /**
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java b/client/src/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java
index 4a6c330d0..98f669eae 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java b/client/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java
index cd0434146..772baca43 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java b/client/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java
index 9326d717d..91f2b1ecf 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java
index 2dce64353..f38ef3939 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java
index 610069a01..efe337bfe 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java
index 5714d1396..b794ab6e9 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufBody.java
new file mode 100644
index 000000000..d236cdade
--- /dev/null
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufBody.java
@@ -0,0 +1,50 @@
+/*
+ *    Copyright (c) 2015-2023 AsyncHttpClient Project. All rights reserved.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+package org.asynchttpclient.netty.request.body;
+
+import io.netty.buffer.ByteBuf;
+
+public class NettyByteBufBody extends NettyDirectBody {
+
+    private final ByteBuf bb;
+    private final CharSequence contentTypeOverride;
+    private final long length;
+
+    public NettyByteBufBody(ByteBuf bb) {
+        this(bb, null);
+    }
+
+    public NettyByteBufBody(ByteBuf bb, CharSequence contentTypeOverride) {
+        this.bb = bb;
+        length = bb.readableBytes();
+        this.contentTypeOverride = contentTypeOverride;
+    }
+
+    @Override
+    public long getContentLength() {
+        return length;
+    }
+
+    @Override
+    public CharSequence getContentTypeOverride() {
+        return contentTypeOverride;
+    }
+
+    @Override
+    public ByteBuf byteBuf() {
+        return bb;
+    }
+}
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java
index 74bd09c4f..e852528fd 100644
--- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java
index 7f8dc69f8..55dfcb8bc 100644
--- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java
index e83b7b8cd..a3c40322d 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java
index 0096cc772..4dba9d951 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java
index fe889e383..7fa23c07f 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java b/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java
index 213b539a9..8b0d4373a 100755
--- a/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java
+++ b/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java b/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java
index 5a72a6b70..74c5d0197 100755
--- a/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java
+++ b/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java
index 67b31c19b..3c9a3675e 100755
--- a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java
+++ b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java
index 6c2a55ff2..acce84b6d 100755
--- a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java
+++ b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java b/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java
index 35847dc78..2329edacf 100755
--- a/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java
+++ b/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java
index ce7624d2b..c2338c46a 100644
--- a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java
+++ b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java
@@ -1083,7 +1083,8 @@ byte[] getChallenge() {
         /**
          * Retrieve the target
          */
-        @Nullable String getTarget() {
+        @Nullable
+        String getTarget() {
             return target;
         }
 
diff --git a/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java b/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java
index 46391c804..048f2e78e 100644
--- a/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java
+++ b/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java
@@ -35,5 +35,6 @@ public interface ProxyServerSelector {
      * @param uri The URI to select a proxy server for.
      * @return The proxy server to use, if any.  May return null.
      */
-    @Nullable ProxyServer select(Uri uri);
+    @Nullable
+    ProxyServer select(Uri uri);
 }
diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java
index dae8acef8..1aa27f0ac 100644
--- a/client/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java
+++ b/client/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/UnboundedQueueFeedableBodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/UnboundedQueueFeedableBodyGenerator.java
index bc72650a7..f55dcbe37 100755
--- a/client/src/main/java/org/asynchttpclient/request/body/generator/UnboundedQueueFeedableBodyGenerator.java
+++ b/client/src/main/java/org/asynchttpclient/request/body/generator/UnboundedQueueFeedableBodyGenerator.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java
index 97ed6cc61..203d37a2c 100644
--- a/client/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java
+++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java
index 3370eb761..1d03ad22b 100644
--- a/client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java
+++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java
index 777489fd4..a156dd077 100644
--- a/client/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java
+++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/Part.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/Part.java
index 9dae23f6a..19266eb25 100644
--- a/client/src/main/java/org/asynchttpclient/request/body/multipart/Part.java
+++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/Part.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java
index 2dff615e4..65e78286f 100644
--- a/client/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java
+++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java
index 2427913ea..05c958d35 100644
--- a/client/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java
+++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/PartVisitor.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/PartVisitor.java
index 4bddbd7e1..8e9029c4f 100644
--- a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/PartVisitor.java
+++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/PartVisitor.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/uri/Uri.java b/client/src/main/java/org/asynchttpclient/uri/Uri.java
index 1c76ff2ce..e1d53d1ca 100644
--- a/client/src/main/java/org/asynchttpclient/uri/Uri.java
+++ b/client/src/main/java/org/asynchttpclient/uri/Uri.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/uri/UriParser.java b/client/src/main/java/org/asynchttpclient/uri/UriParser.java
index 40c195630..c65f145dd 100644
--- a/client/src/main/java/org/asynchttpclient/uri/UriParser.java
+++ b/client/src/main/java/org/asynchttpclient/uri/UriParser.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/util/HttpUtils.java b/client/src/main/java/org/asynchttpclient/util/HttpUtils.java
index f62e2f235..3cca41e61 100644
--- a/client/src/main/java/org/asynchttpclient/util/HttpUtils.java
+++ b/client/src/main/java/org/asynchttpclient/util/HttpUtils.java
@@ -40,6 +40,7 @@ public final class HttpUtils {
     private static final String CONTENT_TYPE_CHARSET_ATTRIBUTE = "charset=";
     private static final String CONTENT_TYPE_BOUNDARY_ATTRIBUTE = "boundary=";
     private static final String BROTLY_ACCEPT_ENCODING_SUFFIX = ", br";
+    private static final String ZSTD_ACCEPT_ENCODING_SUFFIX = ", zstd";
 
     private HttpUtils() {
         // Prevent outside initialization
@@ -173,4 +174,12 @@ public static CharSequence filterOutBrotliFromAcceptEncoding(String acceptEncodi
         }
         return acceptEncoding;
     }
+
+    public static CharSequence filterOutZstdFromAcceptEncoding(String acceptEncoding) {
+        // we don't support zstd ATM
+        if (acceptEncoding.endsWith(ZSTD_ACCEPT_ENCODING_SUFFIX)) {
+            return acceptEncoding.subSequence(0, acceptEncoding.length() - ZSTD_ACCEPT_ENCODING_SUFFIX.length());
+        }
+        return acceptEncoding;
+    }
 }
diff --git a/client/src/main/java/org/asynchttpclient/util/StringUtils.java b/client/src/main/java/org/asynchttpclient/util/StringUtils.java
index bccdd8dd2..0abf0b686 100644
--- a/client/src/main/java/org/asynchttpclient/util/StringUtils.java
+++ b/client/src/main/java/org/asynchttpclient/util/StringUtils.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/util/UriEncoder.java b/client/src/main/java/org/asynchttpclient/util/UriEncoder.java
index 126edee3d..92706d292 100644
--- a/client/src/main/java/org/asynchttpclient/util/UriEncoder.java
+++ b/client/src/main/java/org/asynchttpclient/util/UriEncoder.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java b/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java
index 8e020ddac..628cc1d7d 100644
--- a/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java
+++ b/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties b/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties
index 6450221b0..f74127c23 100644
--- a/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties
+++ b/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties
@@ -13,7 +13,7 @@ org.asynchttpclient.maxRedirects=5
 org.asynchttpclient.compressionEnforced=false
 org.asynchttpclient.enableAutomaticDecompression=true
 org.asynchttpclient.userAgent=AHC/2.1
-org.asynchttpclient.enabledProtocols=TLSv1.2, TLSv1.1, TLSv1
+org.asynchttpclient.enabledProtocols=TLSv1.3, TLSv1.2
 org.asynchttpclient.enabledCipherSuites=
 org.asynchttpclient.filterInsecureCipherSuites=true
 org.asynchttpclient.useProxySelector=false
diff --git a/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java b/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java
index 993f87905..2dcfa859d 100644
--- a/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java
+++ b/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java
@@ -21,9 +21,7 @@
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.slf4j.Logger;
diff --git a/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java b/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java
index 954ae2eac..90a515fca 100644
--- a/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java
+++ b/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java
@@ -441,7 +441,7 @@ public void asyncOptionsTest() throws Throwable {
                     // Some responses contain the TRACE method, some do not - account for both
                     final String[] expected = {"GET", "HEAD", "OPTIONS", "POST"};
                     final String[] expectedWithTrace = {"GET", "HEAD", "OPTIONS", "POST", "TRACE"};
-                    Future<String> f = client.prepareOptions("https://www.shieldblaze.com/").execute(new AsyncHandlerAdapter() {
+                    Future<String> f = client.prepareOptions("https://www.google.com/").execute(new AsyncHandlerAdapter() {
 
                         @Override
                         public State onHeadersReceived(HttpHeaders headers) {
diff --git a/client/src/test/java/org/asynchttpclient/AutomaticDecompressionTest.java b/client/src/test/java/org/asynchttpclient/AutomaticDecompressionTest.java
new file mode 100644
index 000000000..8f57ffb88
--- /dev/null
+++ b/client/src/test/java/org/asynchttpclient/AutomaticDecompressionTest.java
@@ -0,0 +1,166 @@
+/*
+ *    Copyright (c) 2015-2024 AsyncHttpClient Project. All rights reserved.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+package org.asynchttpclient;
+
+import com.aayushatharva.brotli4j.encoder.BrotliOutputStream;
+import com.aayushatharva.brotli4j.encoder.Encoder;
+import com.github.luben.zstd.Zstd;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import io.github.nettyplus.leakdetector.junit.NettyLeakDetectorExtension;
+import io.netty.handler.codec.compression.Brotli;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.zip.GZIPOutputStream;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@ExtendWith(NettyLeakDetectorExtension.class)
+public class AutomaticDecompressionTest {
+    private static final String UNCOMPRESSED_PAYLOAD = "a".repeat(50_000);
+
+    private static HttpServer HTTP_SERVER;
+
+    private static AsyncHttpClient createClient() {
+        AsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
+                .setEnableAutomaticDecompression(true)
+                .setCompressionEnforced(true)
+                .build();
+        return new DefaultAsyncHttpClient(config);
+    }
+
+    @BeforeAll
+    static void setupServer() throws Exception {
+        HTTP_SERVER = HttpServer.create(new InetSocketAddress(0), 0);
+
+        HTTP_SERVER.createContext("/br").setHandler(new HttpHandler() {
+            @Override
+            public void handle(HttpExchange exchange)
+                    throws IOException {
+                validateAcceptEncodingHeader(exchange);
+                exchange.getResponseHeaders().set("Content-Encoding", "br");
+                exchange.sendResponseHeaders(200, 0);
+                OutputStream out = exchange.getResponseBody();
+                Encoder.Parameters params = new Encoder.Parameters();
+                BrotliOutputStream brotliOutputStream = new BrotliOutputStream(out, params);
+                brotliOutputStream.write(UNCOMPRESSED_PAYLOAD.getBytes(StandardCharsets.UTF_8));
+                brotliOutputStream.flush();
+                brotliOutputStream.close();
+            }
+        });
+
+        HTTP_SERVER.createContext("/zstd").setHandler(new HttpHandler() {
+            @Override
+            public void handle(HttpExchange exchange)
+                    throws IOException {
+                validateAcceptEncodingHeader(exchange);
+                exchange.getResponseHeaders().set("Content-Encoding", "zstd");
+                byte[] compressedData = new byte[UNCOMPRESSED_PAYLOAD.length()];
+                long n = Zstd.compress(compressedData, UNCOMPRESSED_PAYLOAD.getBytes(StandardCharsets.UTF_8), 2, true);
+                exchange.sendResponseHeaders(200, n);
+                OutputStream out = exchange.getResponseBody();
+                out.write(compressedData, 0, (int) n);
+                out.flush();
+                out.close();
+            }
+        });
+
+        HTTP_SERVER.createContext("/gzip").setHandler(new HttpHandler() {
+            @Override
+            public void handle(HttpExchange exchange)
+                    throws IOException {
+                validateAcceptEncodingHeader(exchange);
+                exchange.getResponseHeaders().set("Content-Encoding", "gzip");
+                exchange.sendResponseHeaders(200, 0);
+                OutputStream out = exchange.getResponseBody();
+                GZIPOutputStream gzip = new GZIPOutputStream(out);
+                gzip.write(UNCOMPRESSED_PAYLOAD.getBytes(StandardCharsets.UTF_8));
+                gzip.flush();
+                gzip.close();
+            }
+        });
+
+        HTTP_SERVER.start();
+    }
+
+    private static void validateAcceptEncodingHeader(HttpExchange exchange) {
+        Headers requestHeaders = exchange.getRequestHeaders();
+        List<String> acceptEncodingList = requestHeaders.get("Accept-Encoding")
+                .stream()
+                .flatMap(x -> Arrays.asList(x.split(",")).stream())
+                .collect(Collectors.toList());
+        assertEquals(List.of("gzip", "deflate", "br", "zstd"), acceptEncodingList);
+    }
+
+    @AfterAll
+    static void stopServer() {
+        if (HTTP_SERVER != null) {
+            HTTP_SERVER.stop(0);
+        }
+    }
+
+    @Test
+    void zstd() throws Throwable {
+        io.netty.handler.codec.compression.Zstd.ensureAvailability();
+        try (AsyncHttpClient client = createClient()) {
+            Request request = new RequestBuilder("GET")
+                    .setUrl("http://localhost:" + HTTP_SERVER.getAddress().getPort() + "/zstd")
+                    .build();
+            Response response = client.executeRequest(request).get();
+            assertEquals(200, response.getStatusCode());
+            assertEquals(UNCOMPRESSED_PAYLOAD, response.getResponseBody());
+        }
+    }
+
+    @Test
+    void brotli() throws Throwable {
+        Brotli.ensureAvailability();
+        try (AsyncHttpClient client = createClient()) {
+            Request request = new RequestBuilder("GET")
+                    .setUrl("http://localhost:" + HTTP_SERVER.getAddress().getPort() + "/br")
+                    .build();
+            Response response = client.executeRequest(request).get();
+            assertEquals(200, response.getStatusCode());
+            assertEquals(UNCOMPRESSED_PAYLOAD, response.getResponseBody());
+        }
+    }
+
+    @Test
+    void gzip() throws Throwable {
+        try (AsyncHttpClient client = createClient()) {
+            Request request = new RequestBuilder("GET")
+                    .setUrl("http://localhost:" + HTTP_SERVER.getAddress().getPort() + "/gzip")
+                    .build();
+            Response response = client.executeRequest(request).get();
+            assertEquals(200, response.getStatusCode());
+            assertEquals(UNCOMPRESSED_PAYLOAD, response.getResponseBody());
+        }
+    }
+
+
+}
diff --git a/client/src/test/java/org/asynchttpclient/BasicHttpTest.java b/client/src/test/java/org/asynchttpclient/BasicHttpTest.java
index cc75c03c1..f83cac80f 100755
--- a/client/src/test/java/org/asynchttpclient/BasicHttpTest.java
+++ b/client/src/test/java/org/asynchttpclient/BasicHttpTest.java
@@ -79,6 +79,7 @@
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -559,7 +560,7 @@ public void connectFailureNotifiesHandlerWithConnectException() throws Throwable
                         @Override
                         public void onThrowable(Throwable t) {
                             try {
-                                assertTrue(t instanceof ConnectException);
+                                assertInstanceOf(ConnectException.class, t);
                             } finally {
                                 l.countDown();
                             }
@@ -962,8 +963,8 @@ public void requestingPlainHttpEndpointOverHttpsThrowsSslException() throws Thro
                         client.prepareGet(getTargetUrl().replace("http", "https")).execute().get();
                         fail("Request shouldn't succeed");
                     } catch (ExecutionException e) {
-                        assertTrue(e.getCause() instanceof ConnectException, "Cause should be a ConnectException");
-                        assertTrue(e.getCause().getCause() instanceof SSLException, "Root cause should be a SslException");
+                        assertInstanceOf(ConnectException.class, e.getCause(), "Cause should be a ConnectException");
+                        assertInstanceOf(SSLException.class, e.getCause().getCause(), "Root cause should be a SslException");
                     }
                 }));
     }
diff --git a/client/src/test/java/org/asynchttpclient/ClientStatsTest.java b/client/src/test/java/org/asynchttpclient/ClientStatsTest.java
index bda1aa3a2..2d8d324de 100644
--- a/client/src/test/java/org/asynchttpclient/ClientStatsTest.java
+++ b/client/src/test/java/org/asynchttpclient/ClientStatsTest.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientConfigTest.java b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientConfigTest.java
new file mode 100644
index 000000000..1548d6812
--- /dev/null
+++ b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientConfigTest.java
@@ -0,0 +1,30 @@
+package org.asynchttpclient;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DefaultAsyncHttpClientConfigTest {
+    @Test
+    void testStripAuthorizationOnRedirect_DefaultIsFalse() {
+        DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().build();
+        assertFalse(config.isStripAuthorizationOnRedirect(), "Default should be false");
+    }
+
+    @Test
+    void testStripAuthorizationOnRedirect_SetTrue() {
+        DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
+                .setStripAuthorizationOnRedirect(true)
+                .build();
+        assertTrue(config.isStripAuthorizationOnRedirect(), "Should be true when set");
+    }
+
+    @Test
+    void testStripAuthorizationOnRedirect_SetFalse() {
+        DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
+                .setStripAuthorizationOnRedirect(false)
+                .build();
+        assertFalse(config.isStripAuthorizationOnRedirect(), "Should be false when set to false");
+    }
+}
diff --git a/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientTest.java b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientTest.java
index 0bfd6842c..fc7a1c2db 100644
--- a/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientTest.java
+++ b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientTest.java
@@ -50,7 +50,7 @@ public void testNativeTransportWithEpollOnly() throws Exception {
         AsyncHttpClientConfig config = config().setUseNativeTransport(true).setUseOnlyEpollNativeTransport(true).build();
 
         try (DefaultAsyncHttpClient client = (DefaultAsyncHttpClient) asyncHttpClient(config)) {
-            assertDoesNotThrow(() -> client.prepareGet("https://www.shieldblaze.com").execute().get());
+            assertDoesNotThrow(() -> client.prepareGet("https://www.google.com").execute().get());
             assertInstanceOf(EpollEventLoopGroup.class, client.channelManager().getEventLoopGroup());
         }
     }
@@ -60,7 +60,7 @@ public void testNativeTransportWithEpollOnly() throws Exception {
     public void testNativeTransportWithoutEpollOnly() throws Exception {
         AsyncHttpClientConfig config = config().setUseNativeTransport(true).setUseOnlyEpollNativeTransport(false).build();
         try (DefaultAsyncHttpClient client = (DefaultAsyncHttpClient) asyncHttpClient(config)) {
-            assertDoesNotThrow(() -> client.prepareGet("https://www.shieldblaze.com").execute().get());
+            assertDoesNotThrow(() -> client.prepareGet("https://www.google.com").execute().get());
             assertInstanceOf(IOUringEventLoopGroup.class, client.channelManager().getEventLoopGroup());
         }
     }
@@ -70,7 +70,7 @@ public void testNativeTransportWithoutEpollOnly() throws Exception {
     public void testNativeTransportKQueueOnMacOs() throws Exception {
         AsyncHttpClientConfig config = config().setUseNativeTransport(true).build();
         try (DefaultAsyncHttpClient client = (DefaultAsyncHttpClient) asyncHttpClient(config)) {
-            assertDoesNotThrow(() -> client.prepareGet("https://www.shieldblaze.com").execute().get());
+            assertDoesNotThrow(() -> client.prepareGet("https://www.google.com").execute().get());
             assertInstanceOf(KQueueEventLoopGroup.class, client.channelManager().getEventLoopGroup());
         }
     }
diff --git a/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java b/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java
index 4c119779c..ae3eccf85 100644
--- a/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java
+++ b/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java
@@ -38,6 +38,7 @@
 import static org.asynchttpclient.test.TestUtils.addHttpConnector;
 import static org.asynchttpclient.test.TestUtils.findFreePort;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -125,7 +126,7 @@ public void redirected302InvalidTest() throws Exception {
 
         assertNotNull(e);
         Throwable cause = e.getCause();
-        assertTrue(cause instanceof ConnectException);
+        assertInstanceOf(ConnectException.class, cause);
         assertTrue(cause.getMessage().contains(":" + port2));
     }
 
diff --git a/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java b/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java
index b04f16533..bee7d0b67 100644
--- a/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java
+++ b/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java
@@ -34,6 +34,7 @@
 import static org.asynchttpclient.Dsl.config;
 import static org.asynchttpclient.util.DateUtils.unpreciseMillisTime;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -73,7 +74,7 @@ public void testRequestTimeout() throws IOException {
         } catch (InterruptedException e) {
             fail("Interrupted.", e);
         } catch (ExecutionException e) {
-            assertTrue(e.getCause() instanceof TimeoutException);
+            assertInstanceOf(TimeoutException.class, e.getCause());
             checkTimeoutMessage(e.getCause().getMessage(), true);
         } catch (TimeoutException e) {
             fail("Timeout.", e);
@@ -89,7 +90,7 @@ public void testReadTimeout() throws IOException {
         } catch (InterruptedException e) {
             fail("Interrupted.", e);
         } catch (ExecutionException e) {
-            assertTrue(e.getCause() instanceof TimeoutException);
+            assertInstanceOf(TimeoutException.class, e.getCause());
             checkTimeoutMessage(e.getCause().getMessage(), false);
         } catch (TimeoutException e) {
             fail("Timeout.", e);
@@ -107,7 +108,7 @@ public void testGlobalDefaultPerRequestInfiniteTimeout() throws IOException {
         } catch (InterruptedException e) {
             fail("Interrupted.", e);
         } catch (ExecutionException e) {
-            assertTrue(e.getCause() instanceof TimeoutException);
+            assertInstanceOf(TimeoutException.class, e.getCause());
             checkTimeoutMessage(e.getCause().getMessage(), true);
         }
     }
@@ -121,7 +122,7 @@ public void testGlobalRequestTimeout() throws IOException {
         } catch (InterruptedException e) {
             fail("Interrupted.", e);
         } catch (ExecutionException e) {
-            assertTrue(e.getCause() instanceof TimeoutException);
+            assertInstanceOf(TimeoutException.class, e.getCause());
             checkTimeoutMessage(e.getCause().getMessage(), true);
         } catch (TimeoutException e) {
             fail("Timeout.", e);
diff --git a/client/src/test/java/org/asynchttpclient/Relative302Test.java b/client/src/test/java/org/asynchttpclient/Relative302Test.java
index b4d254bfb..074930791 100644
--- a/client/src/test/java/org/asynchttpclient/Relative302Test.java
+++ b/client/src/test/java/org/asynchttpclient/Relative302Test.java
@@ -39,6 +39,7 @@
 import static org.asynchttpclient.test.TestUtils.addHttpConnector;
 import static org.asynchttpclient.test.TestUtils.findFreePort;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -101,7 +102,7 @@ public void redirected302InvalidTest() throws Exception {
 
         assertNotNull(e);
         Throwable cause = e.getCause();
-        assertTrue(cause instanceof ConnectException);
+        assertInstanceOf(ConnectException.class, cause);
         assertTrue(cause.getMessage().contains(":" + port2));
     }
 
diff --git a/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java b/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java
index 024fce5f1..34e79121d 100644
--- a/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java
+++ b/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java
@@ -166,6 +166,40 @@ public void testAddOrReplaceCookies() {
         assertEquals(requestBuilder.cookies.size(), 2, "cookie size must be 2 after adding 1 more cookie i.e. cookie3");
     }
 
+    @RepeatedIfExceptionsTest(repeats = 5)
+    public void testAddIfUnsetCookies() {
+        RequestBuilder requestBuilder = new RequestBuilder();
+        Cookie cookie = new DefaultCookie("name", "value");
+        cookie.setDomain("google.com");
+        cookie.setPath("/");
+        cookie.setMaxAge(1000);
+        cookie.setSecure(true);
+        cookie.setHttpOnly(true);
+        requestBuilder.addCookieIfUnset(cookie);
+        assertEquals(requestBuilder.cookies.size(), 1, "cookies size should be 1 after adding one cookie");
+        assertEquals(requestBuilder.cookies.get(0), cookie, "cookie does not match");
+
+        Cookie cookie2 = new DefaultCookie("name", "value");
+        cookie2.setDomain("google2.com");
+        cookie2.setPath("/path");
+        cookie2.setMaxAge(1001);
+        cookie2.setSecure(false);
+        cookie2.setHttpOnly(false);
+
+        requestBuilder.addCookieIfUnset(cookie2);
+        assertEquals(requestBuilder.cookies.size(), 1, "cookies size should remain 1 as we just ignored cookie2 because of a cookie with same name");
+        assertEquals(requestBuilder.cookies.get(0), cookie, "cookie does not match");
+
+        Cookie cookie3 = new DefaultCookie("name2", "value");
+        cookie3.setDomain("google.com");
+        cookie3.setPath("/");
+        cookie3.setMaxAge(1000);
+        cookie3.setSecure(true);
+        cookie3.setHttpOnly(true);
+        requestBuilder.addCookieIfUnset(cookie3);
+        assertEquals(requestBuilder.cookies.size(), 2, "cookie size must be 2 after adding 1 more cookie i.e. cookie3");
+    }
+
     @RepeatedIfExceptionsTest(repeats = 5)
     public void testSettingQueryParamsBeforeUrlShouldNotProduceNPE() {
         RequestBuilder requestBuilder = new RequestBuilder();
diff --git a/client/src/test/java/org/asynchttpclient/StripAuthorizationOnRedirectHttpTest.java b/client/src/test/java/org/asynchttpclient/StripAuthorizationOnRedirectHttpTest.java
new file mode 100644
index 000000000..08c150c08
--- /dev/null
+++ b/client/src/test/java/org/asynchttpclient/StripAuthorizationOnRedirectHttpTest.java
@@ -0,0 +1,95 @@
+package org.asynchttpclient;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class StripAuthorizationOnRedirectHttpTest {
+    private static HttpServer server;
+    private static int port;
+    private static volatile String lastAuthHeader;
+
+    @BeforeAll
+    public static void startServer() throws Exception {
+        server = HttpServer.create(new InetSocketAddress(0), 0);
+        port = server.getAddress().getPort();
+        server.createContext("/redirect", new RedirectHandler());
+        server.createContext("/final", new FinalHandler());
+        server.start();
+    }
+
+    @AfterAll
+    public static void stopServer() {
+        server.stop(0);
+    }
+
+    static class RedirectHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange exchange) {
+            String auth = exchange.getRequestHeaders().getFirst("Authorization");
+            lastAuthHeader = auth;
+            exchange.getResponseHeaders().add("Location", "http://localhost:" + port + "/final");
+            try {
+                exchange.sendResponseHeaders(302, -1);
+            } catch (Exception ignored) {
+            }
+            exchange.close();
+        }
+    }
+
+    static class FinalHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange exchange) {
+            String auth = exchange.getRequestHeaders().getFirst("Authorization");
+            lastAuthHeader = auth;
+            try {
+                exchange.sendResponseHeaders(200, 0);
+                exchange.getResponseBody().close();
+            } catch (Exception ignored) {
+            }
+            exchange.close();
+        }
+    }
+
+    @Test
+    void testAuthHeaderPropagatedByDefault() throws Exception {
+        DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
+                .setFollowRedirect(true)
+                .build();
+        try (DefaultAsyncHttpClient client = new DefaultAsyncHttpClient(config)) {
+            lastAuthHeader = null;
+            client.prepareGet("http://localhost:" + port + "/redirect")
+                    .setHeader("Authorization", "Bearer testtoken")
+                    .execute()
+                    .get(5, TimeUnit.SECONDS);
+            // By default, Authorization header is propagated to /final
+            assertEquals("Bearer testtoken", lastAuthHeader, "Authorization header should be present on redirect by default");
+        }
+    }
+
+    @Test
+    void testAuthHeaderStrippedWhenEnabled() throws Exception {
+        DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
+                .setFollowRedirect(true)
+                .setStripAuthorizationOnRedirect(true)
+                .build();
+        try (DefaultAsyncHttpClient client = new DefaultAsyncHttpClient(config)) {
+            lastAuthHeader = null;
+            client.prepareGet("http://localhost:" + port + "/redirect")
+                    .setHeader("Authorization", "Bearer testtoken")
+                    .execute()
+                    .get(5, TimeUnit.SECONDS);
+            // When enabled, Authorization header should be stripped on /final
+            assertNull(lastAuthHeader, "Authorization header should be stripped on redirect when enabled");
+        }
+    }
+}
diff --git a/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java b/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java
index 30eded248..5172bae7a 100644
--- a/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java
+++ b/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java
@@ -13,12 +13,17 @@
 package org.asynchttpclient.netty;
 
 import io.github.artsok.RepeatedIfExceptionsTest;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
 import io.netty.handler.codec.http.DefaultHttpHeaders;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.cookie.Cookie;
+import org.asynchttpclient.HttpResponseBodyPart;
 
+import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -73,4 +78,16 @@ public void testCookieParseWeirdExpiresValue() {
         Cookie cookie = cookies.get(0);
         assertEquals(Long.MIN_VALUE, cookie.maxAge());
     }
+
+    @RepeatedIfExceptionsTest(repeats = 5)
+    public void testGetResponseBodyAsByteBuffer() {
+        List<HttpResponseBodyPart> bodyParts = new LinkedList<>();
+        bodyParts.add(new LazyResponseBodyPart(Unpooled.wrappedBuffer("Hello ".getBytes()), false));
+        bodyParts.add(new LazyResponseBodyPart(Unpooled.wrappedBuffer("World".getBytes()), true));
+        NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), null, bodyParts);
+
+        ByteBuf body = response.getResponseBodyAsByteBuf();
+        assertEquals("Hello World", body.toString(StandardCharsets.UTF_8));
+        body.release();
+    }
 }
diff --git a/client/src/test/java/org/asynchttpclient/netty/NettyTest.java b/client/src/test/java/org/asynchttpclient/netty/NettyTest.java
index 9a0293be3..f80c0911e 100644
--- a/client/src/test/java/org/asynchttpclient/netty/NettyTest.java
+++ b/client/src/test/java/org/asynchttpclient/netty/NettyTest.java
@@ -3,6 +3,7 @@
 import io.netty.channel.epoll.Epoll;
 import io.netty.channel.kqueue.KQueue;
 import io.netty.handler.codec.compression.Brotli;
+import io.netty.handler.codec.compression.Zstd;
 import io.netty.incubator.channel.uring.IOUring;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledOnOs;
@@ -12,32 +13,44 @@
 
 public class NettyTest {
     @Test
-    @EnabledOnOs(value = OS.LINUX)
+    @EnabledOnOs(OS.LINUX)
     public void epollIsAvailableOnLinux() {
         assertTrue(Epoll.isAvailable());
     }
 
     @Test
-    @EnabledOnOs(value = OS.LINUX)
+    @EnabledOnOs(OS.LINUX)
     public void ioUringIsAvailableOnLinux() {
         assertTrue(IOUring.isAvailable());
     }
 
     @Test
-    @EnabledOnOs(value = OS.MAC)
+    @EnabledOnOs(OS.MAC)
     public void kqueueIsAvailableOnMac() {
         assertTrue(KQueue.isAvailable());
     }
 
     @Test
-    @EnabledOnOs(value = OS.LINUX)
+    @EnabledOnOs(OS.LINUX)
     public void brotliIsAvailableOnLinux() {
         assertTrue(Brotli.isAvailable());
     }
 
     @Test
-    @EnabledOnOs(value = OS.MAC)
+    @EnabledOnOs(OS.MAC)
     public void brotliIsAvailableOnMac() {
         assertTrue(Brotli.isAvailable());
     }
+
+    @Test
+    @EnabledOnOs(OS.LINUX)
+    public void zstdIsAvailableOnLinux() {
+        assertTrue(Zstd.isAvailable());
+    }
+
+    @Test
+    @EnabledOnOs(OS.MAC)
+    public void zstdIsAvailableOnMac() {
+        assertTrue(Zstd.isAvailable());
+    }
 }
diff --git a/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java b/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java
index 93506925e..0bce17d4c 100644
--- a/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java
+++ b/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -100,7 +100,7 @@ public void testGenerateType1Msg() {
     @RepeatedIfExceptionsTest(repeats = 5)
     public void testGenerateType3MsgThrowsExceptionWhenChallengeTooShort() {
         NtlmEngine engine = new NtlmEngine();
-        assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation",
+        assertThrows(NtlmEngineException.class, () -> NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation",
                         Base64.getEncoder().encodeToString("a".getBytes())),
                 "An NtlmEngineException must have occurred as challenge length is too short");
     }
@@ -108,7 +108,7 @@ public void testGenerateType3MsgThrowsExceptionWhenChallengeTooShort() {
     @RepeatedIfExceptionsTest(repeats = 5)
     public void testGenerateType3MsgThrowsExceptionWhenChallengeDoesNotFollowCorrectFormat() {
         NtlmEngine engine = new NtlmEngine();
-        assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation",
+        assertThrows(NtlmEngineException.class, () -> NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation",
                         Base64.getEncoder().encodeToString("challenge".getBytes())),
                 "An NtlmEngineException must have occurred as challenge length is too short");
     }
@@ -125,7 +125,7 @@ public void testGenerateType3MsgThworsExceptionWhenType2IndicatorNotPresent() th
             buf.write(0);
             buf.write("challenge".getBytes());
             NtlmEngine engine = new NtlmEngine();
-            assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation",
+            assertThrows(NtlmEngineException.class, () -> NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation",
                     Base64.getEncoder().encodeToString(buf.toByteArray())), "An NtlmEngineException must have occurred as type 2 indicator is incorrect");
         }
     }
@@ -151,7 +151,7 @@ public void testGenerateType3MsgThrowsExceptionWhenUnicodeSupportNotIndicated()
 
             buf.write(longToBytes(1L));// challenge
             NtlmEngine engine = new NtlmEngine();
-            assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation",
+            assertThrows(NtlmEngineException.class, () -> NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation",
                             Base64.getEncoder().encodeToString(buf.toByteArray())),
                     "An NtlmEngineException must have occurred as unicode support is not indicated");
         }
@@ -184,7 +184,7 @@ public void testGenerateType3Msg() throws IOException {
 
             buf.write(longToBytes(1L));// challenge
             NtlmEngine engine = new NtlmEngine();
-            String type3Msg = engine.generateType3Msg("username", "password", "localhost", "workstation",
+            String type3Msg = NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation",
                     Base64.getEncoder().encodeToString(buf.toByteArray()));
             assertEquals(type3Msg,
                     "TlRMTVNTUAADAAAAGAAYAEgAAAAYABgAYAAAABIAEgB4AAAAEAAQAIoAAAAWABYAmgAAAAAAAACwAAAAAQAAAgUBKAoAAAAP1g6lqqN1HZ0wSSxeQ5riQkyh7/UexwVlCPQm0SHU2vsDQm2wM6NbT2zPonPzLJL0TABPAEMAQQBMAEgATwBTAFQAdQBzAGUAcgBuAGEAbQBlAFcATwBSAEsAUwBUAEEAVABJAE8ATgA=",
diff --git a/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java b/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java
index 6c4109aec..9bd5ca911 100644
--- a/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java
+++ b/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java
@@ -13,14 +13,22 @@
 package org.asynchttpclient.proxy;
 
 import io.github.artsok.RepeatedIfExceptionsTest;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
 import org.asynchttpclient.AbstractBasicTest;
 import org.asynchttpclient.AsyncHttpClient;
 import org.asynchttpclient.AsyncHttpClientConfig;
 import org.asynchttpclient.RequestBuilder;
 import org.asynchttpclient.Response;
+import org.asynchttpclient.proxy.ProxyServer.Builder;
 import org.asynchttpclient.request.body.generator.ByteArrayBodyGenerator;
 import org.asynchttpclient.test.EchoHandler;
+import org.asynchttpclient.util.HttpConstants;
 import org.eclipse.jetty.proxy.ConnectHandler;
+import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.AbstractHandler;
@@ -36,6 +44,10 @@
 import static org.asynchttpclient.test.TestUtils.addHttpConnector;
 import static org.asynchttpclient.test.TestUtils.addHttpsConnector;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
 
 /**
  * Proxy usage tests.
@@ -46,7 +58,7 @@ public class HttpsProxyTest extends AbstractBasicTest {
 
     @Override
     public AbstractHandler configureHandler() throws Exception {
-        return new ConnectHandler();
+        return new ProxyHandler();
     }
 
     @Override
@@ -142,4 +154,61 @@ public void testPooledConnectionsWithProxy() throws Exception {
             assertEquals(200, response2.getStatusCode());
         }
     }
+    
+    @RepeatedIfExceptionsTest(repeats = 5)
+    public void testFailedConnectWithProxy() throws Exception {
+        try (AsyncHttpClient asyncHttpClient = asyncHttpClient(config().setFollowRedirect(true).setUseInsecureTrustManager(true).setKeepAlive(true))) {
+        	Builder proxyServer = proxyServer("localhost", port1);
+        	proxyServer.setCustomHeaders(r -> new DefaultHttpHeaders().set(ProxyHandler.HEADER_FORBIDDEN, "1"));
+            RequestBuilder rb = get(getTargetUrl2()).setProxyServer(proxyServer);
+
+            Response response1 = asyncHttpClient.executeRequest(rb.build()).get();
+            assertEquals(403, response1.getStatusCode());
+
+            Response response2 = asyncHttpClient.executeRequest(rb.build()).get();
+            assertEquals(403, response2.getStatusCode());
+            
+            Response response3 = asyncHttpClient.executeRequest(rb.build()).get();
+            assertEquals(403, response3.getStatusCode());
+        }
+    }
+    
+    @RepeatedIfExceptionsTest(repeats = 5)
+    public void testClosedConnectionWithProxy() throws Exception {
+        try (AsyncHttpClient asyncHttpClient = asyncHttpClient(
+                config().setFollowRedirect(true).setUseInsecureTrustManager(true).setKeepAlive(true))) {
+            Builder proxyServer = proxyServer("localhost", port1);
+            proxyServer.setCustomHeaders(r -> new DefaultHttpHeaders().set(ProxyHandler.HEADER_FORBIDDEN, "2"));
+            RequestBuilder rb = get(getTargetUrl2()).setProxyServer(proxyServer);
+
+            assertThrowsExactly(ExecutionException.class, () -> asyncHttpClient.executeRequest(rb.build()).get());
+            assertThrowsExactly(ExecutionException.class, () -> asyncHttpClient.executeRequest(rb.build()).get());
+            assertThrowsExactly(ExecutionException.class, () -> asyncHttpClient.executeRequest(rb.build()).get());
+        }
+    }
+
+    public static class ProxyHandler extends ConnectHandler {
+    	final static String HEADER_FORBIDDEN = "X-REJECT-REQUEST";
+
+        @Override
+        public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+            if (HttpConstants.Methods.CONNECT.equalsIgnoreCase(request.getMethod())) {
+                String headerValue = request.getHeader(HEADER_FORBIDDEN);
+                if (headerValue == null) {
+                    headerValue = "";
+                }
+                switch (headerValue) {
+                case "1":
+                    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+                    r.setHandled(true);
+                    return;
+                case "2":
+                    r.getHttpChannel().getConnection().close();
+                    r.setHandled(true);
+                    return;
+                }
+            }
+            super.handle(s, r, request, response);
+        }
+    }
 }
diff --git a/client/src/test/java/org/asynchttpclient/request/body/PutByteBufTest.java b/client/src/test/java/org/asynchttpclient/request/body/PutByteBufTest.java
new file mode 100644
index 000000000..3260604d3
--- /dev/null
+++ b/client/src/test/java/org/asynchttpclient/request/body/PutByteBufTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved.
+ *
+ * This program is licensed to you under the Apache License Version 2.0,
+ * and you may not use this file except in compliance with the Apache License Version 2.0.
+ * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the Apache License Version 2.0 is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
+ */
+package org.asynchttpclient.request.body;
+
+import io.github.artsok.RepeatedIfExceptionsTest;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.asynchttpclient.AbstractBasicTest;
+import org.asynchttpclient.AsyncHttpClient;
+import org.asynchttpclient.Response;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Arrays;
+
+import static org.asynchttpclient.Dsl.asyncHttpClient;
+import static org.asynchttpclient.Dsl.config;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class PutByteBufTest extends AbstractBasicTest {
+
+    private void put(String message) throws Exception {
+        ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getBytes());
+        try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofSeconds(2)))) {
+            Response response = client.preparePut(getTargetUrl()).setBody(byteBuf).execute().get();
+            assertEquals(response.getStatusCode(), 200);
+            assertEquals(response.getResponseBody(), message);
+        }
+    }
+
+    @RepeatedIfExceptionsTest(repeats = 5)
+    public void testPutSmallBody() throws Exception {
+        put("Hello Test");
+    }
+
+    @RepeatedIfExceptionsTest(repeats = 5)
+    public void testPutBigBody() throws Exception {
+        byte[] array = new byte[2048];
+        Arrays.fill(array, (byte) 97);
+        String longString = new String(array, StandardCharsets.UTF_8);
+
+        put(longString);
+    }
+
+    @Override
+    public AbstractHandler configureHandler() throws Exception {
+        return new AbstractHandler() {
+
+            @Override
+            public void handle(String s, Request request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException {
+                int size = 1024;
+                if (request.getContentLength() > 0) {
+                    size = request.getContentLength();
+                }
+                byte[] bytes = new byte[size];
+                if (bytes.length > 0) {
+                    final int read = request.getInputStream().read(bytes);
+                    response.getOutputStream().write(bytes, 0, read);
+                }
+
+                response.setStatus(200);
+                response.getOutputStream().flush();
+            }
+        };
+    }
+}
diff --git a/client/src/test/java/org/asynchttpclient/request/body/generator/FeedableBodyGeneratorTest.java b/client/src/test/java/org/asynchttpclient/request/body/generator/FeedableBodyGeneratorTest.java
index 4c8d14693..7c2a3579b 100755
--- a/client/src/test/java/org/asynchttpclient/request/body/generator/FeedableBodyGeneratorTest.java
+++ b/client/src/test/java/org/asynchttpclient/request/body/generator/FeedableBodyGeneratorTest.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java b/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java
index 1ef8201f6..2568b7ae1 100644
--- a/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java
+++ b/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java b/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java
index d3b193209..b8e67b79f 100644
--- a/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java
+++ b/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java
@@ -39,15 +39,17 @@ public SocksProxy(int runningTime) throws IOException {
             Set<SelectionKey> keys = select.selectedKeys();
             for (SelectionKey k : keys) {
 
-                if (!k.isValid())
+                if (!k.isValid()) {
                     continue;
+                }
 
                 // new connection?
                 if (k.isAcceptable() && k.channel() == socks) {
                     // server socket
                     SocketChannel csock = socks.accept();
-                    if (csock == null)
+                    if (csock == null) {
                         continue;
+                    }
                     addClient(csock);
                     csock.register(select, SelectionKey.OP_READ);
                 } else if (k.isReadable()) {
@@ -56,14 +58,16 @@ public SocksProxy(int runningTime) throws IOException {
                         SocksClient cl = clients.get(i);
                         try {
                             if (k.channel() == cl.client) // from client (e.g. socks client)
+                            {
                                 cl.newClientData(select);
-                            else if (k.channel() == cl.remote) {  // from server client is connected to (e.g. website)
+                            } else if (k.channel() == cl.remote) {  // from server client is connected to (e.g. website)
                                 cl.newRemoteData();
                             }
                         } catch (IOException e) { // error occurred - remove client
                             cl.client.close();
-                            if (cl.remote != null)
+                            if (cl.remote != null) {
                                 cl.remote.close();
+                            }
                             k.cancel();
                             clients.remove(cl);
                         }
@@ -75,10 +79,11 @@ else if (k.channel() == cl.remote) {  // from server client is connected to (e.g
             // client timeout check
             for (int i = 0; i < clients.size(); i++) {
                 SocksClient cl = clients.get(i);
-                if ((System.currentTimeMillis() - cl.lastData) > 30000L) {
+                if (System.currentTimeMillis() - cl.lastData > 30000L) {
                     cl.client.close();
-                    if (cl.remote != null)
+                    if (cl.remote != null) {
                         cl.remote.close();
+                    }
                     clients.remove(cl);
                 }
             }
@@ -115,8 +120,9 @@ class SocksClient {
 
         void newRemoteData() throws IOException {
             ByteBuffer buf = ByteBuffer.allocate(1024);
-            if (remote.read(buf) == -1)
+            if (remote.read(buf) == -1) {
                 throw new IOException("disconnected");
+            }
             lastData = System.currentTimeMillis();
             buf.flip();
             client.write(buf);
@@ -125,8 +131,9 @@ void newRemoteData() throws IOException {
         void newClientData(Selector selector) throws IOException {
             if (!connected) {
                 ByteBuffer inbuf = ByteBuffer.allocate(512);
-                if (client.read(inbuf) < 1)
+                if (client.read(inbuf) < 1) {
                     return;
+                }
                 inbuf.flip();
 
                 // read socks header
@@ -143,13 +150,15 @@ void newClientData(Selector selector) throws IOException {
 
                 final int port = inbuf.getShort() & 0xffff;
 
-                final byte ip[] = new byte[4];
+                final byte[] ip = new byte[4];
                 // fetch IP
                 inbuf.get(ip);
 
                 InetAddress remoteAddr = InetAddress.getByAddress(ip);
 
-                while ((inbuf.get()) != 0) ; // username
+                while (inbuf.get() != 0) {
+                    ; // username
+                }
 
                 // hostname provided, not IP
                 if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] != 0) { // host provided
@@ -172,8 +181,9 @@ void newClientData(Selector selector) throws IOException {
                 out.flip();
                 client.write(out);
 
-                if (!remote.isConnected())
+                if (!remote.isConnected()) {
                     throw new IOException("connect failed");
+                }
 
                 remote.configureBlocking(false);
                 remote.register(selector, SelectionKey.OP_READ);
@@ -181,8 +191,9 @@ void newClientData(Selector selector) throws IOException {
                 connected = true;
             } else {
                 ByteBuffer buf = ByteBuffer.allocate(1024);
-                if (client.read(buf) == -1)
+                if (client.read(buf) == -1) {
                     throw new IOException("disconnected");
+                }
                 lastData = System.currentTimeMillis();
                 buf.flip();
                 remote.write(buf);
diff --git a/client/src/test/java/org/asynchttpclient/uri/UriTest.java b/client/src/test/java/org/asynchttpclient/uri/UriTest.java
index 143008e15..f766854e1 100644
--- a/client/src/test/java/org/asynchttpclient/uri/UriTest.java
+++ b/client/src/test/java/org/asynchttpclient/uri/UriTest.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java b/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java
index a2177ae96..ce9cda3dc 100644
--- a/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java
+++ b/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java
@@ -1,5 +1,5 @@
 /*
- *    Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
+ *    Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved.
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
diff --git a/docs/technical-overview.md b/docs/technical-overview.md
deleted file mode 100644
index ad65ec1b9..000000000
--- a/docs/technical-overview.md
+++ /dev/null
@@ -1,368 +0,0 @@
-# [WIP] AsyncHttpClient Technical Overview
-
-#### Disclaimer
-
-This document is a work in progress.
-
-## Motivation
-
-While heavily used (~2.3M downloads across the project in December 2020 alone), AsyncHttpClient (or AHC) does not - at this point in time - have a single guiding document that
-explains how it works internally. As a maintainer fresh on the scene it was unclear to me ([@TomGranot](https://github.com/TomGranot)) exactly how all the pieces fit together.
-
-As part of the attempt to educate myself, I figured it would be a good idea to write a technical overview of the project. This document provides an in-depth walkthtough of the
-library, allowing new potential contributors to "hop on" the coding train as fast as possible.
-
-Note that this library *is not small*. I expect that in addition to offering a better understanding as to how each piece *works*, writing this document will also allow me to
-understand which pieces *do not work* as well as expected, and direct me towards things that need a little bit of love.
-
-PRs are open for anyone who wants to help out. For now - let the fun begin. :)
-
-**Note: I wrote this guide while using AHC 2.12.2**.
-
-## The flow of a request
-
-### Introduction
-
-AHC is an *Asynchronous* HTTP Client. That means that it needs to have some underlying mechanism of dealing with response data that arrives **asynchronously**. To make that
-part easier, the creator of the library ([@jfarcand](https://github.com/jfarcand)) built it on top of [Netty](https://netty.io/), which is (
-by [their own definition](https://netty.io/#content:~:text=Netty%20is%20a%20NIO%20client%20server,as%20TCP%20and%20UDP%20socket%20server.)) "a framework that enables quick and
-easy development of network applications".
-
-This article is not a Netty user guide. If you're interested in all Netty has to offer, you should check out
-the [official user guide](https://netty.io/wiki/user-guide-for-4.x.html). This article is, instead, more of a discussion of using Netty *in the wild* - an overview of what a
-client library built on top of Netty actually looks like in practice.
-
-### The code in full
-
-The best way to explore what the client actually does is, of course, by following the path a request takes.
-
-Consider the following bit of
-code, [taken verbatim from one of the simplest tests](https://github.com/AsyncHttpClient/async-http-client/blob/2b12d0ba819e05153fa265b4da7ca900651fd5b3/client/src/test/java/org/asynchttpclient/BasicHttpTest.java#L81-L91)
-in the library:
-
-```java
-@Test
-  public void getRootUrl() throws Throwable {
-    withClient().run(client ->
-      withServer(server).run(server -> {
-        String url = server.getHttpUrl();
-        server.enqueueOk();
-
-        Response response = client.executeRequest(get(url), new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS);
-        assertEquals(response.getUri().toUrl(), url);
-      }));
-  }
-```
-
-Let's take it bit by bit.
-
-First:
-
-```java
-withClient().run(client ->
-  withServer(server).run(server -> {
-    String url = server.getHttpUrl();
-    server.enqueueOk();
-```
-
-These lines take care of spinning up a server to run the test against, and create an instance of `AsyncHttpClient` called `client`. If you were to drill deeper into the code,
-you'd notice that the instantiation of `client` can be simplified to (converted from functional to procedural for the sake of the explanation):
-
-```java
-DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().build.()setMaxRedirects(0);
-AsyncHttpClient client = new DefaultAsyncHttpClient(config);
-```
-
-Once the server and the client have been created, we can now run our test:
-
-```java
-Response response = client.executeRequest(get(url), new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS);
-assertEquals(response.getUri().toUrl(), url);
-```
-
-The first line executes a `GET` request to the URL of the server that was previously spun up, while the second line is the assertion part of our test. Once the request is
-completed, the final `Response` object is returned.
-
-The intersting bits, of course, happen between the lines - and the best way to start the discussion is by considering what happens under the hood when a new client is
-instantiated.
-
-### Creating a new AsyncHTTPClient - Configuration
-
-AHC was designed to be *heavily configurable*. There are many, many different knobs you can turn and buttons you can press in order to get it to behave _just right_
-. [`DefaultAsyncHttpClientConfig`](https://github.com/AsyncHttpClient/async-http-client/blob/d4f1e5835b81a5e813033ba2a64a07b020c70007/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java)
-is a utility class that pulls a set
-of [hard-coded, sane defaults](https://github.com/AsyncHttpClient/async-http-client/blob/d4f1e5835b81a5e813033ba2a64a07b020c70007/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties)
-to prevent you from having to deal with these mundane configurations - please check it out if you have the time.
-
-A keen observer will note that the construction of a `DefaultAsyncHttpClient` is done using
-the [Builder Pattern](https://dzone.com/articles/design-patterns-the-builder-pattern) - this is useful, since many times you want to change a few parameters in a
-request (`followRedirect`, `requestTimeout`, etc... ) but still rely on the rest of the default configuration properties. The reason I'm mentioning this here is to prevent a
-bit of busywork on your end the next time you want to create a client - it's much, much easier to work off of the default client and tweak properties than creating your own
-set of configuration properties.
-
-The `setMaxRedicrects(0)` from the initialization code above is an example of doign this in practice. Having no redirects following the `GET` requeset is useful in the context
-of the test, and so we turn a knob to ensure none do.
-
-### Creating a new AsyncHTTPClient - Client Instantiation
-
-Coming back to our example - once we've decided on a proper configuration, it's time to create a client. Let's look at the constructor of
-the [`DefaultAsyncHttpClient`](https://github.com/AsyncHttpClient/async-http-client/blob/a44aac86616f4e8ffe6977dfef0f0aa460e79d07/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java):
-
-```java
-  public DefaultAsyncHttpClient(AsyncHttpClientConfig config) {
-
-    this.config = config;
-    this.noRequestFilters = config.getRequestFilters().isEmpty();
-    allowStopNettyTimer = config.getNettyTimer() == null;
-    nettyTimer = allowStopNettyTimer ? newNettyTimer(config) : config.getNettyTimer();
-
-    channelManager = new ChannelManager(config, nettyTimer);
-    requestSender = new NettyRequestSender(config, channelManager, nettyTimer, new AsyncHttpClientState(closed));
-    channelManager.configureBootstraps(requestSender);
-
-    CookieStore cookieStore = config.getCookieStore();
-    if (cookieStore != null) {
-      int cookieStoreCount = config.getCookieStore().incrementAndGet();
-      if (
-        allowStopNettyTimer // timer is not shared
-        || cookieStoreCount == 1 // this is the first AHC instance for the shared (user-provided) timer
-      ) {
-        nettyTimer.newTimeout(new CookieEvictionTask(config.expiredCookieEvictionDelay(), cookieStore),
-          config.expiredCookieEvictionDelay(), TimeUnit.MILLISECONDS);
-      }
-    }
-  }
-```
-
-The constructor actually reveals a lot of the moving parts of AHC, and is worth a proper walkthrough:
-
-#### `RequestFilters`
-
-```java
- this.noRequestFilters = config.getRequestFilters().isEmpty();
-```
-
-`RequestFilters` are a way to perform some form of computation **before sending a request to a server**. You can read more about request filters [here](#request-filters), but
-a simple example is
-the [ThrottleRequestFilter](https://github.com/AsyncHttpClient/async-http-client/blob/758dcf214bf0ec08142ba234a3967d98a3dc60ef/client/src/main/java/org/asynchttpclient/filter/ThrottleRequestFilter.java)
-that throttles requests by waiting for a response to arrive before executing the next request in line.
-
-Note that there is another set of filters, `ResponseFilters`, that can perform computations **before processing the first byte of the response**. You can read more about
-them [here](#response-filters).
-
-#### `NettyTimer`
-
-```java
-allowStopNettyTimer = config.getNettyTimer() == null;
-nettyTimer = allowStopNettyTimer ? newNettyTimer(config) : config.getNettyTimer();
-```
-
-`NettyTimer` is actually not a timer, but a *task executor* that waits an arbitrary amount of time before performing the next task. In the case of the code above, it is used
-for evicting cookies after they expire - but it has many different use cases (request timeouts being a prime example).
-
-#### `ChannelManager`
-
-```java
-channelManager = new ChannelManager(config, nettyTimer);
-```
-
-`ChannelManager` requires a [section of its own](#channelmanager), but the bottom line is that one has to do a lot of boilerplate work with Channels when building an HTTP
-client using Netty. For any given request there's a variable number of channel operations you would have to take, and there's a lot of value in re-using existing channels in
-clever ways instead of opening new ones. `ChannelManager` is AHC's way of encapsulating at least some of that functionality (for
-example, [connection pooling](https://en.wikipedia.org/wiki/Connection_pool#:~:text=In%20software%20engineering%2C%20a%20connection,executing%20commands%20on%20a%20database.))
-into a single object, instead of having it spread out all over the place.
-
-There are two similiarly-named constructs in the project, so I'm mentioning them in this
-
-* `ChannelPool`, as it
-  is [implemented in AHC](https://github.com/AsyncHttpClient/async-http-client/blob/758dcf214bf0ec08142ba234a3967d98a3dc60ef/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java#L21)
-  , is an **AHC structure** designed to be a "container" of channels - a place you can add and remove channels from as the need arises. Note that the AHC implementation (that
-  might go as far back as 2012) *predates* the [Netty implementation](https://netty.io/news/2015/05/07/4-0-28-Final.html) introduced in 2015 (see
-  this [AHC user guide entry](https://asynchttpclient.github.io/async-http-client/configuring.html#contentBox:~:text=ConnectionsPoo,-%3C) from 2012 in which `ConnectionPool`
-  is referenced as proof).
-
-  As
-  the [Netty release mentions](https://netty.io/news/2015/05/07/4-0-28-Final.html#main-content:~:text=Many%20of%20our%20users%20needed%20to,used%20Netty%20to%20writing%20a%20client.)
-  , connection pooling in the world of Netty-based clients is a valuable feature to have, one that [Jean-Francois](https://github.com/jfarcand) implemented himself instead of
-  waiting for Netty to do so. This might confuse anyone coming to the code a at a later point in time - like me - and I have yet to explore the tradeoffs of stripping away the
-  current implementation and in favor of the upstream one. See [this issue](https://github.com/AsyncHttpClient/async-http-client/issues/1766) for current progress.
-
-* [`ChannelGroup`](https://netty.io/4.0/api/io/netty/channel/group/ChannelGroup.html) (not to be confused with `ChannelPool`) is a **Netty structure** designed to work with
-  Netty `Channel`s *in bulk*, to reduce the need to perform the same operation on multiple channels sequnetially.
-
-#### `NettyRequestSender`
-
-```java
-requestSender = new NettyRequestSender(config, channelManager, nettyTimer, new AsyncHttpClientState(closed));
-channelManager.configureBootstraps(requestSender);
-```
-
-`NettyRequestSender` does the all the heavy lifting required for sending the HTTP request - creating the required `Request` and `Response` objects, making sure `CONNECT`
-requests are sent before the relevant requests, dealing with proxy servers (in the case
-of [HTTPS connections](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT)), dispatching DNS hostname resolution requests and more.
-
-A few extra comments before we move on:
-
-* When finished with all the work, `NettyRequestSender` will send back
-  a [`ListenableFuture`](https://github.com/AsyncHttpClient/async-http-client/blob/d47c56e7ee80b76a4cffd4770237239cfea0ffd6/client/src/main/java/org/asynchttpclient/ListenableFuture.java#L40)
-  . AHC's `ListenableFuture` is an extension of a normal Java `Future` that allows for the addition of "Listeners" - pieces of code that get executed once the computation (the
-  one blocking the `Future` from completing) is finished. It is an example of a *very* common abstraction that exists in many different Java projects -
-  Google's [Guava](https://github.com/google/guava) [has one](https://github.com/google/guava/blob/master/futures/listenablefuture1/src/com/google/common/util/concurrent/ListenableFuture.java)
-  , for example, and so does [Spring](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/concurrent/ListenableFuture.html)).
-
-* Note the invocation of `configureBootstraps` in the second line here. `Bootstrap`s are a Netty concept that make it easy to set up `Channel`s - we'll talk about them a bit
-  later.
-
-#### `CookieStore`
-
-```java
-CookieStore cookieStore = config.getCookieStore();
-    if (cookieStore != null) {
-      int cookieStoreCount = config.getCookieStore().incrementAndGet();
-      if (
-        allowStopNettyTimer // timer is not shared
-        || cookieStoreCount == 1 // this is the first AHC instance for the shared (user-provided) timer
-      ) {
-        nettyTimer.newTimeout(new CookieEvictionTask(config.expiredCookieEvictionDelay(), cookieStore),
-          config.expiredCookieEvictionDelay(), TimeUnit.MILLISECONDS);
-      }
-    }
-```
-
-`CookieStore` is, well, a container for cookies. In this context, it is used to handle the task of cookie eviction (removing cookies whose expiry date has passed). This is, by
-the way, an example of one of the *many, many features* AHC supports out of the box that might not be evident upon first observation.
-
-Once the client has been properly configured, it's time to actually execute the request.
-
-### Executing a request - Before execution
-
-Take a look at the execution line from the code above again:
-
-```java
-Response response = client.executeRequest(get(url), new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS);
-```
-
-Remember that what we have in front of us is an instance of `AsyncHttpClient` called `client` that is configured with an `AsyncHttpClientConfig`, and more specifically an
-instance of `DefaultAsyncHttpClient` that is configured with `DefaultAsyncHttpClientConfig`.
-
-The `executeRequest` method is passed two arguments, and returns a `ListenableFuture`. The `Response` created by executing the `get` method on the `ListenableFuture` is the
-end of the line in our case here, since this test is very simple - there's no response body to parse or any other computations to do in order to assert the test succeeded. The
-only thing that is required for the correct operation of the code is for the `Response` to come back with the correct URL.
-
-Let's turn our eyes to the two arguments passed to `executeRequest`, then, since they are the key parts here:
-
-1. `get(url)` is the functional equivalent of `new RequestBuilder("GET").setUrl(url)`. `RequestBuilder` is in charge of scaffolding an instance
-   of [AHC's `Request` object](https://github.com/AsyncHttpClient/async-http-client/blob/c5eff423ebdd0cddd00bc6fcf17682651a151028/client/src/main/java/org/asynchttpclient/Request.java)
-   and providing it with sane defaults - mostly regarding HTTP headers (`RequestBuilder` does for `Request` what `DefaultAsyncHttpClientConfig.Builder()` does
-   for `DefaultAsyncHttpClient`).
-    1. In our case, the `Request` contains no body (it's a simple `GET`). However, if that request was, for example, a `POST` - it could have a payload that would need to be
-       sent to the server via HTTP as well. We'll be talking about `Request` in more detail [here](#working-with-request-bodies), including how to work with request bodies.
-2. To fully understand what `AsyncCompletionHandlerAdapter` is, and why it's such a core piece of everything that goes on in AHC, a bit of Netty background is required. Let's
-   take a sidestep for a moment:
-
-#### Netty `Channel`s and their associated entities ( `ChannelPipeline`s, `ChannelHandler`s and `ChannelAdapter`s)
-
-Recall that AHC is built on [Netty](https://netty.io/) and its networking abstractions. If you want to dive deeper into the framework you **should**
-read [Netty in Action](https://www.manning.com/books/netty-in-action) (great book, Norman!), but for the sake of our discussion it's enough to settle on clarifying a few basic
-terms:
-
-1. [`Channel`](https://netty.io/4.1/api/io/netty/channel/Channel.html) is Netty's version of a normal
-   Java [`Socket`](https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html), greatly simplified for easier usage. It
-   encapsulates [all that you can know and do](https://netty.io/4.1/api/io/netty/channel/Channel.html#allclasses_navbar_top:~:text=the%20current%20state%20of%20the%20channel,and%20requests%20associated%20with%20the%20channel.)
-   with a regular `Socket`:
-
-    1. **State** - Is the socket currently open? Is it currently closed?
-    2. **I/O Options** - Can we read from it? Can we write to it?
-    3. **Configuration** - What is the receive buffer size? What is the connect timeout?
-    4. `ChannelPipeline` - A reference to this `Channel`'s `ChannelPipeline`.
-
-2. Note that operations on a channel, in and of themselves, are **blocking** - that is, any operation that is performed on a channel blocks any other operations from being
-   performed on the channel at any given point in time. This is contrary to the Asynchronous nature Netty purports to support.
-
-   To solve the issue, Netty adds a `ChannelPipeline` to every new `Channel` that is initialised. A `ChannelPipeline` is nothing but a container for `ChannelHandlers`.
-3. [`ChannelHandler`](https://netty.io/4.1/api/io/netty/channel/ChannelHandler.html)s encapsulate the application logic of a Netty application. To be more precise, a *chain*
-   of `ChannelHandler`s, each in charge of one or more small pieces of logic that - when taken together - describe the entire data processing that is supposed to take place
-   during the lifetime of the application.
-
-4. [`ChannelHandlerContext`](https://netty.io/4.0/api/io/netty/channel/ChannelHandlerContext.html) is also worth mentioning here - it's the actual mechanism a `ChannelHandler`
-   uses to talk to the `ChannelPipeline` that encapsulates it.
-
-5. `ChannelXHandlerAdapter`s are a set of *default* handler implementations - "sugar" that should make the development of application logic easier. `X` can
-   be `Inbound ` (`ChannelInboundHandlerAdapter`), `Oubound` (`ChannelOutboundHandlerAdapter`) or one of many other options Netty provides out of the box.
-
-#### `ChannelXHandlerAdapter` VS. `AsyncXHandlerAdapter`
-
-This where it's important to note the difference between `ChannelXHandlerAdapter` (i.e. `ChannelInboundHandlerAdapater`) - which is a **Netty construct**
-and `AsyncXHandlerAdapter` (i.e. `AsyncCompletionHandlerAdapater`) - which is an **AHC construct**.
-
-Basically, `ChannelXHandlerAdapter` is a Netty construct that provides a default implementation of a `ChannelHandler`, while `AsyncXHandlerAdapter` is an AHC construct that
-provides a default implementation of an `AsyncHandler`.
-
-A `ChannelXHandlerAdapter` has methods that are called when *handler-related* and *channel-related* events occur. When the events "fire", a piece of business logic is carried
-out in the relevant method, and the operation is then **passed on to the** **next `ChannelHandler` in line.** *The methods return nothing.*
-
-An `AsyncXHandlerAdapter` works a bit differently. It has methods that are triggered when *some piece of data is available during an asynchronous response processing*. The
-methods are invoked in a pre-determined order, based on the expected arrival of each piece of data (when the status code arrives, when the headers arrive, etc.). When these
-pieces of information become availale, a piece of business logic is carried out in the relevant method, and *
-a [`STATE`](https://github.com/AsyncHttpClient/async-http-client/blob/f61f88e694850818950195379c5ba7efd1cd82ee/client/src/main/java/org/asynchttpclient/AsyncHandler.java#L242-L253)
-is returned*. This `STATE` enum instructs the current implementation of the `AsyncHandler` (in our case, `AsyncXHandlerAdapater`) whether it should `CONTINUE` or `ABORT` the
-current processing.
-
-This is **the core of AHC**: an asynchronous mechanism that encodes - and allows a developer to "hook" into - the various stages of the asynchronous processing of an HTTP
-response.
-
-### Executing a request - During execution
-
-TODO
-
-### Executing a request - After execution
-
-TODO
-
-## Working with Netty channels
-
-### ChannelManager
-
-TODO
-
-## Transforming requests and responses
-
-TODO
-
-### Working with Request Bodies
-
-TODO
-
-### Request Filters
-
-TODO
-
-### Working with Response Bodies
-
-TODO
-
-### Response Filters
-
-TODO
-
-### Handlers
-
-TODO
-
-## Resources
-
-### Netty
-
-* https://seeallhearall.blogspot.com/2012/05/netty-tutorial-part-1-introduction-to.html
-
-### AsyncHttpClient
-
-TODO
-
-### HTTP
-
-TODO
-
-## Footnotes
-
-[^1] Some Netty-related definitions borrow heavily from [here](https://seeallhearall.blogspot.com/2012/05/netty-tutorial-part-1-introduction-to.html).
diff --git a/pom.xml b/pom.xml
index 3965c62ef..9d64fc54b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
 
     <groupId>org.asynchttpclient</groupId>
     <artifactId>async-http-client-project</artifactId>
-    <version>3.0.0.Beta3</version>
+    <version>3.0.2</version>
     <packaging>pom</packaging>
 
     <name>AHC/Project</name>
@@ -39,34 +39,30 @@
         </license>
     </licenses>
 
-    <developers>
-        <developer>
-            <id>slandelle</id>
-            <name>Stephane Landelle</name>
-            <email>slandelle@gatling.io</email>
-        </developer>
-        <developer>
-            <id>hyperxpro</id>
-            <name>Aayush Atharva</name>
-            <email>aayush@shieldblaze.com</email>
-        </developer>
-    </developers>
-
     <properties>
         <java.version>11</java.version>
         <maven.compiler.source>11</maven.compiler.source>
         <maven.compiler.target>11</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
-        <netty.version>4.1.107.Final</netty.version>
-        <netty.iouring>0.0.25.Final</netty.iouring>
-        <brotli4j.version>1.16.0</brotli4j.version>
-        <slf4j.version>2.0.12</slf4j.version>
+        <netty.version>4.1.119.Final</netty.version>
+        <netty.iouring>0.0.26.Final</netty.iouring>
+        <brotli4j.version>1.18.0</brotli4j.version>
+        <slf4j.version>2.0.16</slf4j.version>
+        <zstd-jni.version>1.5.7-2</zstd-jni.version>
         <activation.version>2.0.1</activation.version>
-        <logback.version>1.4.11</logback.version>
-        <jetbrains-annotations.version>24.0.1</jetbrains-annotations.version>
+        <logback.version>1.5.18</logback.version>
+        <jetbrains-annotations.version>26.0.2</jetbrains-annotations.version>
     </properties>
 
+    <developers>
+        <developer>
+            <id>hyperxpro</id>
+            <name>Aayush Atharva</name>
+            <email>aayush@shieldblaze.com</email>
+        </developer>
+    </developers>
+
     <scm>
         <connection>scm:git:git@github.com:AsyncHttpClient/async-http-client.git</connection>
         <developerConnection>scm:git:git@github.com:AsyncHttpClient/async-http-client.git</developerConnection>
@@ -109,14 +105,14 @@
             <dependency>
                 <groupId>org.junit</groupId>
                 <artifactId>junit-bom</artifactId>
-                <version>5.10.2</version>
+                <version>5.13.0</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
             <dependency>
                 <groupId>io.github.nettyplus</groupId>
                 <artifactId>netty-leak-detector-junit-extension</artifactId>
-                <version>0.0.2</version>
+                <version>0.2.0</version>
             </dependency>
         </dependencies>
     </dependencyManagement>
@@ -224,6 +220,13 @@
             <optional>true</optional>
         </dependency>
 
+        <dependency>
+            <groupId>com.github.luben</groupId>
+            <artifactId>zstd-jni</artifactId>
+            <version>${zstd-jni.version}</version>
+            <optional>true</optional>
+        </dependency>
+
         <dependency>
             <groupId>com.aayushatharva.brotli4j</groupId>
             <artifactId>brotli4j</artifactId>
@@ -290,7 +293,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.12.1</version>
+                <version>3.14.0</version>
                 <configuration>
                     <source>11</source>
                     <target>11</target>
@@ -319,12 +322,12 @@
                         <path>
                             <groupId>com.google.errorprone</groupId>
                             <artifactId>error_prone_core</artifactId>
-                            <version>2.25.0</version>
+                            <version>2.31.0</version>
                         </path>
                         <path>
                             <groupId>com.uber.nullaway</groupId>
                             <artifactId>nullaway</artifactId>
-                            <version>0.10.10</version>
+                            <version>0.12.6</version>
                         </path>
                         <!-- Other annotation processors go here -->
                     </annotationProcessorPaths>
@@ -334,7 +337,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <version>3.2.5</version>
+                <version>3.5.2</version>
                 <configuration>
                     <argLine>
                         @{argLine} --add-exports java.base/jdk.internal.misc=ALL-UNNAMED
@@ -345,7 +348,7 @@
             <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
-                <version>0.8.9</version>
+                <version>0.8.12</version>
                 <executions>
                     <execution>
                         <goals>
@@ -368,7 +371,6 @@
                 <version>3.2.1</version>
                 <executions>
                     <execution>
-                        <id>attach-sources</id>
                         <goals>
                             <goal>jar-no-fork</goal>
                         </goals>
@@ -379,7 +381,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
-                <version>3.6.3</version>
+                <version>3.11.1</version>
                 <executions>
                     <execution>
                         <id>attach-javadocs</id>
@@ -393,7 +395,7 @@
             <plugin>
                 <groupId>org.sonatype.plugins</groupId>
                 <artifactId>nexus-staging-maven-plugin</artifactId>
-                <version>1.6.13</version>
+                <version>1.7.0</version>
                 <extensions>true</extensions>
                 <configuration>
                     <serverId>ossrh</serverId>
@@ -406,7 +408,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-gpg-plugin</artifactId>
-                <version>3.1.0</version>
+                <version>3.2.7</version>
                 <executions>
                     <execution>
                         <id>sign-artifacts</id>
@@ -420,10 +422,38 @@
                                 <arg>--pinentry-mode</arg>
                                 <arg>loopback</arg>
                             </gpgArguments>
+                            <gpg.skip>false</gpg.skip>
                         </configuration>
                     </execution>
                 </executions>
             </plugin>
+
+            <plugin>
+                <groupId>com.github.siom79.japicmp</groupId>
+                <artifactId>japicmp-maven-plugin</artifactId>
+                <version>0.23.1</version>
+                <configuration>
+                    <version>
+                        <old>RELEASE</old>
+                        <new>${project.version}</new>
+                    </version>
+                    <parameter>
+                        <onlyModified>true</onlyModified>
+                        <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications>
+                        <breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications>
+                        <includeSynthetic>false</includeSynthetic>
+                        <accessModifier>public</accessModifier>
+                    </parameter>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>cmp</goal>
+                        </goals>
+                        <phase>verify</phase>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 </project>
