diff --git a/.gitignore b/.gitignore
index b023787595167bf82805d1b90e2bb5b6a4ae23d8..d424b2597a2c49be2f089e39103dbd4dded8d276 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@ test-output
 MANIFEST.MF
 work
 atlassian-ide-plugin.xml
+/bom/.flattened-pom.xml
diff --git a/.travis.yml b/.travis.yml
index bb8adf60b089fcab4d4ae7c3fd134d3e9541a741..2760c26e6f836c6fd1063db01acccd5df173e63e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,12 +1,12 @@
 language: java
 jdk:
-  - oraclejdk8
+  - openjdk8
 
 before_script:
   - travis/before_script.sh
 
 script: 
-  - mvn test -Ptest-output
+  - mvn test javadoc:javadoc -Ptest-output
   - find $HOME/.m2 -name "_remote.repositories" | xargs rm
   - find $HOME/.m2 -name "resolver-status.properties" | xargs rm -f
   
@@ -16,12 +16,6 @@ after_success:
 
 sudo: false
 
-# https://github.com/travis-ci/travis-ci/issues/3259
-addons:
-  apt:
-    packages:
-      - oracle-java8-installer
-
 # Cache settings
 cache:
   directories:
diff --git a/README.md b/README.md
index eed94ccf15dc31e99efb85c8b18848aae897b18f..a306c4937bee18680d8df9408190fdee7df6322f 100644
--- a/README.md
+++ b/README.md
@@ -5,20 +5,41 @@ Follow [@AsyncHttpClient](https://twitter.com/AsyncHttpClient) on Twitter.
 The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses.
 The library also supports the WebSocket Protocol.
 
-It's built on top of [Netty](https://github.com/netty/netty). I's currently compiled on Java 8 but runs on Java 9 too.
+It's built on top of [Netty](https://github.com/netty/netty). It's currently compiled on Java 8 but runs on Java 9 too.
 
 ## Installation
 
-Binaries are deployed on Maven central:
+Binaries are deployed on Maven Central.
+
+Import the AsyncHttpClient Bill of Materials (BOM) to add dependency management for AsyncHttpClient artifacts to your project:
+
+```xml
+<dependencyManagement>
+    <dependencies>
+        <dependency>
+            <groupId>org.asynchttpclient</groupId>
+            <artifactId>async-http-client-bom</artifactId>
+            <version>LATEST_VERSION</version>
+            <type>pom</type>
+            <scope>import</scope>
+        </dependency>
+    </dependencies>
+</dependencyManagement>
+```
+
+Add a dependency on the main AsyncHttpClient artifact: 
 
 ```xml
-<dependency>
-	<groupId>org.asynchttpclient</groupId>
-	<artifactId>async-http-client</artifactId>
-	<version>LATEST_VERSION</version>
-</dependency>
+<dependencies>
+    <dependency>
+    	<groupId>org.asynchttpclient</groupId>
+    	<artifactId>async-http-client</artifactId>
+    </dependency>
+</dependencies>
 ```
 
+The `async-http-client-extras-*` and other modules can also be added without having to specify the version for each dependency, because they are all managed via the BOM.
+
 ## Version
 
 AHC doesn't use SEMVER, and won't.
@@ -73,7 +94,7 @@ AsyncHttpClient c = asyncHttpClient(config().setProxyServer(proxyServer("127.0.0
 ### Basics
 
 AHC provides 2 APIs for defining requests: bound and unbound.
-`AsyncHttpClient` and Dls` provide methods for standard HTTP methods (POST, PUT, etc) but you can also pass a custom one.
+`AsyncHttpClient` and Dsl` provide methods for standard HTTP methods (POST, PUT, etc) but you can also pass a custom one.
 
 ```java
 import org.asynchttpclient.*;
@@ -159,7 +180,7 @@ See `AsyncCompletionHandler` implementation as an example.
 
 The below sample just capture the response status and skips processing the response body chunks.
 
-Note that returning `ABORT` closed the underlying connection.
+Note that returning `ABORT` closes the underlying connection.
 
 ```java
 import static org.asynchttpclient.Dsl.*;
@@ -196,7 +217,7 @@ Integer statusCode = whenStatusCode.get();
 
 #### Using Continuations
 
-`ListenableFuture` has a `toCompletableFuture` that returns a `CompletableFuture`.
+`ListenableFuture` has a `toCompletableFuture` method that returns a `CompletableFuture`.
 Beware that canceling this `CompletableFuture` won't properly cancel the ongoing request.
 There's a very good chance we'll return a `CompletionStage` instead in the next release.
 
@@ -244,7 +265,7 @@ WebSocket websocket = c.prepareGet("ws://demos.kaazing.com/echo")
 
 ## Reactive Streams
 
-AsyncHttpClient has build in support for reactive streams.
+AsyncHttpClient has built-in support for reactive streams.
 
 You can pass a request body as a `Publisher<ByteBuf>` or a `ReactiveStreamsBodyGenerator`.
 
@@ -289,7 +310,7 @@ Keep up to date on the library development by joining the Asynchronous HTTP Clie
 
 Of course, Pull Requests are welcome.
 
-Here a the few rules we'd like you to respect if you do so:
+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.
diff --git a/bom/pom.xml b/bom/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b4a4438aa986f4f4bd634f709248772f6d004eaf
--- /dev/null
+++ b/bom/pom.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.asynchttpclient</groupId>
+        <artifactId>async-http-client-project</artifactId>
+        <version>2.11.0</version>
+    </parent>
+
+    <artifactId>async-http-client-bom</artifactId>
+    <packaging>pom</packaging>
+    <name>Asynchronous Http Client Bill of Materials (BOM)</name>
+    <description>Importing this BOM will provide dependency management for all AsyncHttpClient artifacts.</description>
+    <url>http://github.com/AsyncHttpClient/async-http-client/bom</url>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client-example</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client-extras-guava</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client-extras-jdeferred</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client-extras-registry</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client-extras-retrofit2</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client-extras-rxjava</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client-extras-rxjava2</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client-extras-simple</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client-extras-typesafe-config</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.asynchttpclient</groupId>
+                <artifactId>async-http-client-netty-utils</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <!-- This plugins allows using a parent for this file, so project.version can be resolved.
+                     A clean BOM without a reference to a parent is generated, so that transitive dependencies will
+                     not be included and cannot interfere with projects that use this BOM. -->
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>flatten-maven-plugin</artifactId>
+                <version>1.1.0</version>
+                <inherited>false</inherited>
+                <executions>
+                    <execution>
+                        <id>flatten</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>flatten</goal>
+                        </goals>
+                        <configuration>
+                            <flattenMode>bom</flattenMode>
+                            <pomElements>
+                                <properties>remove</properties>
+                                <dependencies>remove</dependencies>
+                                <build>remove</build>
+                            </pomElements>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/client/pom.xml b/client/pom.xml
index 6a46b6a7a2b9bd80d6e7d6639393a834f5a22265..ee341ce4ba3022fe6d8a36257a70378c98956f7a 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -2,13 +2,17 @@
   <parent>
     <groupId>org.asynchttpclient</groupId>
     <artifactId>async-http-client-project</artifactId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>async-http-client</artifactId>
   <name>Asynchronous Http Client</name>
   <description>The Async Http Client (AHC) classes.</description>
 
+  <properties>
+    <javaModuleName>org.asynchttpclient.client</javaModuleName>
+  </properties>
+
   <build>
     <plugins>
       <plugin>
@@ -53,7 +57,8 @@
     </dependency>
     <dependency>
       <groupId>io.netty</groupId>
-      <artifactId>netty-resolver-dns</artifactId>
+      <artifactId>netty-transport-native-kqueue</artifactId>
+      <classifier>osx-x86_64</classifier>
     </dependency>
     <dependency>
       <groupId>org.reactivestreams</groupId>
diff --git a/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java b/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java
index c7b60e0b4cb15bcf3341d2888a5e5374c34e42cc..d1f30c1ac35548af88db050cba8048b2ddf6bb48 100644
--- a/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java
+++ b/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java
@@ -24,7 +24,7 @@ import org.slf4j.LoggerFactory;
 /**
  * An {@link AsyncHandler} augmented with an {@link #onCompleted(Response)}
  * convenience method which gets called when the {@link Response} processing is
- * finished. This class also implement the {@link ProgressAsyncHandler}
+ * finished. This class also implements the {@link ProgressAsyncHandler}
  * callback, all doing nothing except returning
  * {@link org.asynchttpclient.AsyncHandler.State#CONTINUE}
  *
diff --git a/client/src/main/java/org/asynchttpclient/AsyncHandler.java b/client/src/main/java/org/asynchttpclient/AsyncHandler.java
index 090503cf1422747ab7cd18e236b5a5c9555e9c9b..6733c947112395c441f3b10c9403c67246eb4275 100644
--- a/client/src/main/java/org/asynchttpclient/AsyncHandler.java
+++ b/client/src/main/java/org/asynchttpclient/AsyncHandler.java
@@ -19,6 +19,7 @@ import io.netty.channel.Channel;
 import io.netty.handler.codec.http.HttpHeaders;
 import org.asynchttpclient.netty.request.NettyRequest;
 
+import javax.net.ssl.SSLSession;
 import java.net.InetSocketAddress;
 import java.util.List;
 
@@ -37,9 +38,9 @@ import java.util.List;
  * </ol>
  * <br>
  * Returning a {@link AsyncHandler.State#ABORT} from any of those callback methods will interrupt asynchronous response
- * processing, after that only {@link #onCompleted()} is going to be called.
+ * processing. After that, only {@link #onCompleted()} is going to be called.
  * <br>
- * AsyncHandler aren't thread safe, hence you should avoid re-using the same instance when doing concurrent requests.
+ * AsyncHandlers aren't thread safe. Hence, you should avoid re-using the same instance when doing concurrent requests.
  * As an example, the following may produce unexpected results:
  * <blockquote><pre>
  *   AsyncHandler ah = new AsyncHandler() {....};
@@ -49,9 +50,10 @@ import java.util.List;
  * </pre></blockquote>
  * It is recommended to create a new instance instead.
  * <p>
- * Do NOT perform any blocking operation in there, typically trying to send another request and call get() on its future.
+ * Do NOT perform any blocking operations in any of these methods. A typical example would be trying to send another
+ * request and calling get() on its future.
  * There's a chance you might end up in a dead lock.
- * If you really to perform blocking operation, executed it in a different dedicated thread pool.
+ * If you really need to perform a blocking operation, execute it in a different dedicated thread pool.
  *
  * @param <T> Type of object returned by the {@link java.util.concurrent.Future#get}
  */
@@ -142,6 +144,8 @@ public interface AsyncHandler<T> {
   default void onHostnameResolutionFailure(String name, Throwable cause) {
   }
 
+  // ////////////// TCP CONNECT ////////
+
   /**
    * Notify the callback when trying to open a new connection.
    * <p>
@@ -152,8 +156,6 @@ public interface AsyncHandler<T> {
   default void onTcpConnectAttempt(InetSocketAddress remoteAddress) {
   }
 
-  // ////////////// TCP CONNECT ////////
-
   /**
    * Notify the callback after a successful connect
    *
@@ -174,18 +176,18 @@ public interface AsyncHandler<T> {
   default void onTcpConnectFailure(InetSocketAddress remoteAddress, Throwable cause) {
   }
 
+  // ////////////// TLS ///////////////
+
   /**
    * Notify the callback before TLS handshake
    */
   default void onTlsHandshakeAttempt() {
   }
 
-  // ////////////// TLS ///////////////
-
   /**
    * Notify the callback after the TLS was successful
    */
-  default void onTlsHandshakeSuccess() {
+  default void onTlsHandshakeSuccess(SSLSession sslSession) {
   }
 
   /**
@@ -196,14 +198,14 @@ public interface AsyncHandler<T> {
   default void onTlsHandshakeFailure(Throwable cause) {
   }
 
+  // /////////// POOLING /////////////
+
   /**
    * Notify the callback when trying to fetch a connection from the pool.
    */
   default void onConnectionPoolAttempt() {
   }
 
-  // /////////// POOLING /////////////
-
   /**
    * Notify the callback when a new connection was successfully fetched from the pool.
    *
@@ -220,6 +222,8 @@ public interface AsyncHandler<T> {
   default void onConnectionOffer(Channel connection) {
   }
 
+  // //////////// SENDING //////////////
+
   /**
    * Notify the callback when a request is being written on the channel. If the original request causes multiple requests to be sent, for example, because of authorization or
    * retry, it will be notified multiple times.
@@ -229,8 +233,6 @@ public interface AsyncHandler<T> {
   default void onRequestSend(NettyRequest request) {
   }
 
-  // //////////// SENDING //////////////
-
   /**
    * Notify the callback every time a request is being retried.
    */
diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java
index 1510de4513d06eb350a77ee78b799ffec8d63d19..2ab335f3f6a199dba2ca501c69929b473ec67d5f 100755
--- a/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java
+++ b/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java
@@ -21,15 +21,15 @@ import java.util.concurrent.Future;
 import java.util.function.Predicate;
 
 /**
- * This class support asynchronous and synchronous HTTP request.
+ * This class support asynchronous and synchronous HTTP requests.
  * <br>
- * To execute synchronous HTTP request, you just need to do
+ * To execute a synchronous HTTP request, you just need to do
  * <blockquote><pre>
  *    AsyncHttpClient c = new AsyncHttpClient();
  *    Future&lt;Response&gt; f = c.prepareGet(TARGET_URL).execute();
  * </pre></blockquote>
  * <br>
- * The code above will block until the response is fully received. To execute asynchronous HTTP request, you
+ * The code above will block until the response is fully received. To execute an asynchronous HTTP request, you
  * create an {@link AsyncHandler} or its abstract implementation, {@link AsyncCompletionHandler}
  * <br>
  * <blockquote><pre>
@@ -48,7 +48,7 @@ import java.util.function.Predicate;
  *      &#125;);
  *      Response response = f.get();
  *
- *      // We are just interested to retrieve the status code.
+ *      // We are just interested in retrieving the status code.
  *     Future&lt;Integer&gt; f = c.prepareGet(TARGET_URL).execute(new AsyncCompletionHandler&lt;Integer&gt;() &#123;
  *
  *          &#64;Override
@@ -63,10 +63,10 @@ import java.util.function.Predicate;
  *      &#125;);
  *      Integer statusCode = f.get();
  * </pre></blockquote>
- * The {@link AsyncCompletionHandler#onCompleted(Response)} will be invoked once the http response has been fully read, which include
- * the http headers and the response body. Note that the entire response will be buffered in memory.
+ * The {@link AsyncCompletionHandler#onCompleted(Response)} method will be invoked once the http response has been fully read.
+ * The {@link Response} object includes the http headers and the response body. Note that the entire response will be buffered in memory.
  * <br>
- * You can also have more control about the how the response is asynchronously processed by using a {@link AsyncHandler}
+ * You can also have more control about the how the response is asynchronously processed by using an {@link AsyncHandler}
  * <blockquote><pre>
  *      AsyncHttpClient c = new AsyncHttpClient();
  *      Future&lt;String&gt; f = c.prepareGet(TARGET_URL).execute(new AsyncHandler&lt;String&gt;() &#123;
@@ -106,8 +106,8 @@ import java.util.function.Predicate;
  *
  *      String bodyResponse = f.get();
  * </pre></blockquote>
- * You can asynchronously process the response status,headers and body and decide when to
- * stop the processing the response by returning a new {@link AsyncHandler.State#ABORT} at any moment.
+ * You can asynchronously process the response status, headers and body and decide when to
+ * stop processing the response by returning a new {@link AsyncHandler.State#ABORT} at any moment.
  *
  * This class can also be used without the need of {@link AsyncHandler}.
  * <br>
@@ -125,8 +125,8 @@ import java.util.function.Predicate;
  *      Response r = f.get();
  * </pre></blockquote>
  * <br>
- * An instance of this class will cache every HTTP 1.1 connections and close them when the {@link DefaultAsyncHttpClientConfig#getReadTimeout()}
- * expires. This object can hold many persistent connections to different host.
+ * An instance of this class will cache every HTTP 1.1 connection and close them when the {@link DefaultAsyncHttpClientConfig#getReadTimeout()}
+ * expires. This object can hold many persistent connections to different hosts.
  */
 public interface AsyncHttpClient extends Closeable {
 
@@ -138,7 +138,7 @@ public interface AsyncHttpClient extends Closeable {
   boolean isClosed();
 
   /**
-   * Set default signature calculator to use for requests build by this client instance
+   * Set default signature calculator to use for requests built by this client instance
    *
    * @param signatureCalculator a signature calculator
    * @return {@link RequestBuilder}
diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java
index e0f8413662ba344ea24e0fd477c5cf9113cbfc93..391014df7bfe1989ab8e1d22a0e11701f4530224 100644
--- a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java
+++ b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java
@@ -65,6 +65,14 @@ public interface AsyncHttpClientConfig {
    */
   int getMaxConnectionsPerHost();
 
+  /**
+   * Return the maximum duration in milliseconds an {@link AsyncHttpClient} can wait to acquire a free channel
+   *
+   * @return Return the maximum duration in milliseconds an {@link AsyncHttpClient} can wait to acquire a free channel
+   */
+  int getAcquireFreeChannelTimeout();
+
+
   /**
    * Return the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host
    *
@@ -310,6 +318,8 @@ public interface AsyncHttpClientConfig {
 
   boolean isSoReuseAddress();
 
+  boolean isSoKeepAlive();
+
   int getSoLinger();
 
   int getSoSndBuf();
diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java
index 8d2c3f7ab10fb7ec9cde6e760870643bcd4a2216..7e8c21f901d38b4ada80ccdb7a708475faad2e4f 100644
--- a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java
+++ b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java
@@ -194,7 +194,7 @@ public class DefaultAsyncHttpClient implements AsyncHttpClient {
       try {
         List<Cookie> cookies = config.getCookieStore().get(request.getUri());
         if (!cookies.isEmpty()) {
-          RequestBuilder requestBuilder = new RequestBuilder(request);
+          RequestBuilder requestBuilder = request.toBuilder();
           for (Cookie cookie : cookies) {
             requestBuilder.addOrReplaceCookie(cookie);
           }
@@ -264,7 +264,7 @@ public class DefaultAsyncHttpClient implements AsyncHttpClient {
     }
 
     if (request.getRangeOffset() != 0) {
-      RequestBuilder builder = new RequestBuilder(request);
+      RequestBuilder builder = request.toBuilder();
       builder.setHeader("Range", "bytes=" + request.getRangeOffset() + "-");
       request = builder.build();
     }
diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java
index 96d95f91bd0424640c281b54034a80cda8e37e7c..f179f08a33e98ae20b25938422d5de6d6dc28144 100644
--- a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java
+++ b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java
@@ -84,6 +84,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
   private final int connectionTtl;
   private final int maxConnections;
   private final int maxConnectionsPerHost;
+  private final int acquireFreeChannelTimeout;
   private final ChannelPool channelPool;
   private final ConnectionSemaphoreFactory connectionSemaphoreFactory;
   private final KeepAliveStrategy keepAliveStrategy;
@@ -122,6 +123,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
   private final ByteBufAllocator allocator;
   private final boolean tcpNoDelay;
   private final boolean soReuseAddress;
+  private final boolean soKeepAlive;
   private final int soLinger;
   private final int soSndBuf;
   private final int soRcvBuf;
@@ -163,6 +165,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
                                        int connectionTtl,
                                        int maxConnections,
                                        int maxConnectionsPerHost,
+                                       int acquireFreeChannelTimeout,
                                        ChannelPool channelPool,
                                        ConnectionSemaphoreFactory connectionSemaphoreFactory,
                                        KeepAliveStrategy keepAliveStrategy,
@@ -191,6 +194,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
                                        // tuning
                                        boolean tcpNoDelay,
                                        boolean soReuseAddress,
+                                       boolean soKeepAlive,
                                        int soLinger,
                                        int soSndBuf,
                                        int soRcvBuf,
@@ -250,6 +254,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
     this.connectionTtl = connectionTtl;
     this.maxConnections = maxConnections;
     this.maxConnectionsPerHost = maxConnectionsPerHost;
+    this.acquireFreeChannelTimeout = acquireFreeChannelTimeout;
     this.channelPool = channelPool;
     this.connectionSemaphoreFactory = connectionSemaphoreFactory;
     this.keepAliveStrategy = keepAliveStrategy;
@@ -278,6 +283,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
     // tuning
     this.tcpNoDelay = tcpNoDelay;
     this.soReuseAddress = soReuseAddress;
+    this.soKeepAlive = soKeepAlive;
     this.soLinger = soLinger;
     this.soSndBuf = soSndBuf;
     this.soRcvBuf = soRcvBuf;
@@ -445,6 +451,9 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
     return maxConnectionsPerHost;
   }
 
+  @Override
+  public int getAcquireFreeChannelTimeout() { return acquireFreeChannelTimeout; }
+
   @Override
   public ChannelPool getChannelPool() {
     return channelPool;
@@ -554,6 +563,11 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
     return soReuseAddress;
   }
 
+  @Override
+  public boolean isSoKeepAlive() {
+    return soKeepAlive;
+  }
+
   @Override
   public int getSoLinger() {
     return soLinger;
@@ -696,6 +710,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
     private int connectionTtl = defaultConnectionTtl();
     private int maxConnections = defaultMaxConnections();
     private int maxConnectionsPerHost = defaultMaxConnectionsPerHost();
+    private int acquireFreeChannelTimeout = defaultAcquireFreeChannelTimeout();
     private ChannelPool channelPool;
     private ConnectionSemaphoreFactory connectionSemaphoreFactory;
     private KeepAliveStrategy keepAliveStrategy = new DefaultKeepAliveStrategy();
@@ -719,6 +734,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
     // tuning
     private boolean tcpNoDelay = defaultTcpNoDelay();
     private boolean soReuseAddress = defaultSoReuseAddress();
+    private boolean soKeepAlive = defaultSoKeepAlive();
     private int soLinger = defaultSoLinger();
     private int soSndBuf = defaultSoSndBuf();
     private int soRcvBuf = defaultSoRcvBuf();
@@ -801,6 +817,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
       // tuning
       tcpNoDelay = config.isTcpNoDelay();
       soReuseAddress = config.isSoReuseAddress();
+      soKeepAlive = config.isSoKeepAlive();
       soLinger = config.getSoLinger();
       soSndBuf = config.getSoSndBuf();
       soRcvBuf = config.getSoRcvBuf();
@@ -991,6 +1008,16 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
       return this;
     }
 
+    /**
+     * Sets the maximum duration in milliseconds to acquire a free channel to send a request
+     * @param acquireFreeChannelTimeout maximum duration in milliseconds to acquire a free channel to send a request
+     * @return the same builder instance
+     */
+    public Builder setAcquireFreeChannelTimeout(int acquireFreeChannelTimeout) {
+      this.acquireFreeChannelTimeout = acquireFreeChannelTimeout;
+      return this;
+    }
+
     public Builder setChannelPool(ChannelPool channelPool) {
       this.channelPool = channelPool;
       return this;
@@ -1110,6 +1137,11 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
       return this;
     }
 
+    public Builder setSoKeepAlive(boolean soKeepAlive) {
+      this.soKeepAlive = soKeepAlive;
+      return this;
+    }
+
     public Builder setSoLinger(int soLinger) {
       this.soLinger = soLinger;
       return this;
@@ -1249,6 +1281,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
               connectionTtl,
               maxConnections,
               maxConnectionsPerHost,
+              acquireFreeChannelTimeout,
               channelPool,
               connectionSemaphoreFactory,
               keepAliveStrategy,
@@ -1269,6 +1302,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
               cookieStore,
               tcpNoDelay,
               soReuseAddress,
+              soKeepAlive,
               soLinger,
               soSndBuf,
               soRcvBuf,
diff --git a/client/src/main/java/org/asynchttpclient/Request.java b/client/src/main/java/org/asynchttpclient/Request.java
index 0bcf3ae710a21edf75938b287f5398e806210ad9..cf6a82dee29591ff21ec3df16e1808d728805a48 100644
--- a/client/src/main/java/org/asynchttpclient/Request.java
+++ b/client/src/main/java/org/asynchttpclient/Request.java
@@ -180,4 +180,12 @@ public interface Request {
    * @return the NameResolver to be used to resolve hostnams's IP
    */
   NameResolver<InetAddress> getNameResolver();
+
+  /**
+   * @return a new request builder using this request as a prototype
+   */
+  @SuppressWarnings("deprecation")
+  default RequestBuilder toBuilder() {
+    return new RequestBuilder(this);
+  }
 }
diff --git a/client/src/main/java/org/asynchttpclient/RequestBuilder.java b/client/src/main/java/org/asynchttpclient/RequestBuilder.java
index ad0a141495c10ee8b4129f9ad73a43f41a56d611..4761f0c2c4b28c5275a7590ef4fbd3563efc61ff 100644
--- a/client/src/main/java/org/asynchttpclient/RequestBuilder.java
+++ b/client/src/main/java/org/asynchttpclient/RequestBuilder.java
@@ -18,7 +18,7 @@ package org.asynchttpclient;
 import static org.asynchttpclient.util.HttpConstants.Methods.GET;
 
 /**
- * Builder for a {@link Request}. Warning: mutable and not thread-safe! Beware that it holds a reference on the Request instance it builds, so modifying the builder will modify the
+ * Builder for a {@link Request}. Warning: mutable and not thread-safe! Beware that it holds a reference to the Request instance it builds, so modifying the builder will modify the
  * request even after it has been built.
  */
 public class RequestBuilder extends RequestBuilderBase<RequestBuilder> {
@@ -39,10 +39,15 @@ public class RequestBuilder extends RequestBuilderBase<RequestBuilder> {
     super(method, disableUrlEncoding, validateHeaders);
   }
 
+  /**
+   * @deprecated Use request.toBuilder() instead
+   */
+  @Deprecated
   public RequestBuilder(Request prototype) {
     super(prototype);
   }
 
+  @Deprecated
   public RequestBuilder(Request prototype, boolean disableUrlEncoding, boolean validateHeaders) {
     super(prototype, disableUrlEncoding, validateHeaders);
   }
diff --git a/client/src/main/java/org/asynchttpclient/Response.java b/client/src/main/java/org/asynchttpclient/Response.java
index 51fa5ac30ae5ef3202f24b59f5bed1c00b693f5e..99f033e995345d108a6557c16c6cf858cd1a7335 100644
--- a/client/src/main/java/org/asynchttpclient/Response.java
+++ b/client/src/main/java/org/asynchttpclient/Response.java
@@ -160,16 +160,16 @@ public interface Response {
   boolean hasResponseBody();
 
   /**
-   * Get remote address client initiated request to.
+   * Get the remote address that the client initiated the request to.
    *
-   * @return remote address client initiated request to, may be {@code null} if asynchronous provider is unable to provide the remote address
+   * @return The remote address that the client initiated the request to. May be {@code null} if asynchronous provider is unable to provide the remote address
    */
   SocketAddress getRemoteAddress();
 
   /**
-   * Get local address client initiated request from.
+   * Get the local address that the client initiated the request from.
    *
-   * @return local address client initiated request from, may be {@code null} if asynchronous provider is unable to provide the local address
+   * @return The local address that the client initiated the request from. May be {@code null} if asynchronous provider is unable to provide the local address
    */
   SocketAddress getLocalAddress();
 
diff --git a/client/src/main/java/org/asynchttpclient/SslEngineFactory.java b/client/src/main/java/org/asynchttpclient/SslEngineFactory.java
index 1157e499f38ce61abb979766b036050a1b107cc0..008f1c7ee845906fe500dab676b3b345f012e5df 100644
--- a/client/src/main/java/org/asynchttpclient/SslEngineFactory.java
+++ b/client/src/main/java/org/asynchttpclient/SslEngineFactory.java
@@ -19,7 +19,7 @@ import javax.net.ssl.SSLException;
 public interface SslEngineFactory {
 
   /**
-   * Creates new {@link SSLEngine}.
+   * Creates a new {@link SSLEngine}.
    *
    * @param config   the client config
    * @param peerHost the peer hostname
@@ -39,4 +39,12 @@ public interface SslEngineFactory {
   default void init(AsyncHttpClientConfig config) throws SSLException {
     // no op
   }
+
+  /**
+   * Perform any necessary cleanup.
+   */
+  default void destroy() {
+    // no op
+  }
+
 }
diff --git a/client/src/main/java/org/asynchttpclient/channel/DefaultKeepAliveStrategy.java b/client/src/main/java/org/asynchttpclient/channel/DefaultKeepAliveStrategy.java
index b9fb306cf3d0c8c1fdd790e7d9738568754d5a9f..f1c6a5f42f971867a6cd1059b39c560dce9bad0b 100644
--- a/client/src/main/java/org/asynchttpclient/channel/DefaultKeepAliveStrategy.java
+++ b/client/src/main/java/org/asynchttpclient/channel/DefaultKeepAliveStrategy.java
@@ -5,6 +5,8 @@ import io.netty.handler.codec.http.HttpResponse;
 import io.netty.handler.codec.http.HttpUtil;
 import org.asynchttpclient.Request;
 
+import java.net.InetSocketAddress;
+
 import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
 
 /**
@@ -16,7 +18,7 @@ public class DefaultKeepAliveStrategy implements KeepAliveStrategy {
    * Implemented in accordance with RFC 7230 section 6.1 https://tools.ietf.org/html/rfc7230#section-6.1
    */
   @Override
-  public boolean keepAlive(Request ahcRequest, HttpRequest request, HttpResponse response) {
+  public boolean keepAlive(InetSocketAddress remoteAddress, Request ahcRequest, HttpRequest request, HttpResponse response) {
     return HttpUtil.isKeepAlive(response)
             && HttpUtil.isKeepAlive(request)
             // support non standard Proxy-Connection
diff --git a/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java b/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java
index 4d619f222c1045a9c740e3f424f8fe1da75cef9d..c748fe76ac39d0c4b2268df2131bd0315229ad8f 100644
--- a/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java
+++ b/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java
@@ -17,15 +17,18 @@ import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.HttpResponse;
 import org.asynchttpclient.Request;
 
+import java.net.InetSocketAddress;
+
 public interface KeepAliveStrategy {
 
   /**
    * Determines whether the connection should be kept alive after this HTTP message exchange.
    *
-   * @param ahcRequest    the Request, as built by AHC
-   * @param nettyRequest  the HTTP request sent to Netty
-   * @param nettyResponse the HTTP response received from Netty
+   * @param remoteAddress  the remote InetSocketAddress associated with the request
+   * @param ahcRequest     the Request, as built by AHC
+   * @param nettyRequest   the HTTP request sent to Netty
+   * @param nettyResponse  the HTTP response received from Netty
    * @return true if the connection should be kept alive, false if it should be closed.
    */
-  boolean keepAlive(Request ahcRequest, HttpRequest nettyRequest, HttpResponse nettyResponse);
+  boolean keepAlive(InetSocketAddress remoteAddress, Request ahcRequest, HttpRequest nettyRequest, HttpResponse nettyResponse);
 }
diff --git a/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java
index 274537a6ad4159dd470a5935b8ccf512200bb047..c9e85ca491d18ef96f4aa08a3b148ef1a6551475 100644
--- a/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java
+++ b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java
@@ -22,6 +22,7 @@ public final class AsyncHttpClientConfigDefaults {
   public static final String THREAD_POOL_NAME_CONFIG = "threadPoolName";
   public static final String MAX_CONNECTIONS_CONFIG = "maxConnections";
   public static final String MAX_CONNECTIONS_PER_HOST_CONFIG = "maxConnectionsPerHost";
+  public static final String ACQUIRE_FREE_CHANNEL_TIMEOUT = "acquireFreeChannelTimeout";
   public static final String CONNECTION_TIMEOUT_CONFIG = "connectTimeout";
   public static final String POOLED_CONNECTION_IDLE_TIMEOUT_CONFIG = "pooledConnectionIdleTimeout";
   public static final String CONNECTION_POOL_CLEANER_PERIOD_CONFIG = "connectionPoolCleanerPeriod";
@@ -39,7 +40,7 @@ public final class AsyncHttpClientConfigDefaults {
   public static final String USE_PROXY_PROPERTIES_CONFIG = "useProxyProperties";
   public static final String VALIDATE_RESPONSE_HEADERS_CONFIG = "validateResponseHeaders";
   public static final String AGGREGATE_WEBSOCKET_FRAME_FRAGMENTS_CONFIG = "aggregateWebSocketFrameFragments";
-  public static final String ENABLE_WEBSOCKET_COMPRESSION_CONFIG= "enableWebSocketCompression";
+  public static final String ENABLE_WEBSOCKET_COMPRESSION_CONFIG = "enableWebSocketCompression";
   public static final String STRICT_302_HANDLING_CONFIG = "strict302Handling";
   public static final String KEEP_ALIVE_CONFIG = "keepAlive";
   public static final String MAX_REQUEST_RETRY_CONFIG = "maxRequestRetry";
@@ -52,6 +53,7 @@ public final class AsyncHttpClientConfigDefaults {
   public static final String SSL_SESSION_TIMEOUT_CONFIG = "sslSessionTimeout";
   public static final String TCP_NO_DELAY_CONFIG = "tcpNoDelay";
   public static final String SO_REUSE_ADDRESS_CONFIG = "soReuseAddress";
+  public static final String SO_KEEP_ALIVE_CONFIG = "soKeepAlive";
   public static final String SO_LINGER_CONFIG = "soLinger";
   public static final String SO_SND_BUF_CONFIG = "soSndBuf";
   public static final String SO_RCV_BUF_CONFIG = "soRcvBuf";
@@ -97,6 +99,10 @@ public final class AsyncHttpClientConfigDefaults {
     return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + MAX_CONNECTIONS_PER_HOST_CONFIG);
   }
 
+  public static int defaultAcquireFreeChannelTimeout() {
+    return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + ACQUIRE_FREE_CHANNEL_TIMEOUT);
+  }
+
   public static int defaultConnectTimeout() {
     return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + CONNECTION_TIMEOUT_CONFIG);
   }
@@ -217,6 +223,10 @@ public final class AsyncHttpClientConfigDefaults {
     return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + SO_REUSE_ADDRESS_CONFIG);
   }
 
+  public static boolean defaultSoKeepAlive() {
+    return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + SO_KEEP_ALIVE_CONFIG);
+  }
+
   public static int defaultSoLinger() {
     return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + SO_LINGER_CONFIG);
   }
diff --git a/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java
index 399638fbb2b9105133d03604c4628968e5f42c3b..d6b671a270eeb7a40142c65befafa495f91b7e3f 100644
--- a/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java
+++ b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java
@@ -198,7 +198,7 @@ public class ResumableAsyncHandler implements AsyncHandler<Response> {
       byteTransferred.set(resumableListener.length());
     }
 
-    RequestBuilder builder = new RequestBuilder(request);
+    RequestBuilder builder = request.toBuilder();
     if (request.getHeaders().get(RANGE) == null && byteTransferred.get() != 0) {
       builder.setHeader(RANGE, "bytes=" + byteTransferred.get() + "-");
     }
diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java
index cb3b05fbd85f49edd9aef9355d88dd5168d811c6..9f84ada7ab7a03c58ec0d6c378e6e1b81aba4d7e 100755
--- a/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java
+++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java
@@ -90,7 +90,6 @@ public final class NettyResponseFuture<V> implements ListenableFuture<V> {
   private volatile int isCancelled = 0;
   private volatile int inAuth = 0;
   private volatile int inProxyAuth = 0;
-  private volatile int statusReceived = 0;
   @SuppressWarnings("unused")
   private volatile int contentProcessed = 0;
   @SuppressWarnings("unused")
@@ -539,7 +538,6 @@ public final class NettyResponseFuture<V> implements ListenableFuture<V> {
             ",\n\tredirectCount=" + redirectCount + //
             ",\n\ttimeoutsHolder=" + TIMEOUTS_HOLDER_FIELD.get(this) + //
             ",\n\tinAuth=" + inAuth + //
-            ",\n\tstatusReceived=" + statusReceived + //
             ",\n\ttouch=" + touch + //
             '}';
   }
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 6a5ed0597219f3318604593298319c34228d233c..cf14d3a10141657a0c53f2d85fe0b536da26ff81 100755
--- a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java
@@ -16,10 +16,12 @@ package org.asynchttpclient.netty.channel;
 import io.netty.bootstrap.Bootstrap;
 import io.netty.buffer.ByteBufAllocator;
 import io.netty.channel.*;
+import io.netty.channel.epoll.EpollEventLoopGroup;
 import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.ChannelGroupFuture;
 import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.channel.kqueue.KQueueEventLoopGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.oio.OioEventLoopGroup;
 import io.netty.handler.codec.http.HttpClientCodec;
 import io.netty.handler.codec.http.HttpContentDecompressor;
 import io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder;
@@ -36,6 +38,7 @@ import io.netty.handler.stream.ChunkedWriteHandler;
 import io.netty.resolver.NameResolver;
 import io.netty.util.Timer;
 import io.netty.util.concurrent.*;
+import io.netty.util.internal.PlatformDependent;
 import org.asynchttpclient.*;
 import org.asynchttpclient.channel.ChannelPool;
 import org.asynchttpclient.channel.ChannelPoolPartitioning;
@@ -119,31 +122,31 @@ public class ChannelManager {
     // check if external EventLoopGroup is defined
     ThreadFactory threadFactory = config.getThreadFactory() != null ? config.getThreadFactory() : new DefaultThreadFactory(config.getThreadPoolName());
     allowReleaseEventLoopGroup = config.getEventLoopGroup() == null;
-    ChannelFactory<? extends Channel> channelFactory;
+    TransportFactory<? extends Channel, ? extends EventLoopGroup> transportFactory;
     if (allowReleaseEventLoopGroup) {
       if (config.isUseNativeTransport()) {
-        eventLoopGroup = newEpollEventLoopGroup(config.getIoThreadsCount(), threadFactory);
-        channelFactory = getEpollSocketChannelFactory();
-
+        transportFactory = getNativeTransportFactory();
       } else {
-        eventLoopGroup = new NioEventLoopGroup(config.getIoThreadsCount(), threadFactory);
-        channelFactory = NioSocketChannelFactory.INSTANCE;
+        transportFactory = NioTransportFactory.INSTANCE;
       }
+      eventLoopGroup = transportFactory.newEventLoopGroup(config.getIoThreadsCount(), threadFactory);
 
     } else {
       eventLoopGroup = config.getEventLoopGroup();
-      if (eventLoopGroup instanceof OioEventLoopGroup)
-        throw new IllegalArgumentException("Oio is not supported");
 
       if (eventLoopGroup instanceof NioEventLoopGroup) {
-        channelFactory = NioSocketChannelFactory.INSTANCE;
+        transportFactory = NioTransportFactory.INSTANCE;
+      } else if (eventLoopGroup instanceof EpollEventLoopGroup) {
+        transportFactory = new EpollTransportFactory();
+      } else if (eventLoopGroup instanceof KQueueEventLoopGroup) {
+        transportFactory = new KQueueTransportFactory();
       } else {
-        channelFactory = getEpollSocketChannelFactory();
+        throw new IllegalArgumentException("Unknown event loop group " + eventLoopGroup.getClass().getSimpleName());
       }
     }
 
-    httpBootstrap = newBootstrap(channelFactory, eventLoopGroup, config);
-    wsBootstrap = newBootstrap(channelFactory, eventLoopGroup, config);
+    httpBootstrap = newBootstrap(transportFactory, eventLoopGroup, config);
+    wsBootstrap = newBootstrap(transportFactory, eventLoopGroup, config);
 
     // for reactive streams
     httpBootstrap.option(ChannelOption.AUTO_READ, false);
@@ -159,6 +162,7 @@ public class ChannelManager {
             .option(ChannelOption.ALLOCATOR, config.getAllocator() != null ? config.getAllocator() : ByteBufAllocator.DEFAULT)
             .option(ChannelOption.TCP_NODELAY, config.isTcpNoDelay())
             .option(ChannelOption.SO_REUSEADDR, config.isSoReuseAddress())
+            .option(ChannelOption.SO_KEEPALIVE, config.isSoKeepAlive())
             .option(ChannelOption.AUTO_CLOSE, false);
 
     if (config.getConnectTimeout() > 0) {
@@ -184,22 +188,22 @@ public class ChannelManager {
     return bootstrap;
   }
 
-  private EventLoopGroup newEpollEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory) {
-    try {
-      Class<?> epollEventLoopGroupClass = Class.forName("io.netty.channel.epoll.EpollEventLoopGroup");
-      return (EventLoopGroup) epollEventLoopGroupClass.getConstructor(int.class, ThreadFactory.class).newInstance(ioThreadsCount, threadFactory);
-    } catch (Exception e) {
-      throw new IllegalArgumentException(e);
+  @SuppressWarnings("unchecked")
+  private TransportFactory<? extends Channel, ? extends EventLoopGroup> getNativeTransportFactory() {
+    String nativeTransportFactoryClassName = null;
+    if (PlatformDependent.isOsx()) {
+      nativeTransportFactoryClassName = "org.asynchttpclient.netty.channel.KQueueTransportFactory";
+    } else if (!PlatformDependent.isWindows()) {
+      nativeTransportFactoryClassName = "org.asynchttpclient.netty.channel.EpollTransportFactory";
     }
-  }
 
-  @SuppressWarnings("unchecked")
-  private ChannelFactory<? extends Channel> getEpollSocketChannelFactory() {
     try {
-      return (ChannelFactory<? extends Channel>) Class.forName("org.asynchttpclient.netty.channel.EpollSocketChannelFactory").newInstance();
+      if (nativeTransportFactoryClassName != null) {
+        return (TransportFactory<? extends Channel, ? extends EventLoopGroup>) Class.forName(nativeTransportFactoryClassName).newInstance();
+      }
     } catch (Exception e) {
-      throw new IllegalArgumentException(e);
     }
+    throw new IllegalArgumentException("No suitable native transport (epoll or kqueue) available");
   }
 
   public void configureBootstraps(NettyRequestSender requestSender) {
@@ -291,8 +295,9 @@ public class ChannelManager {
   }
 
   private void doClose() {
-    openChannels.close();
+    ChannelGroupFuture groupFuture = openChannels.close();
     channelPool.destroy();
+    groupFuture.addListener(future -> sslEngineFactory.destroy());
   }
 
   public void close() {
@@ -355,6 +360,11 @@ public class ChannelManager {
 
     if (requestUri.isWebSocket()) {
       pipeline.addAfter(AHC_HTTP_HANDLER, AHC_WS_HANDLER, wsHandler);
+
+      if (config.isEnableWebSocketCompression()) {
+        pipeline.addBefore(AHC_WS_HANDLER, WS_COMPRESSOR_HANDLER, WebSocketClientCompressionHandler.INSTANCE);
+      }
+
       pipeline.remove(AHC_HTTP_HANDLER);
     }
     return whenHanshaked;
@@ -481,7 +491,7 @@ public class ChannelManager {
 
   public ClientStats getClientStats() {
     Map<String, Long> totalConnectionsPerHost = openChannels.stream().map(Channel::remoteAddress).filter(a -> a.getClass() == InetSocketAddress.class)
-            .map(a -> (InetSocketAddress) a).map(InetSocketAddress::getHostName).collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
+            .map(a -> (InetSocketAddress) a).map(InetSocketAddress::getHostString).collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
     Map<String, Long> idleConnectionsPerHost = channelPool.getIdleChannelCountPerHost();
     Map<String, HostStats> statsPerHost = totalConnectionsPerHost.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> {
       final long totalConnectionCount = entry.getValue();
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/CombinedConnectionSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/CombinedConnectionSemaphore.java
new file mode 100644
index 0000000000000000000000000000000000000000..04549fd80d449bb1a0ad92024ce0d31823be3787
--- /dev/null
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/CombinedConnectionSemaphore.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018 AsyncHttpClient Project. 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.netty.channel;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A combined {@link ConnectionSemaphore} with two limits - a global limit and a per-host limit
+ */
+public class CombinedConnectionSemaphore extends PerHostConnectionSemaphore {
+  protected final MaxConnectionSemaphore globalMaxConnectionSemaphore;
+
+  CombinedConnectionSemaphore(int maxConnections, int maxConnectionsPerHost, int acquireTimeout) {
+    super(maxConnectionsPerHost, acquireTimeout);
+    this.globalMaxConnectionSemaphore = new MaxConnectionSemaphore(maxConnections, acquireTimeout);
+  }
+
+  @Override
+  public void acquireChannelLock(Object partitionKey) throws IOException {
+    long remainingTime = super.acquireTimeout > 0 ? acquireGlobalTimed(partitionKey) : acquireGlobal(partitionKey);
+
+    try {
+      if (remainingTime < 0 || !getFreeConnectionsForHost(partitionKey).tryAcquire(remainingTime, TimeUnit.MILLISECONDS)) {
+        releaseGlobal(partitionKey);
+        throw tooManyConnectionsPerHost;
+      }
+    } catch (InterruptedException e) {
+      releaseGlobal(partitionKey);
+      throw new RuntimeException(e);
+    }
+  }
+
+  protected void releaseGlobal(Object partitionKey) {
+    this.globalMaxConnectionSemaphore.releaseChannelLock(partitionKey);
+  }
+
+  protected long acquireGlobal(Object partitionKey) throws IOException {
+    this.globalMaxConnectionSemaphore.acquireChannelLock(partitionKey);
+    return 0;
+  }
+
+  /*
+   * Acquires the global lock and returns the remaining time, in millis, to acquire the per-host lock
+   */
+  protected long acquireGlobalTimed(Object partitionKey) throws IOException {
+    long beforeGlobalAcquire = System.currentTimeMillis();
+    acquireGlobal(partitionKey);
+    long lockTime = System.currentTimeMillis() - beforeGlobalAcquire;
+    return this.acquireTimeout - lockTime;
+  }
+
+  @Override
+  public void releaseChannelLock(Object partitionKey) {
+    this.globalMaxConnectionSemaphore.releaseChannelLock(partitionKey);
+    super.releaseChannelLock(partitionKey);
+  }
+}
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 9988421595afba300f105c61a01ec920f63d550c..f9c08b973b2fc8b92f92be35d8ad77c2f72b4200 100755
--- a/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java
@@ -222,7 +222,7 @@ public final class DefaultChannelPool implements ChannelPool {
             .map(idle -> idle.getChannel().remoteAddress())
             .filter(a -> a.getClass() == InetSocketAddress.class)
             .map(a -> (InetSocketAddress) a)
-            .map(InetSocketAddress::getHostName)
+            .map(InetSocketAddress::getHostString)
             .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
   }
 
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/DefaultConnectionSemaphoreFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultConnectionSemaphoreFactory.java
index a102f1def8c38df4998d9c646abf3dc5bda179cf..eba42186eef91245712bc6267c58cc5ccde98fa5 100644
--- a/client/src/main/java/org/asynchttpclient/netty/channel/DefaultConnectionSemaphoreFactory.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultConnectionSemaphoreFactory.java
@@ -17,14 +17,21 @@ import org.asynchttpclient.AsyncHttpClientConfig;
 
 public class DefaultConnectionSemaphoreFactory implements ConnectionSemaphoreFactory {
 
-    public ConnectionSemaphore newConnectionSemaphore(AsyncHttpClientConfig config) {
-        ConnectionSemaphore semaphore = new NoopConnectionSemaphore();
-        if (config.getMaxConnections() > 0) {
-            semaphore = new MaxConnectionSemaphore(config.getMaxConnections());
-        }
-        if (config.getMaxConnectionsPerHost() > 0) {
-            semaphore = new PerHostConnectionSemaphore(config.getMaxConnectionsPerHost(), semaphore);
-        }
-        return semaphore;
+  public ConnectionSemaphore newConnectionSemaphore(AsyncHttpClientConfig config) {
+    int acquireFreeChannelTimeout = Math.max(0, config.getAcquireFreeChannelTimeout());
+    int maxConnections = config.getMaxConnections();
+    int maxConnectionsPerHost = config.getMaxConnectionsPerHost();
+
+    if (maxConnections > 0 && maxConnectionsPerHost > 0) {
+      return new CombinedConnectionSemaphore(maxConnections, maxConnectionsPerHost, acquireFreeChannelTimeout);
+    }
+    if (maxConnections > 0) {
+      return new MaxConnectionSemaphore(maxConnections, acquireFreeChannelTimeout);
     }
+    if (maxConnectionsPerHost > 0) {
+      return new CombinedConnectionSemaphore(maxConnections, maxConnectionsPerHost, acquireFreeChannelTimeout);
+    }
+
+    return new NoopConnectionSemaphore();
+  }
 }
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/EpollSocketChannelFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/EpollTransportFactory.java
similarity index 54%
rename from client/src/main/java/org/asynchttpclient/netty/channel/EpollSocketChannelFactory.java
rename to client/src/main/java/org/asynchttpclient/netty/channel/EpollTransportFactory.java
index c6970b6d6c07caf25b31d9c0bb4b540a70c613a4..8f84272916400d0960cfa8b1ceb194bac504477d 100644
--- a/client/src/main/java/org/asynchttpclient/netty/channel/EpollSocketChannelFactory.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/EpollTransportFactory.java
@@ -13,13 +13,32 @@
  */
 package org.asynchttpclient.netty.channel;
 
-import io.netty.channel.ChannelFactory;
+import io.netty.channel.epoll.Epoll;
+import io.netty.channel.epoll.EpollEventLoopGroup;
 import io.netty.channel.epoll.EpollSocketChannel;
 
-class EpollSocketChannelFactory implements ChannelFactory<EpollSocketChannel> {
+import java.util.concurrent.ThreadFactory;
+
+class EpollTransportFactory implements TransportFactory<EpollSocketChannel, EpollEventLoopGroup> {
+
+  EpollTransportFactory() {
+    try {
+      Class.forName("io.netty.channel.epoll.Epoll");
+    } catch (ClassNotFoundException e) {
+      throw new IllegalStateException("The epoll transport is not available");
+    }
+    if (!Epoll.isAvailable()) {
+      throw new IllegalStateException("The epoll transport is not supported");
+    }
+  }
 
   @Override
   public EpollSocketChannel newChannel() {
     return new EpollSocketChannel();
   }
+
+  @Override
+  public EpollEventLoopGroup newEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory) {
+    return new EpollEventLoopGroup(ioThreadsCount, threadFactory);
+  }
 }
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/InfiniteSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/InfiniteSemaphore.java
new file mode 100644
index 0000000000000000000000000000000000000000..97b822473964e029548435dfce14bc0f3f4f13db
--- /dev/null
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/InfiniteSemaphore.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2018 AsyncHttpClient Project. 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.netty.channel;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A java.util.concurrent.Semaphore that always has Integer.Integer.MAX_VALUE free permits
+ *
+ * @author Alex Maltinsky
+ */
+public class InfiniteSemaphore extends Semaphore {
+
+  public static final InfiniteSemaphore INSTANCE = new InfiniteSemaphore();
+  private static final long serialVersionUID = 1L;
+
+  private InfiniteSemaphore() {
+    super(Integer.MAX_VALUE);
+  }
+
+  @Override
+  public void acquire() {
+    // NO-OP
+  }
+
+  @Override
+  public void acquireUninterruptibly() {
+    // NO-OP
+  }
+
+  @Override
+  public boolean tryAcquire() {
+    return true;
+  }
+
+  @Override
+  public boolean tryAcquire(long timeout, TimeUnit unit) {
+    return true;
+  }
+
+  @Override
+  public void release() {
+    // NO-OP
+  }
+
+  @Override
+  public void acquire(int permits) {
+    // NO-OP
+  }
+
+  @Override
+  public void acquireUninterruptibly(int permits) {
+    // NO-OP
+  }
+
+  @Override
+  public boolean tryAcquire(int permits) {
+    return true;
+  }
+
+  @Override
+  public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
+    return true;
+  }
+
+  @Override
+  public void release(int permits) {
+    // NO-OP
+  }
+
+  @Override
+  public int availablePermits() {
+    return Integer.MAX_VALUE;
+  }
+
+  @Override
+  public int drainPermits() {
+    return Integer.MAX_VALUE;
+  }
+
+  @Override
+  protected void reducePermits(int reduction) {
+    // NO-OP
+  }
+
+  @Override
+  public boolean isFair() {
+    return true;
+  }
+
+  @Override
+  protected Collection<Thread> getQueuedThreads() {
+    return Collections.emptyList();
+  }
+}
+
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/KQueueTransportFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/KQueueTransportFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..f54fe46157e30369c3e53a049278d997031d7c8b
--- /dev/null
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/KQueueTransportFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2019 AsyncHttpClient Project. 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.netty.channel;
+
+import io.netty.channel.kqueue.KQueue;
+import io.netty.channel.kqueue.KQueueEventLoopGroup;
+import io.netty.channel.kqueue.KQueueSocketChannel;
+
+import java.util.concurrent.ThreadFactory;
+
+class KQueueTransportFactory implements TransportFactory<KQueueSocketChannel, KQueueEventLoopGroup> {
+
+  KQueueTransportFactory() {
+    try {
+      Class.forName("io.netty.channel.kqueue.KQueue");
+    } catch (ClassNotFoundException e) {
+      throw new IllegalStateException("The kqueue transport is not available");
+    }
+    if (!KQueue.isAvailable()) {
+      throw new IllegalStateException("The kqueue transport is not supported");
+    }
+  }
+
+  @Override
+  public KQueueSocketChannel newChannel() {
+    return new KQueueSocketChannel();
+  }
+
+  @Override
+  public KQueueEventLoopGroup newEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory) {
+    return new KQueueEventLoopGroup(ioThreadsCount, threadFactory);
+  }
+}
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/MaxConnectionSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/MaxConnectionSemaphore.java
index 99bd6a4be42e441560a27b92704fd4988c434893..99c318afac194cd013a6c710bb2470f3ddfca409 100644
--- a/client/src/main/java/org/asynchttpclient/netty/channel/MaxConnectionSemaphore.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/MaxConnectionSemaphore.java
@@ -16,6 +16,8 @@ package org.asynchttpclient.netty.channel;
 import org.asynchttpclient.exception.TooManyConnectionsException;
 
 import java.io.IOException;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 
 import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace;
 
@@ -23,21 +25,29 @@ import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace;
  * Max connections limiter.
  *
  * @author Stepan Koltsov
+ * @author Alex Maltinsky
  */
 public class MaxConnectionSemaphore implements ConnectionSemaphore {
 
-  private final NonBlockingSemaphoreLike freeChannels;
-  private final IOException tooManyConnections;
+  protected final Semaphore freeChannels;
+  protected final IOException tooManyConnections;
+  protected final int acquireTimeout;
 
-  MaxConnectionSemaphore(int maxConnections) {
+  MaxConnectionSemaphore(int maxConnections, int acquireTimeout) {
     tooManyConnections = unknownStackTrace(new TooManyConnectionsException(maxConnections), MaxConnectionSemaphore.class, "acquireChannelLock");
-    freeChannels = maxConnections > 0 ? new NonBlockingSemaphore(maxConnections) : NonBlockingSemaphoreInfinite.INSTANCE;
+    freeChannels = maxConnections > 0 ? new Semaphore(maxConnections) : InfiniteSemaphore.INSTANCE;
+    this.acquireTimeout = Math.max(0, acquireTimeout);
   }
 
   @Override
   public void acquireChannelLock(Object partitionKey) throws IOException {
-    if (!freeChannels.tryAcquire())
-      throw tooManyConnections;
+    try {
+      if (!freeChannels.tryAcquire(acquireTimeout, TimeUnit.MILLISECONDS)) {
+        throw tooManyConnections;
+      }
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
   }
 
   @Override
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 76bd652a44dd286cf6eb98633ca7af6e86cc56ef..4a6f4dce20d1381398d4f787bc4a388a50de4eee 100755
--- a/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java
@@ -130,7 +130,7 @@ public final class NettyConnectListener<T> {
         @Override
         protected void onSuccess(Channel value) {
           try {
-            asyncHandler.onTlsHandshakeSuccess();
+            asyncHandler.onTlsHandshakeSuccess(sslHandler.engine().getSession());
           } catch (Exception e) {
             LOGGER.error("onTlsHandshakeSuccess crashed", e);
             NettyConnectListener.this.onFailure(channel, e);
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/NonBlockingSemaphoreInfinite.java b/client/src/main/java/org/asynchttpclient/netty/channel/NioTransportFactory.java
similarity index 57%
rename from client/src/main/java/org/asynchttpclient/netty/channel/NonBlockingSemaphoreInfinite.java
rename to client/src/main/java/org/asynchttpclient/netty/channel/NioTransportFactory.java
index 3d4fb91dbd46e4e314d9ea578db16a7761261e6b..d691ff270a15cf87291e79d46a363ce6cfd4ffd8 100644
--- a/client/src/main/java/org/asynchttpclient/netty/channel/NonBlockingSemaphoreInfinite.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/NioTransportFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 AsyncHttpClient Project. All rights reserved.
+ * Copyright (c) 2019 AsyncHttpClient Project. 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.
@@ -13,27 +13,22 @@
  */
 package org.asynchttpclient.netty.channel;
 
-/**
- * Non-blocking semaphore-like object with infinite permits.
- * <p>
- * So try-acquire always succeeds.
- *
- * @author Stepan Koltsov
- */
-enum NonBlockingSemaphoreInfinite implements NonBlockingSemaphoreLike {
-  INSTANCE;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
 
-  @Override
-  public void release() {
-  }
+import java.util.concurrent.ThreadFactory;
+
+enum NioTransportFactory implements TransportFactory<NioSocketChannel, NioEventLoopGroup> {
+
+  INSTANCE;
 
   @Override
-  public boolean tryAcquire() {
-    return true;
+  public NioSocketChannel newChannel() {
+    return new NioSocketChannel();
   }
 
   @Override
-  public String toString() {
-    return NonBlockingSemaphore.class.getName();
+  public NioEventLoopGroup newEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory) {
+    return new NioEventLoopGroup(ioThreadsCount, threadFactory);
   }
 }
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/NonBlockingSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/NonBlockingSemaphore.java
deleted file mode 100644
index a7bd2eacfe936db6a47267ba59f2e378c448b31f..0000000000000000000000000000000000000000
--- a/client/src/main/java/org/asynchttpclient/netty/channel/NonBlockingSemaphore.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2017 AsyncHttpClient Project. 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.netty.channel;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Semaphore-like API, but without blocking.
- *
- * @author Stepan Koltsov
- */
-class NonBlockingSemaphore implements NonBlockingSemaphoreLike {
-
-  private final AtomicInteger permits;
-
-  NonBlockingSemaphore(int permits) {
-    this.permits = new AtomicInteger(permits);
-  }
-
-  @Override
-  public void release() {
-    permits.incrementAndGet();
-  }
-
-  @Override
-  public boolean tryAcquire() {
-    for (; ; ) {
-      int count = permits.get();
-      if (count <= 0) {
-        return false;
-      }
-      if (permits.compareAndSet(count, count - 1)) {
-        return true;
-      }
-    }
-  }
-
-  @Override
-  public String toString() {
-    // mimic toString of Semaphore class
-    return super.toString() + "[Permits = " + permits + "]";
-  }
-}
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/NonBlockingSemaphoreLike.java b/client/src/main/java/org/asynchttpclient/netty/channel/NonBlockingSemaphoreLike.java
deleted file mode 100644
index 44303c9dfc799d815ba70dd078010d8390c91483..0000000000000000000000000000000000000000
--- a/client/src/main/java/org/asynchttpclient/netty/channel/NonBlockingSemaphoreLike.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (c) 2017 AsyncHttpClient Project. 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.netty.channel;
-
-/**
- * Non-blocking semaphore API.
- *
- * @author Stepan Koltsov
- */
-interface NonBlockingSemaphoreLike {
-  void release();
-
-  boolean tryAcquire();
-}
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/PerHostConnectionSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/PerHostConnectionSemaphore.java
index 5ebb348abf2e569bf7b6c49fe909ca8a6d9330d4..9ce1f20e937164c8470f5900ff1220e456ef1b1b 100644
--- a/client/src/main/java/org/asynchttpclient/netty/channel/PerHostConnectionSemaphore.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/PerHostConnectionSemaphore.java
@@ -17,6 +17,8 @@ import org.asynchttpclient.exception.TooManyConnectionsPerHostException;
 
 import java.io.IOException;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 
 import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace;
 
@@ -25,37 +27,36 @@ import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace;
  */
 public class PerHostConnectionSemaphore implements ConnectionSemaphore {
 
-  private final ConnectionSemaphore globalSemaphore;
+  protected final ConcurrentHashMap<Object, Semaphore> freeChannelsPerHost = new ConcurrentHashMap<>();
+  protected final int maxConnectionsPerHost;
+  protected final IOException tooManyConnectionsPerHost;
+  protected final int acquireTimeout;
 
-  private final ConcurrentHashMap<Object, NonBlockingSemaphore> freeChannelsPerHost = new ConcurrentHashMap<>();
-  private final int maxConnectionsPerHost;
-  private final IOException tooManyConnectionsPerHost;
-
-  PerHostConnectionSemaphore(int maxConnectionsPerHost, ConnectionSemaphore globalSemaphore) {
-    this.globalSemaphore = globalSemaphore;
+  PerHostConnectionSemaphore(int maxConnectionsPerHost, int acquireTimeout) {
     tooManyConnectionsPerHost = unknownStackTrace(new TooManyConnectionsPerHostException(maxConnectionsPerHost), PerHostConnectionSemaphore.class, "acquireChannelLock");
     this.maxConnectionsPerHost = maxConnectionsPerHost;
+    this.acquireTimeout = Math.max(0, acquireTimeout);
   }
 
   @Override
   public void acquireChannelLock(Object partitionKey) throws IOException {
-    globalSemaphore.acquireChannelLock(partitionKey);
-
-    if (!getFreeConnectionsForHost(partitionKey).tryAcquire()) {
-      globalSemaphore.releaseChannelLock(partitionKey);
-      throw tooManyConnectionsPerHost;
+    try {
+      if (!getFreeConnectionsForHost(partitionKey).tryAcquire(acquireTimeout, TimeUnit.MILLISECONDS)) {
+        throw tooManyConnectionsPerHost;
+      }
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
     }
   }
 
   @Override
   public void releaseChannelLock(Object partitionKey) {
-    globalSemaphore.releaseChannelLock(partitionKey);
     getFreeConnectionsForHost(partitionKey).release();
   }
 
-  private NonBlockingSemaphoreLike getFreeConnectionsForHost(Object partitionKey) {
+  protected Semaphore getFreeConnectionsForHost(Object partitionKey) {
     return maxConnectionsPerHost > 0 ?
-            freeChannelsPerHost.computeIfAbsent(partitionKey, pk -> new NonBlockingSemaphore(maxConnectionsPerHost)) :
-            NonBlockingSemaphoreInfinite.INSTANCE;
+            freeChannelsPerHost.computeIfAbsent(partitionKey, pk -> new Semaphore(maxConnectionsPerHost)) :
+            InfiniteSemaphore.INSTANCE;
   }
 }
diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/NioSocketChannelFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/TransportFactory.java
similarity index 67%
rename from client/src/main/java/org/asynchttpclient/netty/channel/NioSocketChannelFactory.java
rename to client/src/main/java/org/asynchttpclient/netty/channel/TransportFactory.java
index 907623bba6980da72fee1c8f080dcdf8c4bdb02d..76f45c2d288d7ad67cf0fe7e35f0343ff1a32065 100644
--- a/client/src/main/java/org/asynchttpclient/netty/channel/NioSocketChannelFactory.java
+++ b/client/src/main/java/org/asynchttpclient/netty/channel/TransportFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 AsyncHttpClient Project. All rights reserved.
+ * Copyright (c) 2019 AsyncHttpClient Project. 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.
@@ -13,15 +13,14 @@
  */
 package org.asynchttpclient.netty.channel;
 
+import io.netty.channel.Channel;
 import io.netty.channel.ChannelFactory;
-import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.channel.EventLoopGroup;
 
-enum NioSocketChannelFactory implements ChannelFactory<NioSocketChannel> {
+import java.util.concurrent.ThreadFactory;
 
-  INSTANCE;
+public interface TransportFactory<C extends Channel, L extends EventLoopGroup> extends ChannelFactory<C> {
+
+  L newEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory);
 
-  @Override
-  public NioSocketChannel newChannel() {
-    return new NioSocketChannel();
-  }
 }
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 ad29808d96d746e7f2194326df3f07f4d7eba683..a52f75fc8345b37df9d6f640ca0ddbf567d30824 100755
--- a/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java
@@ -30,6 +30,7 @@ import org.asynchttpclient.netty.channel.Channels;
 import org.asynchttpclient.netty.request.NettyRequestSender;
 
 import java.io.IOException;
+import java.net.InetSocketAddress;
 
 @Sharable
 public final class HttpHandler extends AsyncHttpClientHandler {
@@ -69,7 +70,7 @@ public final class HttpHandler extends AsyncHttpClientHandler {
     HttpRequest httpRequest = future.getNettyRequest().getHttpRequest();
     logger.debug("\n\nRequest {}\n\nResponse {}\n", httpRequest, response);
 
-    future.setKeepAlive(config.getKeepAliveStrategy().keepAlive(future.getTargetRequest(), httpRequest, response));
+    future.setKeepAlive(config.getKeepAliveStrategy().keepAlive((InetSocketAddress) channel.remoteAddress(), future.getTargetRequest(), httpRequest, response));
 
     NettyResponseStatus status = new NettyResponseStatus(future.getUri(), response, channel);
     HttpHeaders responseHeaders = response.headers();
diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ConnectSuccessInterceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ConnectSuccessInterceptor.java
index 753df0020d30eaad0ddfd20edfeaa65b41c56582..eb2e98e36ff8824cfb7448f92ec1b93a9f9bac36 100644
--- a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ConnectSuccessInterceptor.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ConnectSuccessInterceptor.java
@@ -52,7 +52,7 @@ public class ConnectSuccessInterceptor {
 
     future.setReuseChannel(true);
     future.setConnectAllowed(false);
-    Request targetRequest = new RequestBuilder(future.getTargetRequest()).build();
+    Request targetRequest = future.getTargetRequest().toBuilder().build();
     if (whenHandshaked == null) {
       requestSender.drainChannelAndExecuteNextRequest(channel, future, targetRequest);
     } else {
diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ProxyUnauthorized407Interceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ProxyUnauthorized407Interceptor.java
index 02ee195622e926bac2123da55bf12142b3f67ce2..57436e9ae5e35757f77d21a0c801a8ada85ffd41 100644
--- a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ProxyUnauthorized407Interceptor.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ProxyUnauthorized407Interceptor.java
@@ -79,7 +79,7 @@ public class ProxyUnauthorized407Interceptor {
 
     // FIXME what's this???
     future.setChannelState(ChannelState.NEW);
-    HttpHeaders requestHeaders = new DefaultHttpHeaders(false).add(request.getHeaders());
+    HttpHeaders requestHeaders = new DefaultHttpHeaders().add(request.getHeaders());
 
     switch (proxyRealm.getScheme()) {
       case BASIC:
@@ -163,7 +163,7 @@ public class ProxyUnauthorized407Interceptor {
         throw new IllegalStateException("Invalid Authentication scheme " + proxyRealm.getScheme());
     }
 
-    RequestBuilder nextRequestBuilder = new RequestBuilder(future.getCurrentRequest()).setHeaders(requestHeaders);
+    RequestBuilder nextRequestBuilder = future.getCurrentRequest().toBuilder().setHeaders(requestHeaders);
     if (future.getCurrentRequest().getUri().isSecured()) {
       nextRequestBuilder.setMethod(CONNECT);
     }
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 121bb71658dad96c9c5444b8d35505379f7596a6..d56b90fd24f1e7fe827b9d04d5e6514a7e19d140 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
@@ -111,6 +111,9 @@ public class Redirect30xInterceptor {
             requestBuilder.setBody(request.getByteBufferData());
           else if (request.getBodyGenerator() != null)
             requestBuilder.setBody(request.getBodyGenerator());
+          else if (isNonEmpty(request.getBodyParts())) {
+            requestBuilder.setBodyParts(request.getBodyParts());
+          }
         }
 
         requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody));
diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Unauthorized401Interceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Unauthorized401Interceptor.java
index 30ba1bc3d6452656a62be61afd1b6529c54fa89b..269042529bb6c5ece2118af8dacb6b18eb247eac 100644
--- a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Unauthorized401Interceptor.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Unauthorized401Interceptor.java
@@ -77,7 +77,7 @@ public class Unauthorized401Interceptor {
 
     // FIXME what's this???
     future.setChannelState(ChannelState.NEW);
-    HttpHeaders requestHeaders = new DefaultHttpHeaders(false).add(request.getHeaders());
+    HttpHeaders requestHeaders = new DefaultHttpHeaders().add(request.getHeaders());
 
     switch (realm.getScheme()) {
       case BASIC:
@@ -162,7 +162,7 @@ public class Unauthorized401Interceptor {
         throw new IllegalStateException("Invalid Authentication scheme " + realm.getScheme());
     }
 
-    final Request nextRequest = new RequestBuilder(future.getCurrentRequest()).setHeaders(requestHeaders).build();
+    final Request nextRequest = future.getCurrentRequest().toBuilder().setHeaders(requestHeaders).build();
 
     LOGGER.debug("Sending authentication to {}", request.getUri());
     if (future.isKeepAlive()
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 03255731f787c75fe08d5e8ffe0e203bf30d576d..32720acc10071e6ac258b13d6bf4d412cf80a2fe 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java
@@ -437,8 +437,8 @@ public final class NettyRequestSender {
   }
 
   private void configureTransferAdapter(AsyncHandler<?> handler, HttpRequest httpRequest) {
-    HttpHeaders h = new DefaultHttpHeaders(false).set(httpRequest.headers());
-    TransferCompletionHandler.class.cast(handler).headers(h);
+    HttpHeaders h = new DefaultHttpHeaders().set(httpRequest.headers());
+    ((TransferCompletionHandler) handler).headers(h);
   }
 
   private void scheduleRequestTimeout(NettyResponseFuture<?> nettyResponseFuture,
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/WriteListener.java b/client/src/main/java/org/asynchttpclient/netty/request/WriteListener.java
index ab38a66f945dafd7eb45dd912ed5f4b85a716d34..0a51e63e90919e34fe2b33a8f7f4c8429eee3d3e 100644
--- a/client/src/main/java/org/asynchttpclient/netty/request/WriteListener.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/WriteListener.java
@@ -16,11 +16,13 @@ package org.asynchttpclient.netty.request;
 import io.netty.channel.Channel;
 import org.asynchttpclient.handler.ProgressAsyncHandler;
 import org.asynchttpclient.netty.NettyResponseFuture;
+import org.asynchttpclient.netty.channel.ChannelState;
 import org.asynchttpclient.netty.channel.Channels;
 import org.asynchttpclient.netty.future.StackTraceInspector;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.net.ssl.SSLException;
 import java.nio.channels.ClosedChannelException;
 
 public abstract class WriteListener {
@@ -36,27 +38,27 @@ public abstract class WriteListener {
     this.notifyHeaders = notifyHeaders;
   }
 
-  private boolean abortOnThrowable(Channel channel, Throwable cause) {
-    if (cause != null) {
-      if (cause instanceof IllegalStateException || cause instanceof ClosedChannelException || StackTraceInspector.recoverOnReadOrWriteException(cause)) {
-        LOGGER.debug(cause.getMessage(), cause);
-        Channels.silentlyCloseChannel(channel);
+  private void abortOnThrowable(Channel channel, Throwable cause) {
+    if (future.getChannelState() == ChannelState.POOLED
+      && (cause instanceof IllegalStateException
+      || cause instanceof ClosedChannelException
+      || cause instanceof SSLException
+      || StackTraceInspector.recoverOnReadOrWriteException(cause))) {
+      LOGGER.debug("Write exception on pooled channel, letting retry trigger", cause);
 
-      } else {
-        future.abort(cause);
-      }
-      return true;
+    } else {
+      future.abort(cause);
     }
-
-    return false;
+    Channels.silentlyCloseChannel(channel);
   }
 
   void operationComplete(Channel channel, Throwable cause) {
     future.touch();
 
-    // The write operation failed. If the channel was cached, it means it got asynchronously closed.
+    // The write operation failed. If the channel was pooled, it means it got asynchronously closed.
     // Let's retry a second time.
-    if (abortOnThrowable(channel, cause)) {
+    if (cause != null) {
+      abortOnThrowable(channel, cause);
       return;
     }
 
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 0d2535871369052d4f36dce5fc4507fe58e92556..9d4eacb165d5c517c6d7772f3c32f4a3f579859b 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
@@ -23,6 +23,6 @@ public abstract class NettyDirectBody implements NettyBody {
 
   @Override
   public void write(Channel channel, NettyResponseFuture<?> future) {
-    throw new UnsupportedOperationException("This kind of body is supposed to be writen directly");
+    throw new UnsupportedOperationException("This kind of body is supposed to be written directly");
   }
 }
diff --git a/client/src/main/java/org/asynchttpclient/netty/ssl/DefaultSslEngineFactory.java b/client/src/main/java/org/asynchttpclient/netty/ssl/DefaultSslEngineFactory.java
index 60b14b56e55b0eec6937fd5c2db9818a0cf4478b..401c60a581ed8ab64e763d79485a9f5ccd63ed63 100644
--- a/client/src/main/java/org/asynchttpclient/netty/ssl/DefaultSslEngineFactory.java
+++ b/client/src/main/java/org/asynchttpclient/netty/ssl/DefaultSslEngineFactory.java
@@ -19,6 +19,7 @@ import io.netty.handler.ssl.SslContext;
 import io.netty.handler.ssl.SslContextBuilder;
 import io.netty.handler.ssl.SslProvider;
 import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import io.netty.util.ReferenceCountUtil;
 import org.asynchttpclient.AsyncHttpClientConfig;
 
 import javax.net.ssl.SSLEngine;
@@ -73,6 +74,11 @@ public class DefaultSslEngineFactory extends SslEngineFactoryBase {
     sslContext = buildSslContext(config);
   }
 
+  @Override
+  public void destroy() {
+    ReferenceCountUtil.release(sslContext);
+  }
+
   /**
    * The last step of configuring the SslContextBuilder used to create an SslContext when no context is provided in the {@link AsyncHttpClientConfig}. This defaults to no-op and
    * is intended to be overridden as needed.
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 e746adfdb55bbf1b814a18eeea9b2d43bd5ef901..034502785c9b92a8b3ff98129f3411afc7e34923 100755
--- a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java
+++ b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java
@@ -55,7 +55,7 @@ public abstract class TimeoutTimerTask implements TimerTask {
 
   void appendRemoteAddress(StringBuilder sb) {
     InetSocketAddress remoteAddress = timeoutsHolder.remoteAddress();
-    sb.append(remoteAddress.getHostName());
+    sb.append(remoteAddress.getHostString());
     if (!remoteAddress.isUnresolved()) {
       sb.append('/').append(remoteAddress.getAddress().getHostAddress());
     }
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 531eaadd894b47aaa7d37c94bbba6b59292b07f9..f6ab4ae2f3e4a3b2e010e1259000094aa041d6d0 100755
--- a/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java
+++ b/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java
@@ -155,7 +155,7 @@ public final class NettyWebSocket implements WebSocket {
   @Override
   public Future<Void> sendCloseFrame(int statusCode, String reasonText) {
     if (channel.isOpen()) {
-      return channel.writeAndFlush(new CloseWebSocketFrame(1000, "normal closure"));
+      return channel.writeAndFlush(new CloseWebSocketFrame(statusCode, reasonText));
     }
     return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);
   }
diff --git a/client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorInstance.java b/client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorInstance.java
index acb9fce5b1ccb3764e936449f698b55acc060947..aa92c5aaa1ac2dbc5e37b510200bd77ba977c2ba 100644
--- a/client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorInstance.java
+++ b/client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorInstance.java
@@ -199,7 +199,6 @@ public class OAuthSignatureCalculatorInstance {
     SecretKeySpec signingKey = new SecretKeySpec(keyBytes, HMAC_SHA1_ALGORITHM);
 
     mac.init(signingKey);
-    mac.reset();
     mac.update(message);
     return mac.doFinal();
   }
diff --git a/client/src/main/java/org/asynchttpclient/resolver/RequestHostnameResolver.java b/client/src/main/java/org/asynchttpclient/resolver/RequestHostnameResolver.java
index 3edf13ff1d80169ba2fede6a1d17c8d9cbacdda2..da42fcf6601a1b971c919337f88da31cf0340632 100644
--- a/client/src/main/java/org/asynchttpclient/resolver/RequestHostnameResolver.java
+++ b/client/src/main/java/org/asynchttpclient/resolver/RequestHostnameResolver.java
@@ -35,7 +35,7 @@ public enum RequestHostnameResolver {
 
   public Future<List<InetSocketAddress>> resolve(NameResolver<InetAddress> nameResolver, InetSocketAddress unresolvedAddress, AsyncHandler<?> asyncHandler) {
 
-    final String hostname = unresolvedAddress.getHostName();
+    final String hostname = unresolvedAddress.getHostString();
     final int port = unresolvedAddress.getPort();
     final Promise<List<InetSocketAddress>> promise = ImmediateEventExecutor.INSTANCE.newPromise();
 
diff --git a/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java b/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java
index 5a22abc361bf5a57a61a60d73605364de10598f2..11d00c05723c786602a8e05adbd5d6f20e0da4a6 100644
--- a/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java
+++ b/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java
@@ -156,7 +156,7 @@ public final class ProxyUtils {
                     return null;
                   } else {
                     InetSocketAddress address = (InetSocketAddress) proxy.address();
-                    return proxyServer(address.getHostName(), address.getPort()).build();
+                    return proxyServer(address.getHostString(), address.getPort()).build();
                   }
                 case DIRECT:
                   return null;
diff --git a/client/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java b/client/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java
index a6df2fccf41a0f21deab1f6404056ecc5902db95..5a874af18069fdb022202bbfe335c8b2b98fb122 100644
--- a/client/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java
+++ b/client/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java
@@ -14,7 +14,6 @@
 package org.asynchttpclient.webdav;
 
 import io.netty.handler.codec.http.HttpHeaders;
-import org.asynchttpclient.AsyncCompletionHandlerBase;
 import org.asynchttpclient.AsyncHandler;
 import org.asynchttpclient.HttpResponseBodyPart;
 import org.asynchttpclient.HttpResponseStatus;
@@ -42,11 +41,24 @@ import java.util.List;
  * @param <T> the result type
  */
 public abstract class WebDavCompletionHandlerBase<T> implements AsyncHandler<T> {
-  private final Logger logger = LoggerFactory.getLogger(AsyncCompletionHandlerBase.class);
+  private static final Logger LOGGER = LoggerFactory.getLogger(WebDavCompletionHandlerBase.class);
+  private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY;
   private final List<HttpResponseBodyPart> bodyParts = Collections.synchronizedList(new ArrayList<>());
   private HttpResponseStatus status;
   private HttpHeaders headers;
 
+  static {
+    DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
+    if (Boolean.getBoolean("org.asynchttpclient.webdav.enableDtd")) {
+      try {
+        DOCUMENT_BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+      } catch (ParserConfigurationException e) {
+        LOGGER.error("Failed to disable doctype declaration");
+        throw new ExceptionInInitializerError(e);
+      }
+    }
+  }
+
   /**
    * {@inheritDoc}
    */
@@ -75,13 +87,12 @@ public abstract class WebDavCompletionHandlerBase<T> implements AsyncHandler<T>
   }
 
   private Document readXMLResponse(InputStream stream) {
-    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
     Document document;
     try {
-      document = factory.newDocumentBuilder().parse(stream);
+      document = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().parse(stream);
       parse(document);
     } catch (SAXException | IOException | ParserConfigurationException e) {
-      logger.error(e.getMessage(), e);
+      LOGGER.error(e.getMessage(), e);
       throw new RuntimeException(e);
     }
     return document;
@@ -94,7 +105,7 @@ public abstract class WebDavCompletionHandlerBase<T> implements AsyncHandler<T>
       Node node = statusNode.item(i);
 
       String value = node.getFirstChild().getNodeValue();
-      int statusCode = Integer.valueOf(value.substring(value.indexOf(" "), value.lastIndexOf(" ")).trim());
+      int statusCode = Integer.parseInt(value.substring(value.indexOf(" "), value.lastIndexOf(" ")).trim());
       String statusText = value.substring(value.lastIndexOf(" "));
       status = new HttpStatusWrapper(status, statusText, statusCode);
     }
@@ -122,7 +133,7 @@ public abstract class WebDavCompletionHandlerBase<T> implements AsyncHandler<T>
    */
   @Override
   public void onThrowable(Throwable t) {
-    logger.debug(t.getMessage(), t);
+    LOGGER.debug(t.getMessage(), t);
   }
 
   /**
@@ -134,7 +145,7 @@ public abstract class WebDavCompletionHandlerBase<T> implements AsyncHandler<T>
    */
   abstract public T onCompleted(WebDavResponse response) throws Exception;
 
-  private class HttpStatusWrapper extends HttpResponseStatus {
+  private static class HttpStatusWrapper extends HttpResponseStatus {
 
     private final HttpResponseStatus wrapped;
 
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 cdc632f701a4172689593fa898a1d513042d64e6..cb846ac58052f0f0451795681e138942e1b09fc4 100644
--- a/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties
+++ b/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties
@@ -1,6 +1,7 @@
 org.asynchttpclient.threadPoolName=AsyncHttpClient
 org.asynchttpclient.maxConnections=-1
 org.asynchttpclient.maxConnectionsPerHost=-1
+org.asynchttpclient.acquireFreeChannelTimeout=0
 org.asynchttpclient.connectTimeout=5000
 org.asynchttpclient.pooledConnectionIdleTimeout=60000
 org.asynchttpclient.connectionPoolCleanerPeriod=1000
@@ -31,6 +32,7 @@ org.asynchttpclient.sslSessionCacheSize=0
 org.asynchttpclient.sslSessionTimeout=0
 org.asynchttpclient.tcpNoDelay=true
 org.asynchttpclient.soReuseAddress=false
+org.asynchttpclient.soKeepAlive=true
 org.asynchttpclient.soLinger=-1
 org.asynchttpclient.soSndBuf=-1
 org.asynchttpclient.soRcvBuf=-1
diff --git a/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java b/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java
index e5ec9065765990f2ff1329e9657ca754f45b2d91..1547872aaa51a89397c0a520dc9e682ee5928c94 100644
--- a/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java
+++ b/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java
@@ -25,6 +25,8 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -40,7 +42,7 @@ import static org.testng.Assert.*;
 
 public class AsyncStreamHandlerTest extends HttpTest {
 
-  private static final String RESPONSE = "param_1_";
+  private static final String RESPONSE = "param_1=value_1";
 
   private static HttpServer server;
 
@@ -93,18 +95,25 @@ public class AsyncStreamHandlerTest extends HttpTest {
                   @Override
                   public State onHeadersReceived(HttpHeaders headers) {
                     assertContentTypesEquals(headers.get(CONTENT_TYPE), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET);
+                    for (Map.Entry<String, String> header : headers) {
+                      if (header.getKey().startsWith("X-param")) {
+                        builder.append(header.getKey().substring(2)).append("=").append(header.getValue()).append("&");
+                      }
+                    }
                     return State.CONTINUE;
                   }
 
                   @Override
                   public State onBodyPartReceived(HttpResponseBodyPart content) {
-                    builder.append(new String(content.getBodyPartBytes(), US_ASCII));
                     return State.CONTINUE;
                   }
 
                   @Override
                   public String onCompleted() {
-                    return builder.toString().trim();
+                    if (builder.length() > 0) {
+                      builder.setLength(builder.length() - 1);
+                    }
+                    return builder.toString();
                   }
                 }).get(10, TimeUnit.SECONDS);
 
@@ -174,17 +183,24 @@ public class AsyncStreamHandlerTest extends HttpTest {
                   public State onHeadersReceived(HttpHeaders headers) {
                     assertContentTypesEquals(headers.get(CONTENT_TYPE), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET);
                     onHeadersReceived.set(true);
+                    for (Map.Entry<String, String> header : headers) {
+                      if (header.getKey().startsWith("X-param")) {
+                        builder.append(header.getKey().substring(2)).append("=").append(header.getValue()).append("&");
+                      }
+                    }
                     return State.CONTINUE;
                   }
 
                   @Override
                   public State onBodyPartReceived(HttpResponseBodyPart content) {
-                    builder.append(new String(content.getBodyPartBytes()));
                     return State.CONTINUE;
                   }
 
                   @Override
                   public String onCompleted() {
+                    if (builder.length() > 0) {
+                      builder.setLength(builder.length() - 1);
+                    }
                     return builder.toString().trim();
                   }
 
@@ -254,17 +270,24 @@ public class AsyncStreamHandlerTest extends HttpTest {
           @Override
           public State onHeadersReceived(HttpHeaders headers) {
             responseHeaders.set(headers);
+            for (Map.Entry<String, String> header : headers) {
+              if (header.getKey().startsWith("X-param")) {
+                builder.append(header.getKey().substring(2)).append("=").append(header.getValue()).append("&");
+              }
+            }
             return State.CONTINUE;
           }
 
           @Override
           public State onBodyPartReceived(HttpResponseBodyPart content) {
-            builder.append(new String(content.getBodyPartBytes()));
             return State.CONTINUE;
           }
 
           @Override
           public String onCompleted() {
+            if (builder.length() > 0) {
+              builder.setLength(builder.length() - 1);
+            }
             return builder.toString();
           }
         });
diff --git a/client/src/test/java/org/asynchttpclient/BasicHttpTest.java b/client/src/test/java/org/asynchttpclient/BasicHttpTest.java
index 0a2631049144fb5ffb16c9465de47962d102e354..d38c930f91e4212f1755a093155aa7af020989d1 100755
--- a/client/src/test/java/org/asynchttpclient/BasicHttpTest.java
+++ b/client/src/test/java/org/asynchttpclient/BasicHttpTest.java
@@ -38,7 +38,10 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
 import java.net.ConnectException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
 import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
@@ -206,6 +209,40 @@ public class BasicHttpTest extends HttpTest {
       }));
   }
 
+  @Test
+  public void postChineseChar() throws Throwable {
+    withClient().run(client ->
+      withServer(server).run(server -> {
+        HttpHeaders h = new DefaultHttpHeaders();
+        h.add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED);
+
+        String chineseChar = "是";
+
+        Map<String, List<String>> m = new HashMap<>();
+        m.put("param", Collections.singletonList(chineseChar));
+
+        Request request = post(getTargetUrl()).setHeaders(h).setFormParams(m).build();
+
+        server.enqueueEcho();
+
+        client.executeRequest(request, new AsyncCompletionHandlerAdapter() {
+          @Override
+          public Response onCompleted(Response response) {
+            assertEquals(response.getStatusCode(), 200);
+            String value;
+            try {
+              // headers must be encoded
+              value = URLDecoder.decode(response.getHeader("X-param"), StandardCharsets.UTF_8.name());
+            } catch (UnsupportedEncodingException e) {
+              throw new RuntimeException(e);
+            }
+            assertEquals(value, chineseChar);
+            return response;
+          }
+        }).get(TIMEOUT, SECONDS);
+      }));
+  }
+
   @Test
   public void headHasEmptyBody() throws Throwable {
     withClient().run(client ->
diff --git a/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java b/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java
index 216e2604397a5c85f62acf40daa7dc55864f97d2..4395f0f49ba82a9f389fc2c753b45fdef0eb80a3 100644
--- a/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java
+++ b/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java
@@ -107,7 +107,7 @@ public class BasicHttpsTest extends HttpTest {
   public void multipleConcurrentPostRequestsOverHttpsWithDisabledKeepAliveStrategy() throws Throwable {
     logger.debug(">>> multipleConcurrentPostRequestsOverHttpsWithDisabledKeepAliveStrategy");
 
-    KeepAliveStrategy keepAliveStrategy = (ahcRequest, nettyRequest, nettyResponse) -> !ahcRequest.getUri().isSecured();
+    KeepAliveStrategy keepAliveStrategy = (remoteAddress, ahcRequest, nettyRequest, nettyResponse) -> !ahcRequest.getUri().isSecured();
 
     withClient(config().setSslEngineFactory(createSslEngineFactory()).setKeepAliveStrategy(keepAliveStrategy)).run(client ->
       withServer(server).run(server -> {
diff --git a/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java b/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java
index 41fed53a4c3774f350397bef31ab9a23ed3de066..968c408fbcbdcc07d5469b9934e2bc927f721462 100644
--- a/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java
+++ b/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java
@@ -72,7 +72,7 @@ public class RequestBuilderTest {
   public void testChaining() {
     Request request = get("http://foo.com").addQueryParam("x", "value").build();
 
-    Request request2 = new RequestBuilder(request).build();
+    Request request2 = request.toBuilder().build();
 
     assertEquals(request2.getUri(), request.getUri());
   }
diff --git a/client/src/test/java/org/asynchttpclient/channel/MaxTotalConnectionTest.java b/client/src/test/java/org/asynchttpclient/channel/MaxTotalConnectionTest.java
index 5992bf3ed59b3fbe60d72a93a9be1aa2b4b3a52f..fcf34896f6f3e5bafc8da751ac724a6ec7ddef98 100644
--- a/client/src/test/java/org/asynchttpclient/channel/MaxTotalConnectionTest.java
+++ b/client/src/test/java/org/asynchttpclient/channel/MaxTotalConnectionTest.java
@@ -33,7 +33,7 @@ public class MaxTotalConnectionTest extends AbstractBasicTest {
 
   @Test(groups = "online")
   public void testMaxTotalConnectionsExceedingException() throws IOException {
-    String[] urls = new String[]{"http://google.com", "http://github.com/"};
+    String[] urls = new String[]{"https://google.com", "https://github.com"};
 
     AsyncHttpClientConfig config = config()
             .setConnectTimeout(1000)
@@ -69,7 +69,7 @@ public class MaxTotalConnectionTest extends AbstractBasicTest {
 
   @Test(groups = "online")
   public void testMaxTotalConnections() throws Exception {
-    String[] urls = new String[]{"http://google.com", "http://gatling.io"};
+    String[] urls = new String[]{"https://google.com", "https://github.com"};
 
     final CountDownLatch latch = new CountDownLatch(2);
     final AtomicReference<Throwable> ex = new AtomicReference<>();
diff --git a/client/src/test/java/org/asynchttpclient/filter/FilterTest.java b/client/src/test/java/org/asynchttpclient/filter/FilterTest.java
index 10b36507a527cffeadc3df2e56af001ab2a9a428..14997d6234f8184cf947e0588b8c41966fa67822 100644
--- a/client/src/test/java/org/asynchttpclient/filter/FilterTest.java
+++ b/client/src/test/java/org/asynchttpclient/filter/FilterTest.java
@@ -101,7 +101,7 @@ public class FilterTest extends AbstractBasicTest {
     ResponseFilter responseFilter = new ResponseFilter() {
       public <T> FilterContext<T> filter(FilterContext<T> ctx) {
         if (replay.getAndSet(false)) {
-          Request request = new RequestBuilder(ctx.getRequest()).addHeader("X-Replay", "true").build();
+          Request request = ctx.getRequest().toBuilder().addHeader("X-Replay", "true").build();
           return new FilterContext.FilterContextBuilder<T>().asyncHandler(ctx.getAsyncHandler()).request(request).replayRequest(true).build();
         }
         return ctx;
@@ -123,7 +123,7 @@ public class FilterTest extends AbstractBasicTest {
     ResponseFilter responseFilter = new ResponseFilter() {
       public <T> FilterContext<T> filter(FilterContext<T> ctx) {
         if (ctx.getResponseStatus() != null && ctx.getResponseStatus().getStatusCode() == 200 && replay.getAndSet(false)) {
-          Request request = new RequestBuilder(ctx.getRequest()).addHeader("X-Replay", "true").build();
+          Request request = ctx.getRequest().toBuilder().addHeader("X-Replay", "true").build();
           return new FilterContext.FilterContextBuilder<T>().asyncHandler(ctx.getAsyncHandler()).request(request).replayRequest(true).build();
         }
         return ctx;
@@ -145,7 +145,7 @@ public class FilterTest extends AbstractBasicTest {
     ResponseFilter responseFilter = new ResponseFilter() {
       public <T> FilterContext<T> filter(FilterContext<T> ctx) {
         if (ctx.getResponseHeaders() != null && ctx.getResponseHeaders().get("Ping").equals("Pong") && replay.getAndSet(false)) {
-          Request request = new RequestBuilder(ctx.getRequest()).addHeader("Ping", "Pong").build();
+          Request request = ctx.getRequest().toBuilder().addHeader("Ping", "Pong").build();
           return new FilterContext.FilterContextBuilder<T>().asyncHandler(ctx.getAsyncHandler()).request(request).replayRequest(true).build();
         }
         return ctx;
diff --git a/client/src/test/java/org/asynchttpclient/netty/channel/NonBlockingSemaphoreTest.java b/client/src/test/java/org/asynchttpclient/netty/channel/NonBlockingSemaphoreTest.java
deleted file mode 100644
index a387ba408bf0ef56e0bb42d72665bfdc17e24f62..0000000000000000000000000000000000000000
--- a/client/src/test/java/org/asynchttpclient/netty/channel/NonBlockingSemaphoreTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (c) 2017 AsyncHttpClient Project. 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.netty.channel;
-
-import org.testng.annotations.Test;
-
-import java.util.concurrent.Semaphore;
-
-import static org.testng.Assert.*;
-
-/**
- * @author Stepan Koltsov
- */
-public class NonBlockingSemaphoreTest {
-
-  @Test
-  public void test0() {
-    Mirror mirror = new Mirror(0);
-    assertFalse(mirror.tryAcquire());
-  }
-
-  @Test
-  public void three() {
-    Mirror mirror = new Mirror(3);
-    for (int i = 0; i < 3; ++i) {
-      assertTrue(mirror.tryAcquire());
-    }
-    assertFalse(mirror.tryAcquire());
-    mirror.release();
-    assertTrue(mirror.tryAcquire());
-  }
-
-  @Test
-  public void negative() {
-    Mirror mirror = new Mirror(-1);
-    assertFalse(mirror.tryAcquire());
-    mirror.release();
-    assertFalse(mirror.tryAcquire());
-    mirror.release();
-    assertTrue(mirror.tryAcquire());
-  }
-
-  private static class Mirror {
-    private final Semaphore real;
-    private final NonBlockingSemaphore nonBlocking;
-
-    Mirror(int permits) {
-      real = new Semaphore(permits);
-      nonBlocking = new NonBlockingSemaphore(permits);
-    }
-
-    boolean tryAcquire() {
-      boolean a = real.tryAcquire();
-      boolean b = nonBlocking.tryAcquire();
-      assertEquals(a, b);
-      return a;
-    }
-
-    void release() {
-      real.release();
-      nonBlocking.release();
-    }
-  }
-
-}
diff --git a/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreRunner.java b/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreRunner.java
new file mode 100644
index 0000000000000000000000000000000000000000..7bff799ceb2a08b8caa6ac1e6fc694c7f6fdde24
--- /dev/null
+++ b/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreRunner.java
@@ -0,0 +1,52 @@
+package org.asynchttpclient.netty.channel;
+
+class SemaphoreRunner {
+
+  final ConnectionSemaphore semaphore;
+  final Thread acquireThread;
+
+  volatile long acquireTime;
+  volatile Exception acquireException;
+
+  public SemaphoreRunner(ConnectionSemaphore semaphore, Object partitionKey) {
+    this.semaphore = semaphore;
+    this.acquireThread = new Thread(() -> {
+      long beforeAcquire = System.currentTimeMillis();
+      try {
+        semaphore.acquireChannelLock(partitionKey);
+      } catch (Exception e) {
+        acquireException = e;
+      } finally {
+        acquireTime = System.currentTimeMillis() - beforeAcquire;
+      }
+    });
+  }
+
+  public void acquire() {
+    this.acquireThread.start();
+  }
+
+  public void interrupt() {
+    this.acquireThread.interrupt();
+  }
+
+  public void await() {
+    try {
+      this.acquireThread.join();
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public boolean finished() {
+    return !this.acquireThread.isAlive();
+  }
+
+  public long getAcquireTime() {
+    return acquireTime;
+  }
+
+  public Exception getAcquireException() {
+    return acquireException;
+  }
+}
diff --git a/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreTest.java b/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..125cd9b066bf6f6c44fde9590920ad33e261ff7e
--- /dev/null
+++ b/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreTest.java
@@ -0,0 +1,143 @@
+package org.asynchttpclient.netty.channel;
+
+import org.asynchttpclient.exception.TooManyConnectionsException;
+import org.asynchttpclient.exception.TooManyConnectionsPerHostException;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.testng.AssertJUnit.*;
+
+public class SemaphoreTest {
+
+  static final int CHECK_ACQUIRE_TIME__PERMITS = 10;
+  static final int CHECK_ACQUIRE_TIME__TIMEOUT = 100;
+
+  static final int NON_DETERMINISTIC__INVOCATION_COUNT = 10;
+  static final int NON_DETERMINISTIC__SUCCESS_PERCENT = 70;
+
+  private final Object PK = new Object();
+
+  @DataProvider(name = "permitsAndRunnersCount")
+  public Object[][] permitsAndRunnersCount() {
+    Object[][] objects = new Object[100][];
+    int row = 0;
+    for (int i = 0; i < 10; i++) {
+      for (int j = 0; j < 10; j++) {
+        objects[row++] = new Object[]{i, j};
+      }
+    }
+    return objects;
+  }
+
+  @Test(timeOut = 1000, dataProvider = "permitsAndRunnersCount")
+  public void maxConnectionCheckPermitCount(int permitCount, int runnerCount) {
+    allSemaphoresCheckPermitCount(new MaxConnectionSemaphore(permitCount, 0), permitCount, runnerCount);
+  }
+
+  @Test(timeOut = 1000, dataProvider = "permitsAndRunnersCount")
+  public void perHostCheckPermitCount(int permitCount, int runnerCount) {
+    allSemaphoresCheckPermitCount(new PerHostConnectionSemaphore(permitCount, 0), permitCount, runnerCount);
+  }
+
+  @Test(timeOut = 3000, dataProvider = "permitsAndRunnersCount")
+  public void combinedCheckPermitCount(int permitCount, int runnerCount) {
+    allSemaphoresCheckPermitCount(new CombinedConnectionSemaphore(permitCount, permitCount, 0), permitCount, runnerCount);
+    allSemaphoresCheckPermitCount(new CombinedConnectionSemaphore(0, permitCount, 0), permitCount, runnerCount);
+    allSemaphoresCheckPermitCount(new CombinedConnectionSemaphore(permitCount, 0, 0), permitCount, runnerCount);
+  }
+
+  private void allSemaphoresCheckPermitCount(ConnectionSemaphore semaphore, int permitCount, int runnerCount) {
+    List<SemaphoreRunner> runners = IntStream.range(0, runnerCount)
+            .mapToObj(i -> new SemaphoreRunner(semaphore, PK))
+            .collect(Collectors.toList());
+    runners.forEach(SemaphoreRunner::acquire);
+    runners.forEach(SemaphoreRunner::await);
+
+    long tooManyConnectionsCount = runners.stream().map(SemaphoreRunner::getAcquireException)
+            .filter(Objects::nonNull)
+            .filter(e -> e instanceof IOException)
+            .count();
+
+    long acquired = runners.stream().map(SemaphoreRunner::getAcquireException)
+            .filter(Objects::isNull)
+            .count();
+
+    int expectedAcquired = permitCount > 0 ? Math.min(permitCount, runnerCount) : runnerCount;
+
+    assertEquals(expectedAcquired, acquired);
+    assertEquals(runnerCount - acquired, tooManyConnectionsCount);
+  }
+
+  @Test(timeOut = 1000, invocationCount = NON_DETERMINISTIC__INVOCATION_COUNT, successPercentage = NON_DETERMINISTIC__SUCCESS_PERCENT)
+  public void maxConnectionCheckAcquireTime() {
+    checkAcquireTime(new MaxConnectionSemaphore(CHECK_ACQUIRE_TIME__PERMITS, CHECK_ACQUIRE_TIME__TIMEOUT));
+  }
+
+  @Test(timeOut = 1000, invocationCount = NON_DETERMINISTIC__INVOCATION_COUNT, successPercentage = NON_DETERMINISTIC__SUCCESS_PERCENT)
+  public void perHostCheckAcquireTime() {
+    checkAcquireTime(new PerHostConnectionSemaphore(CHECK_ACQUIRE_TIME__PERMITS, CHECK_ACQUIRE_TIME__TIMEOUT));
+  }
+
+  @Test(timeOut = 1000, invocationCount = NON_DETERMINISTIC__INVOCATION_COUNT, successPercentage = NON_DETERMINISTIC__SUCCESS_PERCENT)
+  public void combinedCheckAcquireTime() {
+    checkAcquireTime(new CombinedConnectionSemaphore(CHECK_ACQUIRE_TIME__PERMITS,
+            CHECK_ACQUIRE_TIME__PERMITS,
+            CHECK_ACQUIRE_TIME__TIMEOUT));
+  }
+
+  private void checkAcquireTime(ConnectionSemaphore semaphore) {
+    List<SemaphoreRunner> runners = IntStream.range(0, CHECK_ACQUIRE_TIME__PERMITS * 2)
+            .mapToObj(i -> new SemaphoreRunner(semaphore, PK))
+            .collect(Collectors.toList());
+    long acquireStartTime = System.currentTimeMillis();
+    runners.forEach(SemaphoreRunner::acquire);
+    runners.forEach(SemaphoreRunner::await);
+    long timeToAcquire = System.currentTimeMillis() - acquireStartTime;
+
+    assertTrue("Semaphore acquired too soon: " + timeToAcquire+" ms",timeToAcquire >= (CHECK_ACQUIRE_TIME__TIMEOUT - 50)); //Lower Bound
+    assertTrue("Semaphore acquired too late: " + timeToAcquire+" ms",timeToAcquire <= (CHECK_ACQUIRE_TIME__TIMEOUT + 300)); //Upper Bound
+  }
+
+  @Test(timeOut = 1000)
+  public void maxConnectionCheckRelease() throws IOException {
+    checkRelease(new MaxConnectionSemaphore(1, 0));
+  }
+
+  @Test(timeOut = 1000)
+  public void perHostCheckRelease() throws IOException {
+    checkRelease(new PerHostConnectionSemaphore(1, 0));
+  }
+
+  @Test(timeOut = 1000)
+  public void combinedCheckRelease() throws IOException {
+    checkRelease(new CombinedConnectionSemaphore(1, 1, 0));
+  }
+
+  private void checkRelease(ConnectionSemaphore semaphore) throws IOException {
+    semaphore.acquireChannelLock(PK);
+    boolean tooManyCaught = false;
+    try {
+      semaphore.acquireChannelLock(PK);
+    } catch (TooManyConnectionsException | TooManyConnectionsPerHostException e) {
+      tooManyCaught = true;
+    }
+    assertTrue(tooManyCaught);
+    tooManyCaught = false;
+    semaphore.releaseChannelLock(PK);
+    try {
+      semaphore.acquireChannelLock(PK);
+    } catch (TooManyConnectionsException | TooManyConnectionsPerHostException e) {
+      tooManyCaught = true;
+    }
+    assertFalse(tooManyCaught);
+  }
+
+
+}
+
diff --git a/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java b/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java
index 252de41913355de8dd3d5eed5f71e5854af97fe7..8047c5f843a89848adb409896df4e4493c5f5b01 100644
--- a/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java
+++ b/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java
@@ -20,6 +20,7 @@ import org.asynchttpclient.Response;
 import org.asynchttpclient.netty.request.NettyRequest;
 import org.testng.Assert;
 
+import javax.net.ssl.SSLSession;
 import java.net.InetSocketAddress;
 import java.util.List;
 import java.util.Queue;
@@ -128,7 +129,8 @@ public class EventCollectingHandler extends AsyncCompletionHandlerBase {
   }
 
   @Override
-  public void onTlsHandshakeSuccess() {
+  public void onTlsHandshakeSuccess(SSLSession sslSession) {
+    Assert.assertNotNull(sslSession);
     firedEvents.add(TLS_HANDSHAKE_SUCCESS_EVENT);
   }
 
diff --git a/client/src/test/java/org/asynchttpclient/testserver/HttpServer.java b/client/src/test/java/org/asynchttpclient/testserver/HttpServer.java
index ae387469a7955c5a39c388bc7a43c27464d96c0f..9b74656b5eebbe196fed29ae415f4adbd65d30db 100644
--- a/client/src/test/java/org/asynchttpclient/testserver/HttpServer.java
+++ b/client/src/test/java/org/asynchttpclient/testserver/HttpServer.java
@@ -25,6 +25,8 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.Closeable;
 import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.Enumeration;
 import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -208,8 +210,9 @@ public class HttpServer implements Closeable {
         response.addHeader("X-" + headerName, request.getHeader(headerName));
       }
 
+      StringBuilder requestBody = new StringBuilder();
       for (Entry<String, String[]> e : baseRequest.getParameterMap().entrySet()) {
-        response.addHeader("X-" + e.getKey(), e.getValue()[0]);
+        response.addHeader("X-" + e.getKey(), URLEncoder.encode(e.getValue()[0], StandardCharsets.UTF_8.name()));
       }
 
       Cookie[] cs = request.getCookies();
@@ -219,14 +222,6 @@ public class HttpServer implements Closeable {
         }
       }
 
-      Enumeration<String> parameterNames = request.getParameterNames();
-      StringBuilder requestBody = new StringBuilder();
-      while (parameterNames.hasMoreElements()) {
-        String param = parameterNames.nextElement();
-        response.addHeader("X-" + param, request.getParameter(param));
-        requestBody.append(param);
-        requestBody.append("_");
-      }
       if (requestBody.length() > 0) {
         response.getOutputStream().write(requestBody.toString().getBytes());
       }
diff --git a/client/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.java b/client/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.java
index 52aaefc3a575c0cdf214d25209d3377301142949..ebbfb511fa24d57edadc1936a925241353078c91 100644
--- a/client/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.java
+++ b/client/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.java
@@ -51,7 +51,8 @@ public class CloseCodeReasonMessageTest extends AbstractBasicWebSocketTest {
       c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new Listener(latch, text)).build()).get();
 
       latch.await();
-      assertEquals(text.get(), "1001-Idle Timeout");
+      // used to be correct 001-Idle Timeout prior to Jetty 9.4.15...
+      assertEquals(text.get(), "1000-");
     }
   }
 
diff --git a/debian/changelog b/debian/changelog
index 98524b5ce8264a7177c0ed42b947298bce49a407..fbd5e82e25bc15e07ff8359afbf729a363812002 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,15 @@
+async-http-client (2.11.0-1) unstable; urgency=medium
+
+  * Team upload.
+  * Update to upstream v2.11.0
+  * Use debhelper-compat.
+    - Update to compat level 12.
+  * Update Standards-Version to 4.5.0
+  * Add Rules-Requires-Root: no.
+  * Use secure copyright format uri.
+
+ -- Sudip Mukherjee <sudipm.mukherjee@gmail.com>  Tue, 14 Apr 2020 23:21:02 +0100
+
 async-http-client (2.6.0-1) unstable; urgency=medium
 
   * New upstream release
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index b4de3947675361a7770d29b8982c407b0ec6b2a0..0000000000000000000000000000000000000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-11
diff --git a/debian/control b/debian/control
index bca2d20a8f026ee709d42f95cf04b907c899545f..cfc21c30d5b5226a6ecd2b2fc12cf07154ab3fce 100644
--- a/debian/control
+++ b/debian/control
@@ -4,7 +4,7 @@ Priority: optional
 Maintainer: Debian Java Maintainers <pkg-java-maintainers@lists.alioth.debian.org>
 Uploaders: Emmanuel Bourg <ebourg@apache.org>
 Build-Depends:
- debhelper (>= 11~),
+ debhelper-compat (= 12),
  default-jdk,
  libactivation-java,
  libmaven-bundle-plugin-java,
@@ -12,8 +12,10 @@ Build-Depends:
  libnetty-reactive-streams-java,
  libreactive-streams-java,
  libslf4j-java,
- maven-debian-helper (>= 2.2)
-Standards-Version: 4.2.1
+ maven-debian-helper (>= 2.2),
+ libhamcrest-java
+Standards-Version: 4.5.0
+Rules-Requires-Root: no
 Vcs-Git: https://salsa.debian.org/java-team/async-http-client.git
 Vcs-Browser: https://salsa.debian.org/java-team/async-http-client
 Homepage: https://github.com/AsyncHttpClient/async-http-client
diff --git a/debian/copyright b/debian/copyright
index 9dc11d826445eb7198f763bfc940ab75fe737e1a..a9ada339525f5ae6493f547c251f31d7228dd925 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -1,4 +1,4 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0
 Upstream-Name: Asynchronous Http Client
 Source: https://github.com/AsyncHttpClient/async-http-client
 
diff --git a/debian/libasync-http-client-java.poms b/debian/libasync-http-client-java.poms
index fea76435f470ff42a0b5a5c03cc8f3b52c83deb0..9bd0abbe9990f578576997e279d75cd5dbe0a4d3 100644
--- a/debian/libasync-http-client-java.poms
+++ b/debian/libasync-http-client-java.poms
@@ -28,6 +28,7 @@
 pom.xml --no-parent --has-package-version
 client/pom.xml --has-package-version
 example/pom.xml               --ignore
+bom/pom.xml                   --ignore
 extras/pom.xml                --ignore
 extras/guava/pom.xml          --ignore
 extras/jdeferred/pom.xml      --ignore
diff --git a/debian/maven.rules b/debian/maven.rules
index 001e4d9ffae8cdffeba1cc2ce66ec65cb97c2134..de361487cdd90016750f3159449678e0a38f9cbf 100644
--- a/debian/maven.rules
+++ b/debian/maven.rules
@@ -19,3 +19,5 @@
 com.ning async-http-client jar s/.*/debian/ * *
 s/org.jboss.netty/io.netty/ netty * s/.*/debian/ * *
 io.netty netty-transport-native-epoll jar s/.*/debian/ s/linux-x86_64// *
+org.hamcrest s/hamcrest/hamcrest-all/ * s/.*/debian/ * *
+io.netty netty-transport-native-kqueue * s/.*/debian/ * *
diff --git a/debian/patches/remove_classifier.patch b/debian/patches/remove_classifier.patch
new file mode 100644
index 0000000000000000000000000000000000000000..88434652c94cd76a81a890bd15e1abce63007fac
--- /dev/null
+++ b/debian/patches/remove_classifier.patch
@@ -0,0 +1,26 @@
+Description: Remove classifier
+
+Author: Sudip Mukherjee <sudipm.mukherjee@gmail.com>
+
+---
+
+--- async-http-client-2.11.0.orig/client/pom.xml
++++ async-http-client-2.11.0/client/pom.xml
+@@ -58,7 +58,6 @@
+     <dependency>
+       <groupId>io.netty</groupId>
+       <artifactId>netty-transport-native-kqueue</artifactId>
+-      <classifier>osx-x86_64</classifier>
+     </dependency>
+     <dependency>
+       <groupId>org.reactivestreams</groupId>
+--- async-http-client-2.11.0.orig/pom.xml
++++ async-http-client-2.11.0/pom.xml
+@@ -308,7 +308,6 @@
+       <dependency>
+         <groupId>io.netty</groupId>
+         <artifactId>netty-transport-native-kqueue</artifactId>
+-        <classifier>osx-x86_64</classifier>
+         <version>${netty.version}</version>
+         <optional>true</optional>
+       </dependency>
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000000000000000000000000000000000000..694d7963c579ee2a38a9c85d6d27c336704bf64e
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1 @@
+remove_classifier.patch
diff --git a/example/pom.xml b/example/pom.xml
index b137d82b484891517ddcc467a555f2721fd7bc7c..38d3d740c4d23cf2e0cf70a108b2f6eb21ad8a30 100644
--- a/example/pom.xml
+++ b/example/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.asynchttpclient</groupId>
     <artifactId>async-http-client-project</artifactId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>async-http-client-example</artifactId>
@@ -11,6 +11,11 @@
   <description>
     The Async Http Client example.
   </description>
+
+  <properties>
+    <javaModuleName>org.asynchttpclient.example</javaModuleName>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>org.asynchttpclient</groupId>
diff --git a/extras/guava/pom.xml b/extras/guava/pom.xml
index 2097f364a18383818db49c6c68bdb1b0885039bd..ac338ae020db524f0afe4e7ba4622387f6c10c6b 100644
--- a/extras/guava/pom.xml
+++ b/extras/guava/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.asynchttpclient</groupId>
     <artifactId>async-http-client-extras-parent</artifactId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>async-http-client-extras-guava</artifactId>
@@ -11,11 +11,15 @@
     The Async Http Client Guava Extras.
   </description>
 
+  <properties>
+    <javaModuleName>org.asynchttpclient.extras.guava</javaModuleName>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
-      <version>14.0.1</version>
+      <version>28.2-jre</version>
     </dependency>
   </dependencies>
 </project>
\ No newline at end of file
diff --git a/extras/jdeferred/pom.xml b/extras/jdeferred/pom.xml
index 6dba93bd4d358980a62bd6093343e03c90d0e834..12ee456acc547ca3a888e46e2e328f8276914cd8 100644
--- a/extras/jdeferred/pom.xml
+++ b/extras/jdeferred/pom.xml
@@ -18,11 +18,16 @@
   <parent>
     <artifactId>async-http-client-extras-parent</artifactId>
     <groupId>org.asynchttpclient</groupId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
   <artifactId>async-http-client-extras-jdeferred</artifactId>
   <name>Asynchronous Http Client JDeferred Extras</name>
   <description>The Async Http Client jDeffered Extras.</description>
+
+  <properties>
+    <javaModuleName>org.asynchttpclient.extras.jdeferred</javaModuleName>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>org.jdeferred</groupId>
diff --git a/extras/pom.xml b/extras/pom.xml
index ffa65b3ed19d686cfbd3ad0b70fdd4353e733244..caecaa8acab02a91d7b11b5bed9eb3a900cc8ed1 100644
--- a/extras/pom.xml
+++ b/extras/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.asynchttpclient</groupId>
     <artifactId>async-http-client-project</artifactId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>async-http-client-extras-parent</artifactId>
diff --git a/extras/registry/pom.xml b/extras/registry/pom.xml
index ee3a66193fb51846156c20f6409f748922b70926..a0eb2145a8df22ece2d42843d27b162c26cac1f0 100644
--- a/extras/registry/pom.xml
+++ b/extras/registry/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.asynchttpclient</groupId>
     <artifactId>async-http-client-extras-parent</artifactId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>async-http-client-extras-registry</artifactId>
@@ -10,4 +10,9 @@
   <description>
     The Async Http Client Registry Extras.
   </description>
+
+  <properties>
+    <javaModuleName>org.asynchttpclient.extras.registry</javaModuleName>
+  </properties>
+
 </project>
\ No newline at end of file
diff --git a/extras/retrofit2/pom.xml b/extras/retrofit2/pom.xml
index 9c1da9b04ef5bc47500f05d231eacd56661eb2c6..70af053f24a0df6ca5b8382c050baaa01b8ac80c 100644
--- a/extras/retrofit2/pom.xml
+++ b/extras/retrofit2/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <artifactId>async-http-client-extras-parent</artifactId>
     <groupId>org.asynchttpclient</groupId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
 
   <artifactId>async-http-client-extras-retrofit2</artifactId>
@@ -12,8 +12,9 @@
   <description>The Async Http Client Retrofit2 Extras.</description>
 
   <properties>
-    <retrofit2.version>2.4.0</retrofit2.version>
-    <lombok.version>1.16.20</lombok.version>
+    <retrofit2.version>2.7.2</retrofit2.version>
+    <lombok.version>1.18.12</lombok.version>
+    <javaModuleName>org.asynchttpclient.extras.retrofit2</javaModuleName>
   </properties>
 
   <dependencies>
diff --git a/extras/retrofit2/src/main/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCall.java b/extras/retrofit2/src/main/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCall.java
index b9517f9fb1347564b5a662a63b06fd72c7c0a826..d5534a9ce1a96c3bb181556590044016531c9e59 100644
--- a/extras/retrofit2/src/main/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCall.java
+++ b/extras/retrofit2/src/main/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCall.java
@@ -17,6 +17,7 @@ import lombok.*;
 import lombok.extern.slf4j.Slf4j;
 import okhttp3.*;
 import okio.Buffer;
+import okio.Timeout;
 import org.asynchttpclient.AsyncCompletionHandler;
 import org.asynchttpclient.AsyncHttpClient;
 import org.asynchttpclient.RequestBuilder;
@@ -29,6 +30,7 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 /**
  * {@link AsyncHttpClient} <a href="http://square.github.io/retrofit/">Retrofit2</a> {@link okhttp3.Call}
@@ -37,16 +39,9 @@ import java.util.function.Consumer;
 @Value
 @Builder(toBuilder = true)
 @Slf4j
-class AsyncHttpClientCall implements Cloneable, okhttp3.Call {
-  /**
-   * Default {@link #execute()} timeout in milliseconds (value: <b>{@value}</b>)
-   *
-   * @see #execute()
-   * @see #executeTimeoutMillis
-   */
-  public static final long DEFAULT_EXECUTE_TIMEOUT_MILLIS = 30_000;
-
+public class AsyncHttpClientCall implements Cloneable, okhttp3.Call {
   private static final ResponseBody EMPTY_BODY = ResponseBody.create(null, "");
+
   /**
    * Tells whether call has been executed.
    *
@@ -54,37 +49,38 @@ class AsyncHttpClientCall implements Cloneable, okhttp3.Call {
    * @see #isCanceled()
    */
   private final AtomicReference<CompletableFuture<Response>> futureRef = new AtomicReference<>();
+
   /**
-   * HttpClient instance.
+   * {@link AsyncHttpClient} supplier
    */
   @NonNull
-  AsyncHttpClient httpClient;
-  /**
-   * {@link #execute()} response timeout in milliseconds.
-   */
-  @Builder.Default
-  long executeTimeoutMillis = DEFAULT_EXECUTE_TIMEOUT_MILLIS;
+  Supplier<AsyncHttpClient> httpClientSupplier;
+
   /**
    * Retrofit request.
    */
   @NonNull
   @Getter(AccessLevel.NONE)
   Request request;
+
   /**
    * List of consumers that get called just before actual async-http-client request is being built.
    */
   @Singular("requestCustomizer")
   List<Consumer<RequestBuilder>> requestCustomizers;
+
   /**
    * List of consumers that get called just before actual HTTP request is being fired.
    */
   @Singular("onRequestStart")
   List<Consumer<Request>> onRequestStart;
+
   /**
    * List of consumers that get called when HTTP request finishes with an exception.
    */
   @Singular("onRequestFailure")
   List<Consumer<Throwable>> onRequestFailure;
+
   /**
    * List of consumers that get called when HTTP request finishes successfully.
    */
@@ -130,7 +126,7 @@ class AsyncHttpClientCall implements Cloneable, okhttp3.Call {
   @Override
   public Response execute() throws IOException {
     try {
-      return executeHttpRequest().get(getExecuteTimeoutMillis(), TimeUnit.MILLISECONDS);
+      return executeHttpRequest().get(getRequestTimeoutMillis(), TimeUnit.MILLISECONDS);
     } catch (ExecutionException e) {
       throw toIOException(e.getCause());
     } catch (Exception e) {
@@ -148,7 +144,7 @@ class AsyncHttpClientCall implements Cloneable, okhttp3.Call {
   @Override
   public void cancel() {
     val future = futureRef.get();
-    if (future != null) {
+    if (future != null && !future.isDone()) {
       if (!future.cancel(true)) {
         log.warn("Cannot cancel future: {}", future);
       }
@@ -167,6 +163,20 @@ class AsyncHttpClientCall implements Cloneable, okhttp3.Call {
     return future != null && future.isCancelled();
   }
 
+  @Override
+  public Timeout timeout() {
+    return new Timeout().timeout(getRequestTimeoutMillis(), TimeUnit.MILLISECONDS);
+  }
+
+  /**
+   * Returns HTTP request timeout in milliseconds, retrieved from http client configuration.
+   *
+   * @return request timeout in milliseconds.
+   */
+  protected long getRequestTimeoutMillis() {
+    return Math.abs(getHttpClient().getConfig().getRequestTimeout());
+  }
+
   @Override
   public Call clone() {
     return toBuilder().build();
@@ -230,6 +240,20 @@ class AsyncHttpClientCall implements Cloneable, okhttp3.Call {
     return future;
   }
 
+  /**
+   * Returns HTTP client.
+   *
+   * @return http client
+   * @throws IllegalArgumentException if {@link #httpClientSupplier} returned {@code null}.
+   */
+  protected AsyncHttpClient getHttpClient() {
+    val httpClient = httpClientSupplier.get();
+    if (httpClient == null) {
+      throw new IllegalStateException("Async HTTP client instance supplier " + httpClientSupplier + " returned null.");
+    }
+    return httpClient;
+  }
+
   /**
    * Converts async-http-client response to okhttp response.
    *
diff --git a/extras/retrofit2/src/main/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallFactory.java b/extras/retrofit2/src/main/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallFactory.java
index b7c087fd350c2ef86868213e1ad015ca031cf789..0077cd32e3903c9ef09b8eef698320575801f5b6 100644
--- a/extras/retrofit2/src/main/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallFactory.java
+++ b/extras/retrofit2/src/main/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallFactory.java
@@ -19,31 +19,35 @@ import org.asynchttpclient.AsyncHttpClient;
 
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 import static org.asynchttpclient.extras.retrofit.AsyncHttpClientCall.runConsumers;
 
 /**
- * {@link AsyncHttpClient} implementation of Retrofit2 {@link Call.Factory}
+ * {@link AsyncHttpClient} implementation of <a href="http://square.github.io/retrofit/">Retrofit2</a>
+ * {@link Call.Factory}.
  */
 @Value
 @Builder(toBuilder = true)
 public class AsyncHttpClientCallFactory implements Call.Factory {
   /**
-   * {@link AsyncHttpClient} in use.
+   * Supplier of {@link AsyncHttpClient}.
    */
   @NonNull
-  AsyncHttpClient httpClient;
+  @Getter(AccessLevel.NONE)
+  Supplier<AsyncHttpClient> httpClientSupplier;
 
   /**
    * List of {@link Call} builder customizers that are invoked just before creating it.
    */
   @Singular("callCustomizer")
+  @Getter(AccessLevel.PACKAGE)
   List<Consumer<AsyncHttpClientCall.AsyncHttpClientCallBuilder>> callCustomizers;
 
   @Override
   public Call newCall(Request request) {
     val callBuilder = AsyncHttpClientCall.builder()
-            .httpClient(httpClient)
+            .httpClientSupplier(httpClientSupplier)
             .request(request);
 
     // customize builder before creating a call
@@ -52,4 +56,35 @@ public class AsyncHttpClientCallFactory implements Call.Factory {
     // create a call
     return callBuilder.build();
   }
-}
+
+  /**
+   * Returns {@link AsyncHttpClient} from {@link #httpClientSupplier}.
+   *
+   * @return http client.
+   */
+  AsyncHttpClient getHttpClient() {
+    return httpClientSupplier.get();
+  }
+
+  /**
+   * Builder for {@link AsyncHttpClientCallFactory}.
+   */
+  public static class AsyncHttpClientCallFactoryBuilder {
+    /**
+     * {@link AsyncHttpClient} supplier that returns http client to be used to execute HTTP requests.
+     */
+    private Supplier<AsyncHttpClient> httpClientSupplier;
+
+    /**
+     * Sets concrete http client to be used by the factory to execute HTTP requests. Invocation of this method
+     * overrides any previous http client supplier set by {@link #httpClientSupplier(Supplier)}!
+     *
+     * @param httpClient http client
+     * @return reference to itself.
+     * @see #httpClientSupplier(Supplier)
+     */
+    public AsyncHttpClientCallFactoryBuilder httpClient(@NonNull AsyncHttpClient httpClient) {
+      return httpClientSupplier(() -> httpClient);
+    }
+  }
+}
\ No newline at end of file
diff --git a/extras/retrofit2/src/test/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallFactoryTest.java b/extras/retrofit2/src/test/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallFactoryTest.java
index 730ab5d007bd85230525ad54c6dc6304aeaa9a58..4b7605a8134f71ba6e017833d62f04247aab1c7c 100644
--- a/extras/retrofit2/src/test/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallFactoryTest.java
+++ b/extras/retrofit2/src/test/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallFactoryTest.java
@@ -14,23 +14,34 @@ package org.asynchttpclient.extras.retrofit;
 
 import lombok.extern.slf4j.Slf4j;
 import lombok.val;
+import okhttp3.MediaType;
 import okhttp3.Request;
+import okhttp3.RequestBody;
 import okhttp3.Response;
 import org.asynchttpclient.AsyncHttpClient;
 import org.asynchttpclient.RequestBuilder;
 import org.testng.annotations.Test;
 
+import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
-import static org.asynchttpclient.extras.retrofit.AsyncHttpClientCallTest.REQUEST;
 import static org.asynchttpclient.extras.retrofit.AsyncHttpClientCallTest.createConsumer;
 import static org.mockito.Mockito.mock;
 import static org.testng.Assert.*;
 
 @Slf4j
 public class AsyncHttpClientCallFactoryTest {
+  private static final MediaType MEDIA_TYPE = MediaType.parse("application/json");
+  private static final String JSON_BODY = "{\"foo\": \"bar\"}";
+  private static final RequestBody BODY = RequestBody.create(MEDIA_TYPE, JSON_BODY);
+  private static final String URL = "http://localhost:11000/foo/bar?a=b&c=d";
+  private static final Request REQUEST = new Request.Builder()
+          .post(BODY)
+          .addHeader("X-Foo", "Bar")
+          .url(URL)
+          .build();
   @Test
   void newCallShouldProduceExpectedResult() {
     // given
@@ -109,12 +120,12 @@ public class AsyncHttpClientCallFactoryTest {
     };
 
     Consumer<AsyncHttpClientCall.AsyncHttpClientCallBuilder> callCustomizer = callBuilder ->
-      callBuilder
-              .requestCustomizer(requestCustomizer)
-              .requestCustomizer(rb -> log.warn("I'm customizing: {}", rb))
-              .onRequestSuccess(createConsumer(numRequestSuccess))
-              .onRequestFailure(createConsumer(numRequestFailure))
-              .onRequestStart(createConsumer(numRequestStart));
+            callBuilder
+                    .requestCustomizer(requestCustomizer)
+                    .requestCustomizer(rb -> log.warn("I'm customizing: {}", rb))
+                    .onRequestSuccess(createConsumer(numRequestSuccess))
+                    .onRequestFailure(createConsumer(numRequestFailure))
+                    .onRequestStart(createConsumer(numRequestStart));
 
     // create factory
     val factory = AsyncHttpClientCallFactory.builder()
@@ -151,4 +162,65 @@ public class AsyncHttpClientCallFactoryTest {
     assertNotNull(call.getRequestCustomizers());
     assertTrue(call.getRequestCustomizers().size() == 2);
   }
+
+  @Test(expectedExceptions = NullPointerException.class,
+          expectedExceptionsMessageRegExp = "httpClientSupplier is marked non-null but is null")
+  void shouldThrowISEIfHttpClientIsNotDefined() {
+    // given
+    val factory = AsyncHttpClientCallFactory.builder()
+            .build();
+
+    // when
+    val httpClient = factory.getHttpClient();
+
+    // then
+    assertNull(httpClient);
+  }
+
+  @Test
+  void shouldUseHttpClientInstanceIfSupplierIsNotAvailable() {
+    // given
+    val httpClient = mock(AsyncHttpClient.class);
+
+    val factory = AsyncHttpClientCallFactory.builder()
+            .httpClient(httpClient)
+            .build();
+
+    // when
+    val usedHttpClient = factory.getHttpClient();
+
+    // then
+    assertTrue(usedHttpClient == httpClient);
+
+    // when
+    val call = (AsyncHttpClientCall) factory.newCall(REQUEST);
+
+    // then: call should contain correct http client
+    assertTrue(call.getHttpClient()== httpClient);
+  }
+
+  @Test
+  void shouldPreferHttpClientSupplierOverHttpClient() {
+    // given
+    val httpClientA = mock(AsyncHttpClient.class);
+    val httpClientB = mock(AsyncHttpClient.class);
+
+    val factory = AsyncHttpClientCallFactory.builder()
+            .httpClient(httpClientA)
+            .httpClientSupplier(() -> httpClientB)
+            .build();
+
+    // when
+    val usedHttpClient = factory.getHttpClient();
+
+    // then
+    assertTrue(usedHttpClient == httpClientB);
+
+    // when: try to create new call
+    val call = (AsyncHttpClientCall) factory.newCall(REQUEST);
+
+    // then: call should contain correct http client
+    assertNotNull(call);
+    assertTrue(call.getHttpClient() == httpClientB);
+  }
 }
diff --git a/extras/retrofit2/src/test/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallTest.java b/extras/retrofit2/src/test/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallTest.java
index 84ed4ddf470622fa142d938dd1b61e21dddb4371..e655ed73fc5a1ec28a922b6a74a715874b230464 100644
--- a/extras/retrofit2/src/test/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallTest.java
+++ b/extras/retrofit2/src/test/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallTest.java
@@ -14,16 +14,18 @@ package org.asynchttpclient.extras.retrofit;
 
 import io.netty.handler.codec.http.DefaultHttpHeaders;
 import io.netty.handler.codec.http.EmptyHttpHeaders;
-import lombok.val;
-import okhttp3.MediaType;
-import okhttp3.Request;
-import okhttp3.RequestBody;
+import lombok.*;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
 import org.asynchttpclient.AsyncCompletionHandler;
 import org.asynchttpclient.AsyncHttpClient;
+import org.asynchttpclient.AsyncHttpClientConfig;
 import org.asynchttpclient.BoundRequestBuilder;
+import org.asynchttpclient.DefaultAsyncHttpClientConfig;
 import org.asynchttpclient.Response;
 import org.mockito.ArgumentCaptor;
 import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -35,17 +37,32 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.asynchttpclient.extras.retrofit.AsyncHttpClientCall.runConsumer;
 import static org.asynchttpclient.extras.retrofit.AsyncHttpClientCall.runConsumers;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.*;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotEquals;
 import static org.testng.Assert.assertTrue;
 
+@Slf4j
 public class AsyncHttpClientCallTest {
     static final Request REQUEST = new Request.Builder().url("http://www.google.com/").build();
+    static final DefaultAsyncHttpClientConfig DEFAULT_AHC_CONFIG = new DefaultAsyncHttpClientConfig.Builder()
+            .setRequestTimeout(1_000)
+            .build();
+
+    private AsyncHttpClient httpClient;
+    private Supplier<AsyncHttpClient> httpClientSupplier = () -> httpClient;
+
+    @BeforeMethod
+    void setup() {
+      httpClient = mock(AsyncHttpClient.class);
+      when(httpClient.getConfig()).thenReturn(DEFAULT_AHC_CONFIG);
+    }
 
     @Test(expectedExceptions = NullPointerException.class, dataProvider = "first")
     void builderShouldThrowInCaseOfMissingProperties(AsyncHttpClientCall.AsyncHttpClientCallBuilder builder) {
@@ -54,12 +71,10 @@ public class AsyncHttpClientCallTest {
 
     @DataProvider(name = "first")
     Object[][] dataProviderFirst() {
-        val httpClient = mock(AsyncHttpClient.class);
-
         return new Object[][]{
                 {AsyncHttpClientCall.builder()},
                 {AsyncHttpClientCall.builder().request(REQUEST)},
-                {AsyncHttpClientCall.builder().httpClient(httpClient)}
+                {AsyncHttpClientCall.builder().httpClientSupplier(httpClientSupplier)}
         };
     }
 
@@ -77,7 +92,7 @@ public class AsyncHttpClientCallTest {
         val numRequestCustomizer = new AtomicInteger();
 
         // prepare http client mock
-        val httpClient = mock(AsyncHttpClient.class);
+        this.httpClient = mock(AsyncHttpClient.class);
 
         val mockRequest = mock(org.asynchttpclient.Request.class);
         when(mockRequest.getHeaders()).thenReturn(EmptyHttpHeaders.INSTANCE);
@@ -94,13 +109,12 @@ public class AsyncHttpClientCallTest {
 
         // create call instance
         val call = AsyncHttpClientCall.builder()
-                .httpClient(httpClient)
+                .httpClientSupplier(httpClientSupplier)
                 .request(REQUEST)
                 .onRequestStart(e -> numStarted.incrementAndGet())
                 .onRequestFailure(t -> numFailed.incrementAndGet())
                 .onRequestSuccess(r -> numOk.incrementAndGet())
                 .requestCustomizer(rb -> numRequestCustomizer.incrementAndGet())
-                .executeTimeoutMillis(1000)
                 .build();
 
         // when
@@ -163,7 +177,7 @@ public class AsyncHttpClientCallTest {
     void toIOExceptionShouldProduceExpectedResult(Throwable exception) {
         // given
         val call = AsyncHttpClientCall.builder()
-                .httpClient(mock(AsyncHttpClient.class))
+                .httpClientSupplier(httpClientSupplier)
                 .request(REQUEST)
                 .build();
 
@@ -237,13 +251,12 @@ public class AsyncHttpClientCallTest {
         Request request = requestWithBody();
 
         ArgumentCaptor<org.asynchttpclient.Request> capture = ArgumentCaptor.forClass(org.asynchttpclient.Request.class);
-        AsyncHttpClient client = mock(AsyncHttpClient.class);
 
-        givenResponseIsProduced(client, aResponse());
+        givenResponseIsProduced(httpClient, aResponse());
 
-        whenRequestIsMade(client, request);
+        whenRequestIsMade(httpClient, request);
 
-        verify(client).executeRequest(capture.capture(), any());
+        verify(httpClient).executeRequest(capture.capture(), any());
 
         org.asynchttpclient.Request ahcRequest = capture.getValue();
 
@@ -255,11 +268,9 @@ public class AsyncHttpClientCallTest {
 
     @Test
     public void contenTypeIsOptionalInResponse() throws Exception {
-        AsyncHttpClient client = mock(AsyncHttpClient.class);
+        givenResponseIsProduced(httpClient, responseWithBody(null, "test"));
 
-        givenResponseIsProduced(client, responseWithBody(null, "test"));
-
-        okhttp3.Response response = whenRequestIsMade(client, REQUEST);
+        okhttp3.Response response = whenRequestIsMade(httpClient, REQUEST);
 
         assertEquals(response.code(), 200);
         assertEquals(response.header("Server"), "nginx");
@@ -269,11 +280,9 @@ public class AsyncHttpClientCallTest {
 
     @Test
     public void contentTypeIsProperlyParsedIfPresent() throws Exception {
-        AsyncHttpClient client = mock(AsyncHttpClient.class);
-
-        givenResponseIsProduced(client, responseWithBody("text/plain", "test"));
+        givenResponseIsProduced(httpClient, responseWithBody("text/plain", "test"));
 
-        okhttp3.Response response = whenRequestIsMade(client, REQUEST);
+        okhttp3.Response response = whenRequestIsMade(httpClient, REQUEST);
 
         assertEquals(response.code(), 200);
         assertEquals(response.header("Server"), "nginx");
@@ -284,17 +293,71 @@ public class AsyncHttpClientCallTest {
 
     @Test
     public void bodyIsNotNullInResponse() throws Exception {
-        AsyncHttpClient client = mock(AsyncHttpClient.class);
-
-        givenResponseIsProduced(client, responseWithNoBody());
+        givenResponseIsProduced(httpClient, responseWithNoBody());
 
-        okhttp3.Response response = whenRequestIsMade(client, REQUEST);
+        okhttp3.Response response = whenRequestIsMade(httpClient, REQUEST);
 
         assertEquals(response.code(), 200);
         assertEquals(response.header("Server"), "nginx");
         assertNotEquals(response.body(), null);
     }
 
+    @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = ".*returned null.")
+    void getHttpClientShouldThrowISEIfSupplierReturnsNull() {
+      // given:
+      val call = AsyncHttpClientCall.builder()
+              .httpClientSupplier(() -> null)
+              .request(requestWithBody())
+              .build();
+
+      // when: should throw ISE
+      call.getHttpClient();
+    }
+
+    @Test
+    void shouldReturnTimeoutSpecifiedInAHCInstanceConfig() {
+        // given:
+        val cfgBuilder = new DefaultAsyncHttpClientConfig.Builder();
+        AsyncHttpClientConfig config = null;
+
+        // and: setup call
+        val call = AsyncHttpClientCall.builder()
+                .httpClientSupplier(httpClientSupplier)
+                .request(requestWithBody())
+                .build();
+
+        // when: set read timeout to 5s, req timeout to 6s
+        config = cfgBuilder.setReadTimeout((int) SECONDS.toMillis(5))
+                .setRequestTimeout((int) SECONDS.toMillis(6))
+                .build();
+        when(httpClient.getConfig()).thenReturn(config);
+
+        // then: expect request timeout
+        assertEquals(call.getRequestTimeoutMillis(), SECONDS.toMillis(6));
+        assertEquals(call.timeout().timeoutNanos(), SECONDS.toNanos(6));
+
+        // when: set read timeout to 10 seconds, req timeout to 7s
+        config = cfgBuilder.setReadTimeout((int) SECONDS.toMillis(10))
+                .setRequestTimeout((int) SECONDS.toMillis(7))
+                .build();
+        when(httpClient.getConfig()).thenReturn(config);
+
+        // then: expect request timeout
+        assertEquals(call.getRequestTimeoutMillis(), SECONDS.toMillis(7));
+        assertEquals(call.timeout().timeoutNanos(), SECONDS.toNanos(7));
+
+        // when: set request timeout to a negative value, just for fun.
+        config = cfgBuilder.setRequestTimeout(-1000)
+                .setReadTimeout(2000)
+                .build();
+
+        when(httpClient.getConfig()).thenReturn(config);
+
+        // then: expect request timeout, but as positive value
+        assertEquals(call.getRequestTimeoutMillis(), SECONDS.toMillis(1));
+        assertEquals(call.timeout().timeoutNanos(), SECONDS.toNanos(1));
+    }
+
     private void givenResponseIsProduced(AsyncHttpClient client, Response response) {
         when(client.executeRequest(any(org.asynchttpclient.Request.class), any())).thenAnswer(invocation -> {
             AsyncCompletionHandler<Response> handler = invocation.getArgument(1);
@@ -304,9 +367,11 @@ public class AsyncHttpClientCallTest {
     }
 
     private okhttp3.Response whenRequestIsMade(AsyncHttpClient client, Request request) throws IOException {
-        AsyncHttpClientCall call = AsyncHttpClientCall.builder().httpClient(client).request(request).build();
-
-        return call.execute();
+        return AsyncHttpClientCall.builder()
+                .httpClientSupplier(() -> client)
+                .request(request)
+                .build()
+                .execute();
     }
 
     private Request requestWithBody() {
diff --git a/extras/rxjava/pom.xml b/extras/rxjava/pom.xml
index 6f475651cff5288a340c23e56e54bedc444a5672..082cdc947db848353222e4199711dbb6f36d7ba3 100644
--- a/extras/rxjava/pom.xml
+++ b/extras/rxjava/pom.xml
@@ -3,11 +3,16 @@
   <parent>
     <artifactId>async-http-client-extras-parent</artifactId>
     <groupId>org.asynchttpclient</groupId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
   <artifactId>async-http-client-extras-rxjava</artifactId>
   <name>Asynchronous Http Client RxJava Extras</name>
   <description>The Async Http Client RxJava Extras.</description>
+
+  <properties>
+    <javaModuleName>org.asynchttpclient.extras.rxjava</javaModuleName>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>io.reactivex</groupId>
diff --git a/extras/rxjava2/pom.xml b/extras/rxjava2/pom.xml
index ad28f39c47a9a496f9a7225e598644682e8a76f7..6ae078a7336bf0be688bdae7f9f4fe69431f6ba5 100644
--- a/extras/rxjava2/pom.xml
+++ b/extras/rxjava2/pom.xml
@@ -3,11 +3,16 @@
   <parent>
     <artifactId>async-http-client-extras-parent</artifactId>
     <groupId>org.asynchttpclient</groupId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
   <artifactId>async-http-client-extras-rxjava2</artifactId>
   <name>Asynchronous Http Client RxJava2 Extras</name>
   <description>The Async Http Client RxJava2 Extras.</description>
+
+  <properties>
+    <javaModuleName>org.asynchttpclient.extras.rxjava2</javaModuleName>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>io.reactivex.rxjava2</groupId>
diff --git a/extras/rxjava2/src/main/java/org/asynchttpclient/extras/rxjava2/maybe/AbstractMaybeAsyncHandlerBridge.java b/extras/rxjava2/src/main/java/org/asynchttpclient/extras/rxjava2/maybe/AbstractMaybeAsyncHandlerBridge.java
index bf366f820078cffb9dffeb8c45d21ebfa50ed29f..6a5f8dca7a737723be212230023719b35cd0ee51 100644
--- a/extras/rxjava2/src/main/java/org/asynchttpclient/extras/rxjava2/maybe/AbstractMaybeAsyncHandlerBridge.java
+++ b/extras/rxjava2/src/main/java/org/asynchttpclient/extras/rxjava2/maybe/AbstractMaybeAsyncHandlerBridge.java
@@ -13,6 +13,7 @@
  */
 package org.asynchttpclient.extras.rxjava2.maybe;
 
+import io.netty.channel.Channel;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.reactivex.MaybeEmitter;
 import io.reactivex.exceptions.CompositeException;
@@ -21,10 +22,14 @@ import org.asynchttpclient.AsyncHandler;
 import org.asynchttpclient.HttpResponseBodyPart;
 import org.asynchttpclient.HttpResponseStatus;
 import org.asynchttpclient.extras.rxjava2.DisposedException;
+import org.asynchttpclient.netty.request.NettyRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.net.ssl.SSLSession;
+import java.net.InetSocketAddress;
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.util.Objects.requireNonNull;
@@ -144,6 +149,76 @@ public abstract class AbstractMaybeAsyncHandlerBridge<T> implements AsyncHandler
     emitOnError(error);
   }
 
+  @Override
+  public void onHostnameResolutionAttempt(String name) {
+    executeUnlessEmitterDisposed(() -> delegate().onHostnameResolutionAttempt(name));
+  }
+
+  @Override
+  public void onHostnameResolutionSuccess(String name, List<InetSocketAddress> addresses) {
+    executeUnlessEmitterDisposed(() -> delegate().onHostnameResolutionSuccess(name, addresses));
+  }
+
+  @Override
+  public void onHostnameResolutionFailure(String name, Throwable cause) {
+    executeUnlessEmitterDisposed(() -> delegate().onHostnameResolutionFailure(name, cause));
+  }
+
+  @Override
+  public void onTcpConnectAttempt(InetSocketAddress remoteAddress) {
+    executeUnlessEmitterDisposed(() -> delegate().onTcpConnectAttempt(remoteAddress));
+  }
+
+  @Override
+  public void onTcpConnectSuccess(InetSocketAddress remoteAddress, Channel connection) {
+    executeUnlessEmitterDisposed(() -> delegate().onTcpConnectSuccess(remoteAddress, connection));
+  }
+
+  @Override
+  public void onTcpConnectFailure(InetSocketAddress remoteAddress, Throwable cause) {
+    executeUnlessEmitterDisposed(() -> delegate().onTcpConnectFailure(remoteAddress, cause));
+  }
+
+  @Override
+  public void onTlsHandshakeAttempt() {
+    executeUnlessEmitterDisposed(() -> delegate().onTlsHandshakeAttempt());
+  }
+
+  @Override
+  public void onTlsHandshakeSuccess(SSLSession sslSession) {
+    executeUnlessEmitterDisposed(() -> delegate().onTlsHandshakeSuccess(sslSession));
+  }
+
+  @Override
+  public void onTlsHandshakeFailure(Throwable cause) {
+    executeUnlessEmitterDisposed(() -> delegate().onTlsHandshakeFailure(cause));
+  }
+
+  @Override
+  public void onConnectionPoolAttempt() {
+    executeUnlessEmitterDisposed(() -> delegate().onConnectionPoolAttempt());
+  }
+
+  @Override
+  public void onConnectionPooled(Channel connection) {
+    executeUnlessEmitterDisposed(() -> delegate().onConnectionPooled(connection));
+  }
+
+  @Override
+  public void onConnectionOffer(Channel connection) {
+    executeUnlessEmitterDisposed(() -> delegate().onConnectionOffer(connection));
+  }
+
+  @Override
+  public void onRequestSend(NettyRequest request) {
+    executeUnlessEmitterDisposed(() -> delegate().onRequestSend(request));
+  }
+
+  @Override
+  public void onRetry() {
+    executeUnlessEmitterDisposed(() -> delegate().onRetry());
+  }
+
   /**
    * Called to indicate that request processing is to be aborted because the linked Rx stream has been disposed. If
    * the {@link #delegate() delegate} didn't already receive a terminal event,
@@ -184,4 +259,12 @@ public abstract class AbstractMaybeAsyncHandlerBridge<T> implements AsyncHandler
       LOGGER.debug("Not propagating onError after disposal: {}", error.getMessage(), error);
     }
   }
+
+  private void executeUnlessEmitterDisposed(Runnable runnable) {
+    if (emitter.isDisposed()) {
+      disposed();
+    } else {
+      runnable.run();
+    }
+  }
 }
diff --git a/extras/rxjava2/src/test/java/org/asynchttpclient/extras/rxjava2/maybe/AbstractMaybeAsyncHandlerBridgeTest.java b/extras/rxjava2/src/test/java/org/asynchttpclient/extras/rxjava2/maybe/AbstractMaybeAsyncHandlerBridgeTest.java
index b8a9b3b4e40526f19b5c9e4714e0a453ec7c1cb0..5c14778e1c77dce2c4dbe14afc8056e24e24f373 100644
--- a/extras/rxjava2/src/test/java/org/asynchttpclient/extras/rxjava2/maybe/AbstractMaybeAsyncHandlerBridgeTest.java
+++ b/extras/rxjava2/src/test/java/org/asynchttpclient/extras/rxjava2/maybe/AbstractMaybeAsyncHandlerBridgeTest.java
@@ -13,6 +13,7 @@
  */
 package org.asynchttpclient.extras.rxjava2.maybe;
 
+import io.netty.channel.Channel;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.reactivex.MaybeEmitter;
 import io.reactivex.exceptions.CompositeException;
@@ -26,7 +27,10 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import javax.net.ssl.SSLSession;
+import java.net.InetSocketAddress;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Callable;
 
@@ -35,10 +39,6 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.mockito.BDDMockito.*;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.only;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 
 public class AbstractMaybeAsyncHandlerBridgeTest {
 
@@ -57,6 +57,20 @@ public class AbstractMaybeAsyncHandlerBridgeTest {
   @Mock
   private HttpResponseBodyPart bodyPart;
 
+  private final String hostname = "service:8080";
+
+  @Mock
+  private InetSocketAddress remoteAddress;
+
+  @Mock
+  private Channel channel;
+
+  @Mock
+  private SSLSession sslSession;
+
+  @Mock
+  private Throwable error;
+
   @Captor
   private ArgumentCaptor<Throwable> throwable;
 
@@ -76,6 +90,20 @@ public class AbstractMaybeAsyncHandlerBridgeTest {
     };
   }
 
+  private static Runnable named(String name, Runnable runnable) {
+    return new Runnable() {
+      @Override
+      public String toString() {
+        return name;
+      }
+
+      @Override
+      public void run() {
+        runnable.run();
+      }
+    };
+  }
+
   @BeforeMethod
   public void initializeTest() {
     MockitoAnnotations.initMocks(this);
@@ -104,10 +132,68 @@ public class AbstractMaybeAsyncHandlerBridgeTest {
     underTest.onTrailingHeadersReceived(headers);
     then(delegate).should().onTrailingHeadersReceived(headers);
 
+    /* when */
+    underTest.onHostnameResolutionAttempt(hostname);
+    then(delegate).should().onHostnameResolutionAttempt(hostname);
+
+    /* when */
+    List<InetSocketAddress> remoteAddresses = Collections.singletonList(remoteAddress);
+    underTest.onHostnameResolutionSuccess(hostname, remoteAddresses);
+    then(delegate).should().onHostnameResolutionSuccess(hostname, remoteAddresses);
+
+    /* when */
+    underTest.onHostnameResolutionFailure(hostname, error);
+    then(delegate).should().onHostnameResolutionFailure(hostname, error);
+
+    /* when */
+    underTest.onTcpConnectAttempt(remoteAddress);
+    then(delegate).should().onTcpConnectAttempt(remoteAddress);
+
+    /* when */
+    underTest.onTcpConnectSuccess(remoteAddress, channel);
+    then(delegate).should().onTcpConnectSuccess(remoteAddress, channel);
+
+    /* when */
+    underTest.onTcpConnectFailure(remoteAddress, error);
+    then(delegate).should().onTcpConnectFailure(remoteAddress, error);
+
+    /* when */
+    underTest.onTlsHandshakeAttempt();
+    then(delegate).should().onTlsHandshakeAttempt();
+
+    /* when */
+    underTest.onTlsHandshakeSuccess(sslSession);
+    then(delegate).should().onTlsHandshakeSuccess(sslSession);
+
+    /* when */
+    underTest.onTlsHandshakeFailure(error);
+    then(delegate).should().onTlsHandshakeFailure(error);
+
+    /* when */
+    underTest.onConnectionPoolAttempt();
+    then(delegate).should().onConnectionPoolAttempt();
+
+    /* when */
+    underTest.onConnectionPooled(channel);
+    then(delegate).should().onConnectionPooled(channel);
+
+    /* when */
+    underTest.onConnectionOffer(channel);
+    then(delegate).should().onConnectionOffer(channel);
+
+    /* when */
+    underTest.onRequestSend(null);
+    then(delegate).should().onRequestSend(null);
+
+    /* when */
+    underTest.onRetry();
+    then(delegate).should().onRetry();
+
     /* when */
     underTest.onCompleted();
     then(delegate).should().onCompleted();
     then(emitter).should().onSuccess(this);
+
     /* then */
     verifyNoMoreInteractions(delegate);
   }
@@ -254,6 +340,42 @@ public class AbstractMaybeAsyncHandlerBridgeTest {
     verifyNoMoreInteractions(delegate);
   }
 
+  @DataProvider
+  public Object[][] variousEvents() {
+    return new Object[][]{
+            {named("onHostnameResolutionAttempt", () -> underTest.onHostnameResolutionAttempt("service:8080"))},
+            {named("onHostnameResolutionSuccess", () -> underTest.onHostnameResolutionSuccess("service:8080",
+                    Collections.singletonList(remoteAddress)))},
+            {named("onHostnameResolutionFailure", () -> underTest.onHostnameResolutionFailure("service:8080", error))},
+            {named("onTcpConnectAttempt", () -> underTest.onTcpConnectAttempt(remoteAddress))},
+            {named("onTcpConnectSuccess", () -> underTest.onTcpConnectSuccess(remoteAddress, channel))},
+            {named("onTcpConnectFailure", () -> underTest.onTcpConnectFailure(remoteAddress, error))},
+            {named("onTlsHandshakeAttempt", () -> underTest.onTlsHandshakeAttempt())},
+            {named("onTlsHandshakeSuccess", () -> underTest.onTlsHandshakeSuccess(sslSession))},
+            {named("onTlsHandshakeFailure", () -> underTest.onTlsHandshakeFailure(error))},
+            {named("onConnectionPoolAttempt", () -> underTest.onConnectionPoolAttempt())},
+            {named("onConnectionPooled", () -> underTest.onConnectionPooled(channel))},
+            {named("onConnectionOffer", () -> underTest.onConnectionOffer(channel))},
+            {named("onRequestSend", () -> underTest.onRequestSend(null))},
+            {named("onRetry", () -> underTest.onRetry())},
+    };
+  }
+
+  @Test(dataProvider = "variousEvents")
+  public void variousEventCallbacksCheckDisposal(Runnable event) {
+    given(emitter.isDisposed()).willReturn(true);
+
+    /* when */
+    event.run();
+    /* then */
+    then(delegate).should(only()).onThrowable(isA(DisposedException.class));
+
+    /* when */
+    event.run();
+    /* then */
+    verifyNoMoreInteractions(delegate);
+  }
+
   private final class UnderTest extends AbstractMaybeAsyncHandlerBridge<Object> {
     UnderTest() {
       super(AbstractMaybeAsyncHandlerBridgeTest.this.emitter);
diff --git a/extras/simple/pom.xml b/extras/simple/pom.xml
index e91d11634b89d153f8116fba8acdfca866f1ec7e..17634f855e52acb6a6e4d3645b09ee593d1c4d47 100644
--- a/extras/simple/pom.xml
+++ b/extras/simple/pom.xml
@@ -3,9 +3,14 @@
   <parent>
     <artifactId>async-http-client-extras-parent</artifactId>
     <groupId>org.asynchttpclient</groupId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
   <artifactId>async-http-client-extras-simple</artifactId>
   <name>Asynchronous Http Simple Client</name>
   <description>The Async Http Simple Client.</description>
+
+  <properties>
+    <javaModuleName>org.asynchttpclient.extras.simple</javaModuleName>
+  </properties>
+
 </project>
diff --git a/extras/simple/src/main/java/org/asynchttpclient/extras/simple/SimpleAsyncHttpClient.java b/extras/simple/src/main/java/org/asynchttpclient/extras/simple/SimpleAsyncHttpClient.java
index 8d5bf18afebaf828018221cd3b0bd16a5924f44f..b1926b39882f4af6585bf1db77ee0b499b893c6d 100644
--- a/extras/simple/src/main/java/org/asynchttpclient/extras/simple/SimpleAsyncHttpClient.java
+++ b/extras/simple/src/main/java/org/asynchttpclient/extras/simple/SimpleAsyncHttpClient.java
@@ -275,7 +275,7 @@ public class SimpleAsyncHttpClient implements Closeable {
   }
 
   private RequestBuilder rebuildRequest(Request rb) {
-    return new RequestBuilder(rb);
+    return rb.toBuilder();
   }
 
   private Future<Response> execute(RequestBuilder rb, BodyConsumer bodyConsumer, ThrowableHandler throwableHandler) throws IOException {
@@ -422,7 +422,7 @@ public class SimpleAsyncHttpClient implements Closeable {
     }
 
     private Builder(SimpleAsyncHttpClient client) {
-      this.requestBuilder = new RequestBuilder(client.requestBuilder.build());
+      this.requestBuilder = client.requestBuilder.build().toBuilder();
       this.defaultThrowableHandler = client.defaultThrowableHandler;
       this.errorDocumentBehaviour = client.errorDocumentBehaviour;
       this.enableResumableDownload = client.resumeEnabled;
diff --git a/extras/typesafeconfig/README.md b/extras/typesafeconfig/README.md
index 3078cac2d51412f79b315edcc4e84d68bc01ff8f..dcc29dc2693d03114993912ace90158f6687b666 100644
--- a/extras/typesafeconfig/README.md
+++ b/extras/typesafeconfig/README.md
@@ -8,7 +8,7 @@ Download [the latest JAR][2] or grab via [Maven][3]:
 ```xml
 <dependency>
   <groupId>org.asynchttpclient</groupId>
-  <artifactId>async-http-client-extras-typesafeconfig</artifactId>
+  <artifactId>async-http-client-extras-typesafe-config</artifactId>
   <version>latest.version</version>
 </dependency>
 ```
@@ -16,12 +16,12 @@ Download [the latest JAR][2] or grab via [Maven][3]:
 or [Gradle][3]:
 
 ```groovy
-compile "org.asynchttpclient:async-http-client-extras-typesafeconfig:latest.version"
+compile "org.asynchttpclient:async-http-client-extras-typesafe-config:latest.version"
 ```
 
  [1]: https://github.com/lightbend/config
- [2]: https://search.maven.org/remote_content?g=org.asynchttpclient&a=async-http-client-extras-typesafeconfig&v=LATEST
- [3]: http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.asynchttpclient%22%20a%3A%22async-http-client-extras-typesafeconfig%22
+ [2]: https://search.maven.org/remote_content?g=org.asynchttpclient&a=async-http-client-extras-typesafe-config&v=LATEST
+ [3]: http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.asynchttpclient%22%20a%3A%22async-http-client-extras-typesafe-config%22
  [snap]: https://oss.sonatype.org/content/repositories/snapshots/
 
 ## Example usage
@@ -31,4 +31,4 @@ compile "org.asynchttpclient:async-http-client-extras-typesafeconfig:latest.vers
 com.typesafe.config.Config config = ...
 AsyncHttpClientTypesafeConfig ahcConfig = new AsyncHttpClientTypesafeConfig(config);
 AsyncHttpClient client = new DefaultAsyncHttpClient(ahcConfig);
-```
\ No newline at end of file
+```
diff --git a/extras/typesafeconfig/pom.xml b/extras/typesafeconfig/pom.xml
index 34914084013686ff2c33eae450e610c033b268b0..ef0717daf4935d033840efef62cdc36b8331c7a5 100644
--- a/extras/typesafeconfig/pom.xml
+++ b/extras/typesafeconfig/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <artifactId>async-http-client-extras-parent</artifactId>
     <groupId>org.asynchttpclient</groupId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
 
   <artifactId>async-http-client-extras-typesafe-config</artifactId>
@@ -13,6 +13,7 @@
 
   <properties>
     <typesafeconfig.version>1.3.3</typesafeconfig.version>
+    <javaModuleName>org.asynchttpclient.extras.typesafeconfig</javaModuleName>
   </properties>
 
   <dependencies>
diff --git a/extras/typesafeconfig/src/main/java/org/asynchttpclient/extras/typesafeconfig/AsyncHttpClientTypesafeConfig.java b/extras/typesafeconfig/src/main/java/org/asynchttpclient/extras/typesafeconfig/AsyncHttpClientTypesafeConfig.java
index 8917052611469ec5ab99806f9a2e4aa476bb2258..5875c16b70d896c4f77e08800d0da7d4e300b26c 100644
--- a/extras/typesafeconfig/src/main/java/org/asynchttpclient/extras/typesafeconfig/AsyncHttpClientTypesafeConfig.java
+++ b/extras/typesafeconfig/src/main/java/org/asynchttpclient/extras/typesafeconfig/AsyncHttpClientTypesafeConfig.java
@@ -69,6 +69,11 @@ public class AsyncHttpClientTypesafeConfig implements AsyncHttpClientConfig {
     return getIntegerOpt(MAX_CONNECTIONS_PER_HOST_CONFIG).orElse(defaultMaxConnectionsPerHost());
   }
 
+  @Override
+  public int getAcquireFreeChannelTimeout() {
+    return getIntegerOpt(ACQUIRE_FREE_CHANNEL_TIMEOUT).orElse(defaultAcquireFreeChannelTimeout());
+  }
+
   @Override
   public int getConnectTimeout() {
     return getIntegerOpt(CONNECTION_TIMEOUT_CONFIG).orElse(defaultConnectTimeout());
@@ -364,6 +369,11 @@ public class AsyncHttpClientTypesafeConfig implements AsyncHttpClientConfig {
     return getBooleanOpt(SO_REUSE_ADDRESS_CONFIG).orElse(defaultSoReuseAddress());
   }
 
+  @Override
+  public boolean isSoKeepAlive() {
+    return getBooleanOpt(SO_KEEP_ALIVE_CONFIG).orElse(defaultSoKeepAlive());
+  }
+
   @Override
   public int getSoLinger() {
     return getIntegerOpt(SO_LINGER_CONFIG).orElse(defaultSoLinger());
@@ -407,7 +417,7 @@ public class AsyncHttpClientTypesafeConfig implements AsyncHttpClientConfig {
 
   private <T> Optional<T> getOpt(Function<String, T> func, String key) {
     return config.hasPath(key)
-        ? Optional.ofNullable(func.apply(key))
-        : Optional.empty();
+            ? Optional.ofNullable(func.apply(key))
+            : Optional.empty();
   }
 }
diff --git a/netty-utils/pom.xml b/netty-utils/pom.xml
index 8a8f85c87b3a8606c1538bb347b4bea6a9e1a19f..0635d4e92675e63e9ebbf2768b803799a7e9d163 100644
--- a/netty-utils/pom.xml
+++ b/netty-utils/pom.xml
@@ -2,12 +2,16 @@
   <parent>
     <groupId>org.asynchttpclient</groupId>
     <artifactId>async-http-client-project</artifactId>
-    <version>2.6.0</version>
+    <version>2.11.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>async-http-client-netty-utils</artifactId>
   <name>Asynchronous Http Client Netty Utils</name>
 
+  <properties>
+    <javaModuleName>org.asynchttpclient.utils</javaModuleName>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>io.netty</groupId>
diff --git a/netty-utils/src/test/java/org/asynchttpclient/netty/util/ByteBufUtilsTests.java b/netty-utils/src/test/java/org/asynchttpclient/netty/util/ByteBufUtilsTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..4aaa61c8af5880aae267fa9943ac36a336137beb
--- /dev/null
+++ b/netty-utils/src/test/java/org/asynchttpclient/netty/util/ByteBufUtilsTests.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2019 AsyncHttpClient Project. 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.netty.util;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.nio.charset.Charset;
+import org.testng.annotations.Test;
+import org.testng.Assert;
+import org.testng.internal.junit.ArrayAsserts;
+
+public class ByteBufUtilsTests {
+
+    @Test
+    public void testByteBuf2BytesEmptyByteBuf() {
+        ByteBuf buf = Unpooled.buffer();
+
+        try {
+            ArrayAsserts.assertArrayEquals(new byte[]{},
+                    ByteBufUtils.byteBuf2Bytes(buf));
+        } finally {
+            buf.release();
+        }
+    }
+
+    @Test
+    public void testByteBuf2BytesNotEmptyByteBuf() {
+        ByteBuf byteBuf = Unpooled.wrappedBuffer(new byte[]{'f', 'o', 'o'});
+
+        try {
+            ArrayAsserts.assertArrayEquals(new byte[]{'f', 'o', 'o'},
+                    ByteBufUtils.byteBuf2Bytes(byteBuf));
+        } finally {
+            byteBuf.release();
+        }
+    }
+
+    @Test
+    public void testByteBuf2String() {
+        ByteBuf byteBuf = Unpooled.wrappedBuffer(new byte[]{'f', 'o', 'o'});
+        Charset charset = Charset.forName("US-ASCII");
+
+        try {
+            Assert.assertEquals(
+                    ByteBufUtils.byteBuf2String(charset, byteBuf), "foo");
+        } finally {
+            byteBuf.release();
+        }
+    }
+
+    @Test
+    public void testByteBuf2StringWithByteBufArray() {
+        ByteBuf byteBuf1 = Unpooled.wrappedBuffer(new byte[]{'f'});
+        ByteBuf byteBuf2 = Unpooled.wrappedBuffer(new byte[]{'o', 'o'});
+
+        try {
+            Assert.assertEquals(ByteBufUtils.byteBuf2String(
+                    Charset.forName("ISO-8859-1"), byteBuf1, byteBuf2), "foo");
+        } finally {
+            byteBuf1.release();
+            byteBuf2.release();
+        }
+    }
+
+    @Test
+    public void testByteBuf2Chars() {
+        ByteBuf byteBuf1 = Unpooled.wrappedBuffer(new byte[]{});
+        ByteBuf byteBuf2 = Unpooled.wrappedBuffer(new byte[]{'o'});
+
+        try {
+            ArrayAsserts.assertArrayEquals(new char[]{}, ByteBufUtils
+                    .byteBuf2Chars(Charset.forName("US-ASCII"), byteBuf1));
+            ArrayAsserts.assertArrayEquals(new char[]{}, ByteBufUtils
+                    .byteBuf2Chars(Charset.forName("ISO-8859-1"), byteBuf1));
+            ArrayAsserts.assertArrayEquals(new char[]{'o'}, ByteBufUtils
+                    .byteBuf2Chars(Charset.forName("ISO-8859-1"), byteBuf2));
+        } finally {
+            byteBuf1.release();
+            byteBuf2.release();
+        }
+    }
+
+    @Test
+    public void testByteBuf2CharsWithByteBufArray() {
+        ByteBuf byteBuf1 = Unpooled.wrappedBuffer(new byte[]{'f', 'o'});
+        ByteBuf byteBuf2 = Unpooled.wrappedBuffer(new byte[]{'%', '*'});
+
+        try {
+            ArrayAsserts.assertArrayEquals(new char[]{'f', 'o', '%', '*'},
+                    ByteBufUtils.byteBuf2Chars(Charset.forName("US-ASCII"),
+                            byteBuf1, byteBuf2));
+            ArrayAsserts.assertArrayEquals(new char[]{'f', 'o', '%', '*'},
+                    ByteBufUtils.byteBuf2Chars(Charset.forName("ISO-8859-1"),
+                            byteBuf1, byteBuf2));
+        } finally {
+            byteBuf1.release();
+            byteBuf2.release();
+        }
+    }
+
+    @Test
+    public void testByteBuf2CharsWithEmptyByteBufArray() {
+        ByteBuf byteBuf1 = Unpooled.wrappedBuffer(new byte[]{});
+        ByteBuf byteBuf2 = Unpooled.wrappedBuffer(new byte[]{'o'});
+
+        try {
+            ArrayAsserts.assertArrayEquals(new char[]{'o'}, ByteBufUtils
+                    .byteBuf2Chars(Charset.forName("ISO-8859-1"),
+                            byteBuf1, byteBuf2));
+        } finally {
+            byteBuf1.release();
+            byteBuf2.release();
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index 94f322c79740c93dafaea01bd770532b30fbec23..dc8d77b68c99e198410bbea5f90ada244bd74d9b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,31 +1,58 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <parent>
-    <groupId>org.sonatype.oss</groupId>
-    <artifactId>oss-parent</artifactId>
-    <version>9</version>
-  </parent>
   <modelVersion>4.0.0</modelVersion>
+
   <groupId>org.asynchttpclient</groupId>
   <artifactId>async-http-client-project</artifactId>
-  <name>Asynchronous Http Client Project</name>
-  <version>2.6.0</version>
+  <version>2.11.0</version>
   <packaging>pom</packaging>
+
+  <name>Asynchronous Http Client Project</name>
   <description>
     The Async Http Client (AHC) library's purpose is to allow Java
     applications to easily execute HTTP requests and
     asynchronously process the response.
   </description>
   <url>http://github.com/AsyncHttpClient/async-http-client</url>
+
+  <licenses>
+    <license>
+      <name>The Apache Software License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+    </license>
+  </licenses>
+
+  <developers>
+    <developer>
+      <id>slandelle</id>
+      <name>Stephane Landelle</name>
+      <email>slandelle@gatling.io</email>
+    </developer>
+  </developers>
+
   <scm>
-    <url>https://github.com/AsyncHttpClient/async-http-client</url>
     <connection>scm:git:git@github.com:AsyncHttpClient/async-http-client.git</connection>
     <developerConnection>scm:git:git@github.com:AsyncHttpClient/async-http-client.git</developerConnection>
+    <url>https://github.com/AsyncHttpClient/async-http-client/tree/master</url>
+    <tag>async-http-client-project-2.11.0</tag>
   </scm>
+
+  <distributionManagement>
+    <snapshotRepository>
+      <id>sonatype-nexus-staging</id>
+      <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+    </snapshotRepository>
+    <repository>
+      <id>sonatype-nexus-staging</id>
+      <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+    </repository>
+  </distributionManagement>
+
   <issueManagement>
-    <system>jira</system>
-    <url>https://issues.sonatype.org/browse/AHC</url>
+    <system>github</system>
+    <url>https://github.com/AsyncHttpClient/async-http-client/issues</url>
   </issueManagement>
+
   <mailingLists>
     <mailingList>
       <name>asynchttpclient</name>
@@ -36,23 +63,6 @@
     </mailingList>
   </mailingLists>
 
-  <prerequisites>
-    <maven>3.0.0</maven>
-  </prerequisites>
-  <developers>
-    <developer>
-      <id>slandelle</id>
-      <name>Stephane Landelle</name>
-      <email>slandelle@gatling.io</email>
-    </developer>
-  </developers>
-  <licenses>
-    <license>
-      <name>Apache License 2.0</name>
-      <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
-      <distribution>repo</distribution>
-    </license>
-  </licenses>
   <build>
     <resources>
       <resource>
@@ -65,20 +75,28 @@
       <extension>
         <groupId>org.apache.maven.wagon</groupId>
         <artifactId>wagon-ssh-external</artifactId>
-        <version>1.0-beta-6</version>
+        <version>3.3.4</version>
       </extension>
       <extension>
         <groupId>org.apache.maven.scm</groupId>
         <artifactId>maven-scm-provider-gitexe</artifactId>
-        <version>1.6</version>
+        <version>1.11.2</version>
       </extension>
       <extension>
         <groupId>org.apache.maven.scm</groupId>
         <artifactId>maven-scm-manager-plexus</artifactId>
-        <version>1.6</version>
+        <version>1.11.2</version>
       </extension>
     </extensions>
     <defaultGoal>install</defaultGoal>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <artifactId>maven-release-plugin</artifactId>
+          <version>2.5.3</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
     <plugins>
       <plugin>
         <artifactId>maven-compiler-plugin</artifactId>
@@ -136,11 +154,23 @@
       </plugin>
       <plugin>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.2.0</version>
+        <executions>
+          <execution>
+            <id>default-jar</id>
+            <configuration>
+              <archive>
+                <manifestEntries>
+                  <Automatic-Module-Name>${javaModuleName}</Automatic-Module-Name>
+                </manifestEntries>
+              </archive>
+            </configuration>
+          </execution>
+        </executions>
       </plugin>
       <plugin>
         <artifactId>maven-source-plugin</artifactId>
-        <version>3.0.1</version>
+        <version>3.2.1</version>
         <executions>
           <execution>
             <id>attach-sources</id>
@@ -151,42 +181,61 @@
           </execution>
         </executions>
       </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>3.0.1</version>
+        <extensions>true</extensions>
+        <configuration>
+          <manifestLocation>META-INF</manifestLocation>
+          <instructions>
+            <Bundle-Version>$(replace;$(project.version);-SNAPSHOT;.$(tstamp;yyyyMMdd-HHmm))</Bundle-Version>
+            <Bundle-Vendor>The AsyncHttpClient Project</Bundle-Vendor>
+            <Import-Package>javax.activation;version="[1.1,2)", *</Import-Package>
+          </instructions>
+        </configuration>
+        <executions>
+          <execution>
+            <id>osgi-bundle</id>
+            <phase>package</phase>
+            <goals>
+              <goal>bundle</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>3.1.1</version>
+        <configuration>
+          <doclint>none</doclint>
+        </configuration>
+        <executions>
+          <execution>
+            <id>attach-javadocs</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-gpg-plugin</artifactId>
+        <version>1.6</version>
+        <executions>
+          <execution>
+            <id>sign-artifacts</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>sign</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
     </plugins>
-    <pluginManagement>
-      <plugins>
-        <plugin>
-          <artifactId>maven-javadoc-plugin</artifactId>
-          <version>2.10.4</version>
-        </plugin>
-      </plugins>
-    </pluginManagement>
   </build>
   <profiles>
-    <profile>
-      <id>release-sign-artifacts</id>
-      <activation>
-        <property>
-          <name>performRelease</name>
-          <value>true</value>
-        </property>
-      </activation>
-      <build>
-        <plugins>
-          <plugin>
-            <artifactId>maven-gpg-plugin</artifactId>
-            <executions>
-              <execution>
-                <id>sign-artifacts</id>
-                <phase>verify</phase>
-                <goals>
-                  <goal>sign</goal>
-                </goals>
-              </execution>
-            </executions>
-          </plugin>
-        </plugins>
-      </build>
-    </profile>
     <profile>
       <id>test-output</id>
       <properties>
@@ -194,20 +243,9 @@
       </properties>
     </profile>
   </profiles>
-  <distributionManagement>
-    <repository>
-      <id>sonatype-nexus-staging</id>
-      <name>Sonatype Release</name>
-      <url>http://oss.sonatype.org/service/local/staging/deploy/maven2
-      </url>
-    </repository>
-    <snapshotRepository>
-      <id>sonatype-nexus-snapshots</id>
-      <name>sonatype-nexus-snapshots</name>
-      <url>${distMgmtSnapshotsUrl}</url>
-    </snapshotRepository>
-  </distributionManagement>
+
   <modules>
+    <module>bom</module>
     <module>netty-utils</module>
     <module>client</module>
     <module>extras</module>
@@ -267,6 +305,13 @@
         <version>${netty.version}</version>
         <optional>true</optional>
       </dependency>
+      <dependency>
+        <groupId>io.netty</groupId>
+        <artifactId>netty-transport-native-kqueue</artifactId>
+        <classifier>osx-x86_64</classifier>
+        <version>${netty.version}</version>
+        <optional>true</optional>
+      </dependency>
       <dependency>
         <groupId>org.reactivestreams</groupId>
         <artifactId>reactive-streams</artifactId>
@@ -398,32 +443,31 @@
     </dependency>
     <dependency>
       <groupId>org.hamcrest</groupId>
-      <artifactId>java-hamcrest</artifactId>
+      <artifactId>hamcrest</artifactId>
       <version>${hamcrest.version}</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
   <properties>
-    <distMgmtSnapshotsUrl>http://oss.sonatype.org/content/repositories/snapshots</distMgmtSnapshotsUrl>
     <surefire.redirectTestOutputToFile>true</surefire.redirectTestOutputToFile>
     <source.property>1.8</source.property>
     <target.property>1.8</target.property>
-    <netty.version>4.1.30.Final</netty.version>
-    <slf4j.version>1.7.25</slf4j.version>
-    <reactive-streams.version>1.0.2</reactive-streams.version>
+    <netty.version>4.1.46.Final</netty.version>
+    <slf4j.version>1.7.30</slf4j.version>
+    <reactive-streams.version>1.0.3</reactive-streams.version>
     <activation.version>1.2.0</activation.version>
-    <netty-reactive-streams.version>2.0.0</netty-reactive-streams.version>
+    <netty-reactive-streams.version>2.0.4</netty-reactive-streams.version>
     <rxjava.version>1.3.8</rxjava.version>
-    <rxjava2.version>2.1.16</rxjava2.version>
+    <rxjava2.version>2.2.18</rxjava2.version>
     <logback.version>1.2.3</logback.version>
-    <testng.version>6.13.1</testng.version>
-    <jetty.version>9.4.11.v20180605</jetty.version>
-    <tomcat.version>9.0.10</tomcat.version>
+    <testng.version>7.1.0</testng.version>
+    <jetty.version>9.4.18.v20190429</jetty.version>
+    <tomcat.version>9.0.31</tomcat.version>
     <commons-io.version>2.6</commons-io.version>
     <commons-fileupload.version>1.3.3</commons-fileupload.version>
     <privilegedaccessor.version>1.2.2</privilegedaccessor.version>
-    <mockito.version>2.19.0</mockito.version>
-    <hamcrest.version>2.0.0.0</hamcrest.version>
-    <kerby.version>1.1.1</kerby.version>
+    <mockito.version>3.3.0</mockito.version>
+    <hamcrest.version>2.2</hamcrest.version>
+    <kerby.version>2.0.0</kerby.version>
   </properties>
 </project>