[java-shib-shared] branch main updated: JSSH-16: Update to Apache HttpClient 5.x

Brent Putman putmanb at georgetown.edu
Tue Feb 21 19:31:04 UTC 2023


This is an automated email from the git hooks/post-receive script.

putmanb pushed a commit to branch main
in repository java-shib-shared.

View the commit online:
http://git.shibboleth.net/view/?p=java-shib-shared.git;a=commit;h=0c2baf83366be65ce07d21240dd3b8dc63dfe01e

The following commit(s) were added to refs/heads/main by this push:
     new 0c2baf83 JSSH-16: Update to Apache HttpClient 5.x
0c2baf83 is described below

commit 0c2baf83366be65ce07d21240dd3b8dc63dfe01e
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Wed Oct 12 21:46:28 2022 -0400

    JSSH-16: Update to Apache HttpClient 5.x
    
    Initial refactor.
---
 shib-networking-spring/pom.xml                     |  12 +-
 .../factory/FileCachingHttpClientFactoryBean.java  |   2 +-
 .../httpclient/factory/HttpClientFactoryBean.java  |   2 +-
 .../InMemoryCachingHttpClientFactoryBean.java      |   2 +-
 .../resource/FileBackedHTTPResource.java           |   8 +-
 .../spring/httpclient/resource/HTTPResource.java   |  44 +-
 .../resource/ConditionalResourceTest.java          |  14 +-
 .../resource/FileBackedHTTPResourceTest.java       |   2 +-
 .../httpclient/resource/HTTPResourceTest.java      |  22 +-
 .../httpclient/resource/TestHTTPResource.java      |   6 +-
 shib-networking/pom.xml                            |  12 +-
 .../shared/httpclient/AbstractHttpClient.java      | 181 +++++++
 .../httpclient/ContextHandlingHttpClient.java      |  75 ++-
 .../httpclient/FileCachingHttpClientBuilder.java   |  62 +--
 .../shared/httpclient/HttpClientBuilder.java       | 602 +++++++++++++--------
 .../httpclient/HttpClientContextHandler.java       |   8 +-
 .../shared/httpclient/HttpClientSupport.java       |  24 +-
 .../shared/httpclient/IdleConnectionSweeper.java   | 131 -----
 .../InMemoryCachingHttpClientBuilder.java          |  10 +-
 .../httpclient/LocalAddressProxyRoutePlanner.java  |  58 ++
 .../httpclient/LocalAddressRoutePlanner.java       |  56 ++
 .../httpclient/LocalAddressSystemRoutePlanner.java |  58 ++
 .../shared/httpclient/RequestConnectionClose.java  |  42 +-
 .../shared/httpclient/TLSSocketFactory.java        |  25 +-
 .../java/support/httpclient/HttpClientSupport.java |   9 +-
 .../httpclient/ContextHandlingHttpClientTest.java  | 232 ++++----
 .../FileCachingHttpClientBuilderTest.java          |   2 +-
 .../shared/httpclient/HttpClientBuilderTest.java   |  10 +-
 .../shared/httpclient/HttpClientSupportTest.java   |   8 +-
 .../httpclient/IdleConectionSweeperTest.java       | 101 ----
 .../InMemoryCachingHttpClientBuilderTest.java      |   2 +-
 31 files changed, 1043 insertions(+), 779 deletions(-)

diff --git a/shib-networking-spring/pom.xml b/shib-networking-spring/pom.xml
index a0d269e6..10c87792 100644
--- a/shib-networking-spring/pom.xml
+++ b/shib-networking-spring/pom.xml
@@ -38,16 +38,16 @@
         </dependency>
 
         <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpcore</artifactId>
+            <groupId>${httpclient.httpcore.groupId}</groupId>
+            <artifactId>${httpclient.httpcore.artifactId}</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
+            <groupId>${httpclient.groupId}</groupId>
+            <artifactId>${httpclient.artifactId}</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient-cache</artifactId>
+            <groupId>${httpclient.groupId}</groupId>
+            <artifactId>${httpclient.cache.artifactId}</artifactId>
         </dependency>
 
         <dependency>
diff --git a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/FileCachingHttpClientFactoryBean.java b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/FileCachingHttpClientFactoryBean.java
index c74705f0..6dca47dd 100644
--- a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/FileCachingHttpClientFactoryBean.java
+++ b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/FileCachingHttpClientFactoryBean.java
@@ -19,7 +19,7 @@ package net.shibboleth.shared.spring.httpclient.factory;
 
 import javax.annotation.Nullable;
 
-import org.apache.http.client.HttpClient;
+import org.apache.hc.client5.http.classic.HttpClient;
 import org.springframework.beans.factory.DisposableBean;
 import org.springframework.beans.factory.FactoryBean;
 
diff --git a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/HttpClientFactoryBean.java b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/HttpClientFactoryBean.java
index aab7794f..ca16c281 100644
--- a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/HttpClientFactoryBean.java
+++ b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/HttpClientFactoryBean.java
@@ -19,7 +19,7 @@ package net.shibboleth.shared.spring.httpclient.factory;
 
 import javax.annotation.Nullable;
 
-import org.apache.http.client.HttpClient;
+import org.apache.hc.client5.http.classic.HttpClient;
 import org.springframework.beans.factory.FactoryBean;
 
 import net.shibboleth.shared.httpclient.HttpClientBuilder;
diff --git a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/InMemoryCachingHttpClientFactoryBean.java b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/InMemoryCachingHttpClientFactoryBean.java
index 357395e0..1ff55f7e 100644
--- a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/InMemoryCachingHttpClientFactoryBean.java
+++ b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/factory/InMemoryCachingHttpClientFactoryBean.java
@@ -19,7 +19,7 @@ package net.shibboleth.shared.spring.httpclient.factory;
 
 import javax.annotation.Nullable;
 
-import org.apache.http.client.HttpClient;
+import org.apache.hc.client5.http.classic.HttpClient;
 import org.springframework.beans.factory.FactoryBean;
 
 import net.shibboleth.shared.httpclient.InMemoryCachingHttpClientBuilder;
diff --git a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/resource/FileBackedHTTPResource.java b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/resource/FileBackedHTTPResource.java
index 024f9ad5..bbb605e8 100644
--- a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/resource/FileBackedHTTPResource.java
+++ b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/resource/FileBackedHTTPResource.java
@@ -31,9 +31,9 @@ import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.client.HttpClient;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
 import org.slf4j.Logger;
 
 import org.springframework.core.io.FileSystemResource;
@@ -141,7 +141,7 @@ public class FileBackedHTTPResource extends HTTPResource {
             log.info("{}: Could not reach URL, trying file", getDescription(), e);
             return backingResource.exists();
         }
-        final int httpStatusCode = response.getStatusLine().getStatusCode();
+        final int httpStatusCode = response.getCode();
 
         if (httpStatusCode == HttpStatus.SC_OK) {
             return true;
diff --git a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/resource/HTTPResource.java b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/resource/HTTPResource.java
index 98890fe9..7f99c1a5 100644
--- a/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/resource/HTTPResource.java
+++ b/shib-networking-spring/src/main/java/net/shibboleth/shared/spring/httpclient/resource/HTTPResource.java
@@ -28,19 +28,19 @@ import java.net.URL;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import org.apache.http.Header;
-import org.apache.http.HttpHeaders;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.cache.CacheResponseStatus;
-import org.apache.http.client.cache.HttpCacheContext;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.client.utils.DateUtils;
-import org.apache.http.util.EntityUtils;
+import org.apache.hc.client5.http.cache.CacheResponseStatus;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
 import org.slf4j.Logger;
 
 import org.springframework.beans.factory.BeanNameAware;
@@ -171,7 +171,7 @@ public class HTTPResource extends AbstractIdentifiedInitializableComponent imple
         }
         
         log.debug("Attempting to get data from remote resource '{}'", resourceURL);
-        final HttpResponse response = httpClient.execute(httpGet, context);
+        final ClassicHttpResponse response = httpClient.executeOpen(null, httpGet, context);
         
         if (contextHandler != null) {
             log.debug("Invoking HttpClientContextHandler after execution");
@@ -179,7 +179,7 @@ public class HTTPResource extends AbstractIdentifiedInitializableComponent imple
         }
         
         reportCachingStatus(context);
-        final int httpStatusCode = response.getStatusLine().getStatusCode();
+        final int httpStatusCode = response.getCode();
 
         if (httpStatusCode != HttpStatus.SC_OK) {
             final String errMsg =
@@ -212,7 +212,7 @@ public class HTTPResource extends AbstractIdentifiedInitializableComponent imple
         } catch (final IOException e) {
             return false;
         }
-        final int httpStatusCode = response.getStatusLine().getStatusCode();
+        final int httpStatusCode = response.getCode();
 
         return httpStatusCode == HttpStatus.SC_OK;
     }
@@ -258,7 +258,7 @@ public class HTTPResource extends AbstractIdentifiedInitializableComponent imple
     protected HttpResponse getResourceHeaders() throws IOException {
         final HttpUriRequest httpRequest = new HttpGet(resourceURL.toExternalForm());
 
-        HttpResponse httpResponse = null;
+        ClassicHttpResponse httpResponse = null;
         try {
             final HttpCacheContext context = buildHttpClientContext();
             
@@ -269,7 +269,7 @@ public class HTTPResource extends AbstractIdentifiedInitializableComponent imple
                 contextHandler.invokeBefore(context, httpRequest);
             }
             
-            httpResponse = httpClient.execute(httpRequest, context);
+            httpResponse = httpClient.executeOpen(null, httpRequest, context);
             
             if (contextHandler != null) {
                 log.debug("Invoking HttpClientContextHandler after execution");
@@ -298,7 +298,7 @@ public class HTTPResource extends AbstractIdentifiedInitializableComponent imple
 
         log.debug("Attempting to fetch remote resource as '{}'", resourceURL);
         response = getResourceHeaders();
-        final int httpStatusCode = response.getStatusLine().getStatusCode();
+        final int httpStatusCode = response.getCode();
 
         if (httpStatusCode != HttpStatus.SC_OK) {
             final String errMsg =
@@ -331,7 +331,7 @@ public class HTTPResource extends AbstractIdentifiedInitializableComponent imple
     @Override public long lastModified() throws IOException {
         final String response = getResponseHeader(HttpHeaders.LAST_MODIFIED);
         if (null != response) {
-            return DateUtils.parseDate(response).getTime();
+            return DateUtils.parseStandardDate(response).toEpochMilli();
         }
         final String errMsg = "Response from remote resource " + resourceURL.toString() + 
                 " did not contain a Last-Modified header";
@@ -403,7 +403,7 @@ public class HTTPResource extends AbstractIdentifiedInitializableComponent imple
      private static class ConnectionClosingInputStream extends InputStream {
 
         /** HTTP response that is being wrapped. */
-        private final HttpResponse response;
+        private final ClassicHttpResponse response;
 
         /** Stream owned by the given HTTP response. */
         private final InputStream stream;
@@ -414,7 +414,7 @@ public class HTTPResource extends AbstractIdentifiedInitializableComponent imple
          * @param httpResponse HTTP method that was invoked
          * @throws IOException if there is a problem getting the entity content input stream from the response
          */
-        public ConnectionClosingInputStream(@Nonnull final HttpResponse httpResponse) throws IOException {
+        public ConnectionClosingInputStream(@Nonnull final ClassicHttpResponse httpResponse) throws IOException {
             response = httpResponse;
             stream = response.getEntity().getContent();
         }
diff --git a/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/ConditionalResourceTest.java b/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/ConditionalResourceTest.java
index 1e672668..ac696387 100644
--- a/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/ConditionalResourceTest.java
+++ b/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/ConditionalResourceTest.java
@@ -26,9 +26,9 @@ import net.shibboleth.shared.spring.custom.SchemaTypeAwareXMLBeanDefinitionReade
 import net.shibboleth.shared.spring.resource.ConditionalResource;
 import net.shibboleth.shared.spring.resource.ConditionalResourceResolver;
 
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
 import org.springframework.context.support.GenericApplicationContext;
 import org.springframework.core.io.ClassPathResource;
@@ -78,10 +78,10 @@ public class ConditionalResourceTest {
     @Test public void contextHandlerFailBeforeTest() throws IOException, ComponentInitializationException {
         final HTTPResource existsHTTPResource = new HTTPResource(client, existsURL);
         existsHTTPResource.setHttpClientContextHandler(new HttpClientContextHandler() {
-            public void invokeBefore(HttpClientContext context, HttpUriRequest request) throws IOException {
+            public void invokeBefore(HttpClientContext context, ClassicHttpRequest request) throws IOException {
                 throw new IOException("Fail");
             }
-            public void invokeAfter(HttpClientContext context, HttpUriRequest request) throws IOException {
+            public void invokeAfter(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             }
         });
         
@@ -95,9 +95,9 @@ public class ConditionalResourceTest {
     @Test public void contextHandlerFailAfterTest() throws IOException, ComponentInitializationException {
         final HTTPResource existsHTTPResource = new HTTPResource(client, existsURL);
         existsHTTPResource.setHttpClientContextHandler(new HttpClientContextHandler() {
-            public void invokeBefore(HttpClientContext context, HttpUriRequest request) throws IOException {
+            public void invokeBefore(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             }
-            public void invokeAfter(HttpClientContext context, HttpUriRequest request) throws IOException {
+            public void invokeAfter(HttpClientContext context, ClassicHttpRequest request) throws IOException {
                 throw new IOException("Fail");
             }
         });
diff --git a/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/FileBackedHTTPResourceTest.java b/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/FileBackedHTTPResourceTest.java
index 27c8a956..a9ad1160 100644
--- a/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/FileBackedHTTPResourceTest.java
+++ b/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/FileBackedHTTPResourceTest.java
@@ -24,7 +24,7 @@ import net.shibboleth.shared.httpclient.HttpClientBuilder;
 import net.shibboleth.shared.spring.context.FilesystemGenericApplicationContext;
 import net.shibboleth.shared.spring.custom.SchemaTypeAwareXMLBeanDefinitionReader;
 
-import org.apache.http.client.HttpClient;
+import org.apache.hc.client5.http.classic.HttpClient;
 import org.springframework.context.support.GenericApplicationContext;
 import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
 import org.springframework.core.env.MutablePropertySources;
diff --git a/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/HTTPResourceTest.java b/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/HTTPResourceTest.java
index 77c01f5e..4c4731c9 100644
--- a/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/HTTPResourceTest.java
+++ b/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/HTTPResourceTest.java
@@ -24,11 +24,11 @@ import java.nio.file.Path;
 import java.time.Instant;
 import java.util.Collection;
 
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.cache.CacheResponseStatus;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.hc.client5.http.cache.CacheResponseStatus;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.GenericApplicationContext;
@@ -74,9 +74,9 @@ public class HTTPResourceTest {
     @Test public void contextHandlerNoopTest() throws IOException {
         final HTTPResource existsResource = new HTTPResource(client, existsURL);
         existsResource.setHttpClientContextHandler(new HttpClientContextHandler() {
-            public void invokeBefore(HttpClientContext context, HttpUriRequest request) throws IOException {
+            public void invokeBefore(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             }
-            public void invokeAfter(HttpClientContext context, HttpUriRequest request) throws IOException {
+            public void invokeAfter(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             }
         });
 
@@ -86,10 +86,10 @@ public class HTTPResourceTest {
     @Test public void contextHandlerFailBeforeTest() throws IOException {
         final HTTPResource existsResource = new HTTPResource(client, existsURL);
         existsResource.setHttpClientContextHandler(new HttpClientContextHandler() {
-            public void invokeBefore(HttpClientContext context, HttpUriRequest request) throws IOException {
+            public void invokeBefore(HttpClientContext context, ClassicHttpRequest request) throws IOException {
                 throw new IOException("Fail");
             }
-            public void invokeAfter(HttpClientContext context, HttpUriRequest request) throws IOException {
+            public void invokeAfter(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             }
         });
 
@@ -99,9 +99,9 @@ public class HTTPResourceTest {
     @Test public void contextHandlerFailAfterTest() throws IOException {
         final HTTPResource existsResource = new HTTPResource(client, existsURL);
         existsResource.setHttpClientContextHandler(new HttpClientContextHandler() {
-            public void invokeBefore(HttpClientContext context, HttpUriRequest request) throws IOException {
+            public void invokeBefore(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             }
-            public void invokeAfter(HttpClientContext context, HttpUriRequest request) throws IOException {
+            public void invokeAfter(HttpClientContext context, ClassicHttpRequest request) throws IOException {
                 throw new IOException("Fail");
             }
         });
diff --git a/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/TestHTTPResource.java b/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/TestHTTPResource.java
index 75a3ca13..04a17a58 100644
--- a/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/TestHTTPResource.java
+++ b/shib-networking-spring/src/test/java/net/shibboleth/shared/spring/httpclient/resource/TestHTTPResource.java
@@ -19,9 +19,9 @@ package net.shibboleth.shared.spring.httpclient.resource;
 
 import java.io.IOException;
 
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.cache.CacheResponseStatus;
-import org.apache.http.client.cache.HttpCacheContext;
+import org.apache.hc.client5.http.cache.CacheResponseStatus;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
+import org.apache.hc.client5.http.classic.HttpClient;
 
 class TestHTTPResource extends HTTPResource {
 
diff --git a/shib-networking/pom.xml b/shib-networking/pom.xml
index 7285fb35..f204d1cf 100644
--- a/shib-networking/pom.xml
+++ b/shib-networking/pom.xml
@@ -33,16 +33,16 @@
         </dependency>
 
         <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
+            <groupId>${httpclient.groupId}</groupId>
+            <artifactId>${httpclient.artifactId}</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient-cache</artifactId>
+            <groupId>${httpclient.groupId}</groupId>
+            <artifactId>${httpclient.cache.artifactId}</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpcore</artifactId>
+            <groupId>${httpclient.httpcore.groupId}</groupId>
+            <artifactId>${httpclient.httpcore.artifactId}</artifactId>
         </dependency>
 
         <!-- Provided Dependencies -->
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/AbstractHttpClient.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/AbstractHttpClient.java
new file mode 100644
index 00000000..31a54cc6
--- /dev/null
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/AbstractHttpClient.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.shared.httpclient;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.routing.RoutingSupport;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.io.ModalCloseable;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Basic abstract implementation of {@link HttpClient}.
+ * 
+ * Based on {@link org.apache.hc.client5.http.impl.classic.CloseableHttpClient}.
+ */
+public abstract class AbstractHttpClient implements HttpClient, ModalCloseable {
+    
+    /** Logger. */
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractHttpClient.class);
+
+    /**
+     * Execute the HTTP request.
+     * 
+     * @param target the target host for the request, may be {@code null}
+     * @param request the request to execute
+     * @param context the context to use for the execution, or {@code null} to use the default context
+     * @return the response to the request
+     * @throws IOException in case of a problem or the connection was aborted
+     */
+    protected abstract ClassicHttpResponse doExecute(
+            @Nullable final HttpHost target,
+            @Nonnull final ClassicHttpRequest request,
+            @Nullable final HttpContext context) throws IOException;
+
+    /**
+     * Determine the {@link HttpHost} instance based on the specified request.
+     * 
+     * @param request the request being executed
+     * @return the {@link HttpHost} instance reflecting the specified request
+     * @throws ClientProtocolException if target host can not be determined
+     */
+    private static HttpHost determineTarget(@Nullable final ClassicHttpRequest request) throws ClientProtocolException {
+        try {
+            return RoutingSupport.determineHost(request);
+        } catch (final HttpException ex) {
+            throw new ClientProtocolException(ex);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ClassicHttpResponse execute(
+            @Nullable final HttpHost target,
+            @Nonnull final ClassicHttpRequest request,
+            @Nullable final HttpContext context) throws IOException {
+        Constraint.isNotNull(request, "HTTP request");
+        return doExecute(target, request, context);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ClassicHttpResponse execute(
+            @Nonnull ClassicHttpRequest request,
+            @Nullable final HttpContext context) throws IOException {
+        Constraint.isNotNull(request, "HTTP request");
+        return doExecute(determineTarget(request), request, context);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ClassicHttpResponse execute(
+            @Nonnull final ClassicHttpRequest request) throws IOException {
+        Constraint.isNotNull(request, "HTTP request");
+        return doExecute(determineTarget(request), request, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ClassicHttpResponse execute(
+            @Nullable final HttpHost target,
+            @Nonnull final ClassicHttpRequest request) throws IOException {
+        Constraint.isNotNull(request, "HTTP request");
+        return doExecute(target, request, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public <T> T execute(
+            @Nonnull final ClassicHttpRequest request,
+            @Nonnull final HttpClientResponseHandler<? extends T> responseHandler) throws IOException {
+        Constraint.isNotNull(request, "HTTP request");
+        Constraint.isNotNull(responseHandler, "HTTP response handler");
+        return execute(request, null, responseHandler);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public <T> T execute(
+            @Nonnull final ClassicHttpRequest request,
+            @Nullable final HttpContext context,
+            @Nonnull final HttpClientResponseHandler<? extends T> responseHandler) throws IOException {
+        Constraint.isNotNull(request, "HTTP request");
+        Constraint.isNotNull(responseHandler, "HTTP response handler");
+        final HttpHost target = determineTarget(request);
+        return execute(target, request, context, responseHandler);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public <T> T execute(
+            @Nullable final HttpHost target,
+            @Nonnull ClassicHttpRequest request,
+            @Nonnull final HttpClientResponseHandler<? extends T> responseHandler) throws IOException {
+        Constraint.isNotNull(request, "HTTP request");
+        Constraint.isNotNull(responseHandler, "HTTP response handler");
+        return execute(target, request, null, responseHandler);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public <T> T execute(
+            @Nullable final HttpHost target,
+            @Nonnull final ClassicHttpRequest request,
+            @Nullable final HttpContext context,
+            @Nonnull final HttpClientResponseHandler<? extends T> responseHandler) throws IOException {
+        Constraint.isNotNull(request, "HTTP request");
+        Constraint.isNotNull(responseHandler, "HTTP response handler");
+
+        try (final ClassicHttpResponse response = doExecute(target, request, context)) {
+            try {
+                final T result = responseHandler.handleResponse(response);
+                final HttpEntity entity = response.getEntity();
+                EntityUtils.consume(entity);
+                return result;
+            } catch (final HttpException t) {
+                // Try to salvage the underlying connection in case of a protocol exception
+                final HttpEntity entity = response.getEntity();
+                try {
+                    EntityUtils.consume(entity);
+                } catch (final Exception t2) {
+                    // Log this exception. The original exception is more
+                    // important and will be thrown to the caller.
+                    LOG.warn("Error consuming content after an exception.", t2);
+                }
+                throw new ClientProtocolException(t);
+            }
+        }
+    }
+
+}
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/ContextHandlingHttpClient.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/ContextHandlingHttpClient.java
index f4088842..f0eea8f5 100644
--- a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/ContextHandlingHttpClient.java
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/ContextHandlingHttpClient.java
@@ -17,22 +17,23 @@
 
 package net.shibboleth.shared.httpclient;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.util.List;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpRequestWrapper;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.protocol.BasicHttpContext;
-import org.apache.http.protocol.HttpContext;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.protocol.BasicHttpContext;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.io.ModalCloseable;
 import org.slf4j.Logger;
 
 import com.google.common.collect.Lists;
@@ -65,13 +66,13 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * <li>Static handlers supplied via the constructor, in reverse list order</li>
  * </ol>
  */
-class ContextHandlingHttpClient extends CloseableHttpClient {
+class ContextHandlingHttpClient extends AbstractHttpClient {
     
     /** Logger. */
     private Logger log = LoggerFactory.getLogger(ContextHandlingHttpClient.class);
     
     /** The wrapped client instance. */
-    @Nonnull private CloseableHttpClient httpClient;
+    @Nonnull private HttpClient httpClient;
     
     /** Optional list of static handlers supplied to this class instance. */
     @Nonnull private List<HttpClientContextHandler> handlers;
@@ -81,7 +82,7 @@ class ContextHandlingHttpClient extends CloseableHttpClient {
      *
      * @param client the wrapped client instance
      */
-    public ContextHandlingHttpClient(@Nonnull final CloseableHttpClient client) {
+    public ContextHandlingHttpClient(@Nonnull final HttpClient client) {
         this(client, CollectionSupport.emptyList());
     }
 
@@ -91,52 +92,50 @@ class ContextHandlingHttpClient extends CloseableHttpClient {
      * @param client the wrapped client instance
      * @param staticHandlers the list of static handlers
      */
-    public ContextHandlingHttpClient(@Nonnull final CloseableHttpClient client, 
+    public ContextHandlingHttpClient(@Nonnull final HttpClient client, 
             @Nonnull final List<HttpClientContextHandler> staticHandlers) {
         httpClient = Constraint.isNotNull(client, "HttpClient was null");
         handlers = Constraint.isNotNull(staticHandlers, "staticHandlers was null");
     }
-
-    /** {@inheritDoc} */
-    @Deprecated
-    @Override
-    public org.apache.http.params.HttpParams getParams() {
-        return httpClient.getParams();
-    }
-
+    
     /** {@inheritDoc} */
-    @Deprecated
     @Override
-    public org.apache.http.conn.ClientConnectionManager getConnectionManager() {
-        return httpClient.getConnectionManager();
+    public void close() throws IOException {
+        if (Closeable.class.isInstance(httpClient)) {
+            Closeable.class.cast(httpClient).close();
+        }
     }
     
     /** {@inheritDoc} */
     @Override
-    public void close() throws IOException {
-        httpClient.close();
+    public void close(final CloseMode closeMode) {
+        if (ModalCloseable.class.isInstance(httpClient)) {
+            ModalCloseable.class.cast(httpClient).close(closeMode);
+        }
     }
 
     /** {@inheritDoc} */
     @Override
-    protected CloseableHttpResponse doExecute(final HttpHost target, final HttpRequest request, 
-            final HttpContext context) throws IOException, ClientProtocolException {
+    protected ClassicHttpResponse doExecute(@Nullable final HttpHost target, @Nonnull ClassicHttpRequest request, 
+            @Nullable final HttpContext context) throws IOException {
         
         Throwable error = null;
         
         final HttpClientContext clientContext = 
                 HttpClientContext.adapt(context != null ? context : new BasicHttpContext());
-        final HttpUriRequest uriRequest = HttpUriRequest.class.isInstance(request) 
-                ? (HttpUriRequest)request : HttpRequestWrapper.wrap(request, target);
-        assert clientContext != null && uriRequest != null;
+
+        assert clientContext != null;
+
         try {
-            invokeBefore(uriRequest, clientContext);
-            return httpClient.execute(target, request, clientContext);
+            invokeBefore(request, clientContext);
+            // As of 5.2, using executeOpen(...) b/c all other execute(...) variants are deprecated.
+            final ClassicHttpResponse classicResponse = httpClient.executeOpen(target, request, clientContext);
+            return classicResponse;
         } catch (final Throwable t) {
             error = t;
             throw t;
         } finally {
-            invokeAfter(uriRequest, clientContext, error);
+            invokeAfter(request, clientContext, error);
         }
     }
 
@@ -148,7 +147,7 @@ class ContextHandlingHttpClient extends CloseableHttpClient {
      * @param context the HTTP context
      * @throws IOException if any handler throws an error
      */
-    private void invokeBefore(@Nonnull final HttpUriRequest request, @Nonnull final HttpClientContext context)
+    private void invokeBefore(@Nonnull final ClassicHttpRequest request, @Nonnull final HttpClientContext context)
             throws IOException {
         log.trace("In invokeBefore");
         
@@ -199,7 +198,7 @@ class ContextHandlingHttpClient extends CloseableHttpClient {
      *                     is a type of unchecked error (RuntimeException or Error) that will be propagated out
      *                     here as well.
      */
-    private void invokeAfter(@Nonnull final HttpUriRequest request, @Nonnull final HttpClientContext context, 
+    private void invokeAfter(@Nonnull final ClassicHttpRequest request, @Nonnull final HttpClientContext context, 
             final Throwable priorError) throws IOException {
         log.trace("In invokeAfter");
         
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/FileCachingHttpClientBuilder.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/FileCachingHttpClientBuilder.java
index 438b3c1a..fcb25783 100644
--- a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/FileCachingHttpClientBuilder.java
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/FileCachingHttpClientBuilder.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.shared.httpclient;
 
+import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
 import java.time.Duration;
@@ -25,17 +26,17 @@ import java.util.TimerTask;
 
 import javax.annotation.Nonnull;
 
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.cache.CacheConfig;
-import org.apache.http.impl.client.cache.CachingHttpClientBuilder;
-import org.apache.http.impl.client.cache.FileResourceFactory;
-import org.apache.http.impl.client.cache.ManagedHttpCacheStorage;
-import org.apache.http.protocol.HttpContext;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.impl.cache.CacheConfig;
+import org.apache.hc.client5.http.impl.cache.CachingHttpClientBuilder;
+import org.apache.hc.client5.http.impl.cache.FileResourceFactory;
+import org.apache.hc.client5.http.impl.cache.ManagedHttpCacheStorage;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.io.ModalCloseable;
 import org.slf4j.Logger;
 
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
@@ -51,7 +52,7 @@ import net.shibboleth.shared.primitive.StringSupport;
 import net.shibboleth.shared.primitive.TimerSupport;
 
 /**
- * An {@link org.apache.http.client.HttpClient} builder that supports RFC 2616 caching.
+ * An {@link org.apache.hc.client5.http.classic.HttpClient} builder that supports RFC 2616 caching.
  * <p>
  * Cached content is written to disk. Special care should be taken so that multiple clients do not share a single cache
  * directory unintentionally. This could result in sensitive data being available in ways it should not be.
@@ -251,7 +252,7 @@ public class FileCachingHttpClientBuilder extends HttpClientBuilder {
 
     /** {@inheritDoc} */
     public synchronized HttpClient buildClient() throws Exception {
-        final CloseableHttpClient client = (CloseableHttpClient) super.buildClient();
+        final HttpClient client = super.buildClient();
         final ManagedHttpCacheStorage tempStorage = managedStorage;
         // Null this out so we don't keep a reference, inhibiting garbage collection.
         assert client != null && tempStorage != null;
@@ -260,17 +261,17 @@ public class FileCachingHttpClientBuilder extends HttpClientBuilder {
     }
     
     /**
-     * Class which wraps a caching instance of {@link CloseableHttpClient} and its associated 
+     * Class which wraps a caching instance of {@link HttpClient} and its associated 
      * {@link ManagedHttpCacheStorage}, and manages the scheduled maintenance and lifecycle of the latter.
      */
-    private static class StorageManagingHttpClient extends CloseableHttpClient 
+    private static class StorageManagingHttpClient extends AbstractHttpClient
             implements InitializableComponent, DestructableComponent {
         
         /** Logger. */
         private Logger log = LoggerFactory.getLogger(StorageManagingHttpClient.class);
         
         /** The wrapped HttpClient instance. */
-        private CloseableHttpClient httpClient;
+        private HttpClient httpClient;
         
         /** The cache storage instance to manage. */
         private ManagedHttpCacheStorage storage;
@@ -297,7 +298,7 @@ public class FileCachingHttpClientBuilder extends HttpClientBuilder {
          * @param managedStorage the managed cache storage instance
          * @param taskInterval the interval at which storage maintenance should run
          */
-        public StorageManagingHttpClient(@Nonnull final CloseableHttpClient wrappedClient, 
+        public StorageManagingHttpClient(@Nonnull final HttpClient wrappedClient, 
                 @Nonnull final ManagedHttpCacheStorage managedStorage, final long taskInterval)  {
            httpClient = Constraint.isNotNull(wrappedClient, "HttpClient was null");
            storage = Constraint.isNotNull(managedStorage, "ManagedHttpCacheStorage was null");
@@ -321,34 +322,29 @@ public class FileCachingHttpClientBuilder extends HttpClientBuilder {
 
 
         /** {@inheritDoc} */
-        protected CloseableHttpResponse doExecute(final HttpHost target, final HttpRequest request,
+        protected ClassicHttpResponse doExecute(final HttpHost target, final ClassicHttpRequest request,
                 final HttpContext context)
-                throws IOException, ClientProtocolException {
+                throws IOException {
             throwComponentStateExceptions();
-            return httpClient.execute(target, request, context);
+            return httpClient.executeOpen(target, request, context);
         }
 
         /** {@inheritDoc} */
         @Override
-        @Deprecated
-        public org.apache.http.params.HttpParams getParams() {
-            throwComponentStateExceptions();
-            return httpClient.getParams();
-        }
-        
-        /** {@inheritDoc} */
-        @Override
-        @Deprecated
-        public org.apache.http.conn.ClientConnectionManager getConnectionManager() {
+        public void close() throws IOException {
             throwComponentStateExceptions();
-            return httpClient.getConnectionManager();
+            if (Closeable.class.isInstance(httpClient)) {
+                Closeable.class.cast(httpClient).close();
+            }
         }
 
         /** {@inheritDoc} */
         @Override
-        public void close() throws IOException {
+        public void close(CloseMode closeMode) {
             throwComponentStateExceptions();
-            httpClient.close();
+            if (ModalCloseable.class.isInstance(httpClient)) {
+                ModalCloseable.class.cast(httpClient).close(closeMode);
+            }
         }
 
         /** {@inheritDoc} */
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientBuilder.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientBuilder.java
index 942c7bda..255a6c61 100644
--- a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientBuilder.java
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientBuilder.java
@@ -18,27 +18,39 @@
 package net.shibboleth.shared.httpclient;
 
 import java.net.InetAddress;
+import java.net.ProxySelector;
 import java.net.UnknownHostException;
+import java.nio.charset.Charset;
 import java.time.Duration;
 import java.util.List;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequestInterceptor;
-import org.apache.http.HttpResponseInterceptor;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.CredentialsProvider;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.HttpRequestRetryHandler;
-import org.apache.http.client.ServiceUnavailableRetryStrategy;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.config.ConnectionConfig;
-import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
-import org.apache.http.impl.client.BasicCredentialsProvider;
-import org.apache.http.util.CharsetUtils;
+import org.apache.hc.client5.http.HttpRequestRetryStrategy;
+import org.apache.hc.client5.http.SchemePortResolver;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.config.ConnectionConfig;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
+import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
+import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.io.HttpClientConnectionManager;
+import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
+import org.apache.hc.client5.http.routing.HttpRoutePlanner;
+import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.HttpResponseInterceptor;
+import org.apache.hc.core5.http.config.CharCodingConfig;
+import org.apache.hc.core5.http.config.Http1Config;
+import org.apache.hc.core5.http.io.HttpConnectionFactory;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
 
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotLive;
@@ -54,88 +66,30 @@ import net.shibboleth.shared.primitive.StringSupport;
  * 
  * <p>
  * When using the single-arg constructor variant to wrap an existing instance of
- * {@link org.apache.http.impl.client.HttpClientBuilder}, there are several caveats of which to be aware:
- * 
- * <ul>
- * 
- * <li>
- * Instances of the following which are set as the default instance on the Apache builder will be unconditionally
- * overwritten by this builder when {@link #buildClient()} is called:
- * 
- * <ul>
- * <li>{@link RequestConfig}</li>
- * <li>{@link ConnectionConfig}</li>
- * </ul>
- * 
- * <p>
- * This is due to the unfortunate fact that the Apache builder does not currently provide accessor methods to obtain the
- * default instances currently set on the builder. Therefore, if you need to set any default request or connection
- * config parameters which are not exposed by this builder, then you must use the Apache builder directly and may not
- * use this builder.
- * </p>
- * </li>
- * 
- * <li>
- * If this builder's <code>connectionDisregardTLSCertificate</code> is set to <code>true</code>, then any value
- * previously set via the Apache builder's
- * {@link org.apache.http.impl.client.HttpClientBuilder#setSSLSocketFactory} will be
- * unconditionally overwritten.</li>
- * 
- * <li>
- * If this builder is supplied with a <code>connectionProxyHost</code>, <code>connectionProxyUsername</code> and
- * <code>connectionProxyPassword</code>, then any value previously set via the Apache builder's
- * {@link org.apache.http.impl.client.HttpClientBuilder#setDefaultCredentialsProvider(CredentialsProvider)} will be
- * unconditionally overwritten.</li>
- * 
- * <li>
- * Per the Apache builder's Javadoc, if a non-null instance of {@link org.apache.http.conn.HttpClientConnectionManager}
- * is set on the Apache builder via
- * {@link org.apache.http.impl.client.HttpClientBuilder#setConnectionManager},
- * this supersedes various other properties set on the Apache builder. This includes the following
- * instances/properties on the Apache builder:
- * 
- * <ul>
- * <li><code>SSLSocketFactory</code> ({@link org.apache.http.conn.socket.LayeredConnectionSocketFactory})</li>
- * <li>{@link javax.net.ssl.SSLContext}</li>
- * <li>{@link javax.net.ssl.HostnameVerifier}</li>
- * <li>{@link org.apache.http.config.SocketConfig}</li>
- * <li>{@link ConnectionConfig}</li>
- * <li><code>maxConnTotal</code></li>
- * <li><code>maxConnPerRoute</code></li>
- * </ul>
+ * {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder}, there are several caveats of which to be aware:
  * 
  * <p>
- * Similarly, the following setters on this builder will become ineffective when a non-null connection manger is set on
- * the Apache builder:
+ * Instances of the following will be unconditionally overwritten by this builder when
+ * {@link #buildClient()} is called:
  * </p>
  * 
  * <ul>
- * <li>{@link #setTLSSocketFactory(LayeredConnectionSocketFactory)}</li>
- * <li>{@link #setConnectionDisregardTLSCertificate(boolean)}</li>
- * <li>{@link #setSocketBufferSize(int)}</li>
- * <li>{@link #setHttpContentCharSet(String)}</li>
- * <li>{@link #setMaxConnectionsTotal(int)}</li>
- * <li>{@link #setMaxConnectionsPerRoute(int)}</li>
+ * <li>{@link HttpClientConnectionManager} which includes the following sub-components:</li>
+ *   <ul>
+ *     <li>Default {@link ConnectionConfig}</li>
+ *     <li>{@link HttpConnectionFactory}</li>
+ *     <li>{@link LayeredConnectionSocketFactory} used as the <code>SSLSocketFactory</code></li>
+ *   </ul>
+ * <li>{@link HttpRoutePlanner}</li>
+ * <li>Default {@link RequestConfig}</li>
+ * <li>Default {@link CredentialsProvider}</li>
  * </ul>
  * 
  * <p>
- * Therefore, if you need to explicitly supply a connection manager instance to the Apache builder (for example in order
- * to be able to use {@link IdleConnectionSweeper}), then you must supply these properties or instances directly to the
- * connection manager rather than to this builder or the Apache builder.
+ * This is due to the unfortunate fact that the Apache builder does not currently provide accessor methods to obtain the
+ * default instances currently set on the builder. Therefore, if you need to set any config parameters which are not
+ * exposed by this builder, then you must use the Apache builder directly and may not use this builder.
  * </p>
- * </li>
- * 
- * <li>
- * Similar to the above issue, setting an explicit <code>SSLSocketFactory</code> on the Apache builder will supersede
- * the following Apache builder properties:
- * 
- * <ul>
- * <li>{@link javax.net.ssl.SSLContext}</li>
- * <li>{@link javax.net.ssl.HostnameVerifier}</li>
- * </ul>
- * </li>
- * 
- * </ul>
  */
 public class HttpClientBuilder {
 
@@ -157,6 +111,9 @@ public class HttpClientBuilder {
      */
     @Nonnull private Duration connectionRequestTimeout;
     
+    /** Determines the timeout until arrival of a response from the opposite endpoint. */
+    @Nonnull private Duration responseTimeout;
+    
     /**
      * Max total simultaneous connections allowed by the pooling connection manager.
      */
@@ -177,10 +134,9 @@ public class HttpClientBuilder {
     private boolean connectionCloseAfterResponse;
 
     /**
-     * Whether to check a connection for staleness before using. This can be an expensive operation. Default value:
-     * false
+     * Sets period after inactivity after which persistent connections must be checked to ensure they are still valid.
      */
-    private boolean connectionStaleCheck;
+    @Nullable private Duration validateAfterInactivity;
 
     /** Host name of the HTTP proxy server through which connections will be made. Default value: null. */
     @Nullable private String connectionProxyHost;
@@ -203,11 +159,11 @@ public class HttpClientBuilder {
     /** Character set used for HTTP entity content. Default value: UTF-8 */
     @Nullable private String httpContentCharSet;
 
-    /** Handler which determines if a request should be retried after a recoverable exception during execution. */
-    @Nullable private HttpRequestRetryHandler retryHandler;
-
-    /** Strategy which determines if a request should be retried given the response from the target server. */
-    @Nullable private ServiceUnavailableRetryStrategy serviceUnavailStrategy;
+    /** Strategy which determines whether and how a retry should be attempted. */
+    @Nullable private HttpRequestRetryStrategy retryStrategy;
+    
+    /** Resolver for port based on a scheme. */
+    @Nullable private SchemePortResolver schemePortResolver;
     
     /** Flag for disabling auth caching.*/
     private boolean disableAuthCaching;
@@ -226,10 +182,19 @@ public class HttpClientBuilder {
 
     /** Flag for disabling redirect handling.*/
     private boolean disableRedirectHandling;
-
+    
     /** Flag for enabling use of system properties.*/
     private boolean useSystemProperties;
 
+    /** Flag for evicting expired connections from the connection pool using a background thread. */
+    private boolean evictExpiredConnections;
+    
+    /** Flag for evicting expired connections from the connection pool using a background thread. */
+    private boolean evictIdleConnections;
+
+    /** Max idle time allowed for an idle connection before it is evicted. */
+    private Duration connectionMaxIdleTime;
+
     /** List of request interceptors to add first. */
     @Nonnull @NonnullElements @Unmodifiable @NotLive private List<HttpRequestInterceptor> requestInterceptorsFirst;
 
@@ -246,11 +211,11 @@ public class HttpClientBuilder {
     @Nonnull @NonnullElements @Unmodifiable @NotLive private List<HttpClientContextHandler> staticContextHandlers;
 
     /** The Apache HttpClientBuilder 4.3+ instance over which to layer this builder. */
-    private org.apache.http.impl.client.HttpClientBuilder apacheBuilder;
+    private org.apache.hc.client5.http.impl.classic.HttpClientBuilder apacheBuilder;
 
     /** Constructor. */
     public HttpClientBuilder() {
-        this(org.apache.http.impl.client.HttpClientBuilder.create());
+        this(org.apache.hc.client5.http.impl.classic.HttpClientBuilder.create());
     }
 
     /**
@@ -258,7 +223,7 @@ public class HttpClientBuilder {
      * 
      * @param builder the Apache HttpClientBuilder 4.3+ instance over which to layer this builder
      */
-    public HttpClientBuilder(@Nonnull final org.apache.http.impl.client.HttpClientBuilder builder) {
+    public HttpClientBuilder(@Nonnull final org.apache.hc.client5.http.impl.classic.HttpClientBuilder builder) {
         apacheBuilder = Constraint.isNotNull(builder, "Apache HttpClientBuilder may not be null");
         
         // Defaults are duplicated to avoid static null analyzer issues.
@@ -267,11 +232,11 @@ public class HttpClientBuilder {
         socketLocalAddress = null;
         socketBufferSize = 8192;
         socketTimeout = Duration.ofSeconds(60);
+        responseTimeout = Duration.ofNanos(60);
         connectionTimeout = Duration.ofSeconds(60);
         connectionRequestTimeout = Duration.ofSeconds(60);
         connectionDisregardTLSCertificate = false;
         connectionCloseAfterResponse = true;
-        connectionStaleCheck = false;
         connectionProxyHost = null;
         connectionProxyPort = 8080;
         connectionProxyUsername = null;
@@ -279,6 +244,21 @@ public class HttpClientBuilder {
         httpFollowRedirects = true;
         httpContentCharSet = "UTF-8";
         userAgent = null;
+        validateAfterInactivity = null;
+        retryStrategy = null;
+        schemePortResolver = null;
+        
+        disableAuthCaching = false;
+        disableAutomaticRetries = false;
+        disableConnectionState = false;
+        disableContentCompression = false;
+        disableCookieManagement = false;
+        disableRedirectHandling = false;
+        useSystemProperties = false;
+
+        evictExpiredConnections = false;
+        evictIdleConnections = false;
+        connectionMaxIdleTime = Duration.ofMinutes(30);
         
         requestInterceptorsFirst = CollectionSupport.emptyList();
         requestInterceptorsLast = CollectionSupport.emptyList();
@@ -297,11 +277,11 @@ public class HttpClientBuilder {
         socketLocalAddress = null;
         socketBufferSize = 8192;
         socketTimeout = Duration.ofSeconds(60);
+        responseTimeout = Duration.ofNanos(60);
         connectionTimeout = Duration.ofSeconds(60);
         connectionRequestTimeout = Duration.ofSeconds(60);
         connectionDisregardTLSCertificate = false;
         connectionCloseAfterResponse = true;
-        connectionStaleCheck = false;
         connectionProxyHost = null;
         connectionProxyPort = 8080;
         connectionProxyUsername = null;
@@ -309,7 +289,22 @@ public class HttpClientBuilder {
         httpFollowRedirects = true;
         httpContentCharSet = "UTF-8";
         userAgent = null;
+        validateAfterInactivity = null;
+        retryStrategy = null;
+        schemePortResolver = null;
+        
+        disableAuthCaching = false;
+        disableAutomaticRetries = false;
+        disableConnectionState = false;
+        disableContentCompression = false;
+        disableCookieManagement = false;
+        disableRedirectHandling = false;
+        useSystemProperties = false;
         
+        evictExpiredConnections = false;
+        evictIdleConnections = false;
+        connectionMaxIdleTime = Duration.ofMinutes(30);
+
         requestInterceptorsFirst = CollectionSupport.emptyList();
         requestInterceptorsLast = CollectionSupport.emptyList();
         responseInterceptorsFirst = CollectionSupport.emptyList();
@@ -382,6 +377,27 @@ public class HttpClientBuilder {
         socketLocalAddress = InetAddress.getByName(Constraint.isNotNull(ipOrHost, "IP or hostname may not be null"));
     }
 
+    /**
+     * Gets the timeout until arrival of a response from the opposite endpoint.
+     * 
+     * @return timeout
+     */
+    @Nonnull public Duration getResponseTimeout() {
+        return responseTimeout;
+    }
+
+    /**
+     * Gets the timeout until arrival of a response from the opposite endpoint.
+     * 
+     * @param timeout 
+     */
+    public void setResponseTimeout(@Nonnull final Duration timeout) {
+        Constraint.isNotNull(timeout, "Timeout cannot be null");
+        Constraint.isLessThanOrEqual(Integer.MAX_VALUE, timeout.toMillis(), "Timeout too large");
+
+        responseTimeout = timeout;
+    }
+
     /**
      * Gets the maximum period inactivity between two consecutive data packets. A value of less than 1 ms
      * indicates no timeout.
@@ -532,62 +548,25 @@ public class HttpClientBuilder {
     public void setConnectionCloseAfterResponse(final boolean close) {
         connectionCloseAfterResponse = close;
     }
-
-    /**
-     * Gets whether reused connections are checked if they are closed before being used by the client.
-     * 
-     * @return whether reused connections are checked if they are closed before being used by the client
-     * 
-     * @deprecated use {@link #isConnectionStaleCheck()}
-     */
-    @Deprecated(forRemoval=true)
-    public boolean isConnectionStalecheck() {
-        return isConnectionStaleCheck();
-    }
-
-    /**
-     * Sets whether reused connections are checked if they are closed before being used by the client. Checking can take
-     * up to 30ms (per request). If checking is turned off an I/O error occurs if the connection is used request. This
-     * should be enabled uncles the code using the client explicitly handles the error case and retries connection as
-     * appropriate.
-     * 
-     * @param check whether reused connections are checked if they are closed before being used by the client
-     * 
-     * @deprecated use {@link #setConnectionStaleCheck(boolean)}
-     */
-    @Deprecated(forRemoval=true)
-    public void setConnectionStalecheck(final boolean check) {
-        setConnectionStaleCheck(check);
-    }
     
     /**
-     * Gets whether reused connections are checked if they are closed before being used by the client.
-     * 
-     * @return whether reused connections are checked if they are closed before being used by the client
-     * 
-     * @deprecated use a custom-configured connection pool manger.
-     *     See {@link org.apache.http.impl.conn.PoolingHttpClientConnectionManager#setValidateAfterInactivity(int)}
+     * Gets period after inactivity after which persistent
+     * connections must be checked to ensure they are still valid.
      * 
+     * @return the duration value
      */
-    @Deprecated(forRemoval=true)
-    public boolean isConnectionStaleCheck() {
-        return connectionStaleCheck;
+    @Nullable Duration getValidateAfterInactivity() {
+        return validateAfterInactivity;
     }
 
     /**
-     * Sets whether reused connections are checked if they are closed before being used by the client. Checking can take
-     * up to 30ms (per request). If checking is turned off an I/O error occurs if the connection is used request. This
-     * should be enabled uncles the code using the client explicitly handles the error case and retries connection as
-     * appropriate.
-     * 
-     * @param check whether reused connections are checked if they are closed before being used by the client
+     * Sets period after inactivity after which persistent
+     * connections must be checked to ensure they are still valid.
      * 
-     * @deprecated use a custom-configured connection pool manger.
-     *     See {@link org.apache.http.impl.conn.PoolingHttpClientConnectionManager#setValidateAfterInactivity(int)}
+     * @param duration the duration value
      */
-    @Deprecated(forRemoval=true)
-    public void setConnectionStaleCheck(final boolean check) {
-        connectionStaleCheck = check;
+    public void setValidateAfterInactivity(@Nullable final Duration duration) {
+        validateAfterInactivity = duration;
     }
 
     /**
@@ -719,39 +698,39 @@ public class HttpClientBuilder {
     }
 
     /**
-     * Get the handler which determines if a request should be retried after a recoverable exception during execution.
+     * Get the strategy which determines whether and how a retry should be attempted.
      * 
-     * @return handler which determines if a request should be retried
+     * @return strategy which determines if a request should be retried
      */
-    @Nullable public HttpRequestRetryHandler getHttpRequestRetryHandler() {
-        return retryHandler;
+    @Nullable public HttpRequestRetryStrategy getHttpRequestRetryStrategy() {
+        return retryStrategy;
     }
 
     /**
-     * Set the handler which determines if a request should be retried after a recoverable exception during execution.
+     * Set the strategy which determines whether and how a retry should be attempted.
      * 
-     * @param handler handler which determines if a request should be retried
+     * @param strategy handler which determines if a request should be retried
      */
-    public void setHttpRequestRetryHandler(@Nullable final HttpRequestRetryHandler handler) {
-        retryHandler = handler;
+    public void setHttpRequestRetryStrategy(@Nullable final HttpRequestRetryStrategy strategy) {
+        retryStrategy = strategy;
     }
-
+    
     /**
-     * Get the handler which determines if a request should be retried given the response from the target server.
+     * Get the resolver for port based on a scheme.
      * 
-     * @return handler which determines if a request should be retried
+     * @return the resolver, or null
      */
-    @Nullable public ServiceUnavailableRetryStrategy getServiceUnavailableRetryHandler() {
-        return serviceUnavailStrategy;
+    @Nullable public SchemePortResolver getSchemePortResolver() {
+        return schemePortResolver;
     }
-
+    
     /**
-     * Set the strategy which determines if a request should be retried given the response from the target server.
+     * Set the resolver for port based on a scheme.
      * 
-     * @param strategy handler which determines if a request should be retried
+     * @param resolver the resolver, or null
      */
-    public void setServiceUnavailableRetryHandler(@Nullable final ServiceUnavailableRetryStrategy strategy) {
-        serviceUnavailStrategy = strategy;
+    public void setSchemePortResolver(@Nullable final SchemePortResolver resolver) {
+        schemePortResolver = resolver;
     }
     
     /** 
@@ -881,6 +860,63 @@ public class HttpClientBuilder {
         useSystemProperties = flag;
     }
 
+    /**
+     * Get the flag for evicting expired connections from the connection pool using a background thread. 
+     * 
+     * @return true if enabled, false if not
+     */
+    public boolean isEvictExpiredConnections() {
+        return evictExpiredConnections;
+    }
+
+    /**
+     * Set the flag for evicting expired connections from the connection pool using a background thread. 
+     * 
+     * @param flag true if enabled, false if not
+     */
+    public void setEvictExpiredConnections(final boolean flag) {
+        evictExpiredConnections = flag;
+    }
+
+    /**
+     * Get the flag for evicting idle connections from the connection pool using a background thread. 
+     * 
+     * @return true if enabled, false if not
+     */
+    public boolean isEvictIdleConnections() {
+        return evictIdleConnections;
+    }
+
+    /**
+     * Set the flag for evicting idle connections from the connection pool using a background thread. 
+     * 
+     * @param flag true if enabled, false if not
+     */
+    public void setEvictIdleConnections(final boolean flag) {
+        evictIdleConnections = flag;
+    }
+
+    /**
+     * Get the max idle time allowed for an idle connection before it is evicted. 
+     * 
+     * @return max idle time
+     */
+    @Nonnull  Duration getConnectionMaxIdleTime() {
+        return connectionMaxIdleTime;
+    }
+
+    /**
+     * Set the max idle time allowed for an idle connection before it is evicted. 
+     * 
+     * @param duration the max idle time
+     */
+    public void setConnectionMaxIdleTime(@Nonnull final Duration duration) {
+        Constraint.isNotNull(duration, "Connection max idle time cannot be null");
+        Constraint.isLessThanOrEqual(Integer.MAX_VALUE, duration.toMillis(), "Connection max idle time too large");
+
+        connectionMaxIdleTime = duration;
+    }
+
     /**
      * Get the list of request interceptors to add first.
      * 
@@ -1017,43 +1053,82 @@ public class HttpClientBuilder {
      * 
      * @throws Exception if there is a problem decorating the Apache builder
      */
-    // Checkstyle: CyclomaticComplexity|MethodLength OFF
-    @SuppressWarnings("deprecation")
     protected void decorateApacheBuilder() throws Exception {
-        final org.apache.http.impl.client.HttpClientBuilder builder = getApacheBuilder();
+        final org.apache.hc.client5.http.impl.classic.HttpClientBuilder builder = getApacheBuilder();
         
-        if (getTLSSocketFactory() != null) {
-            builder.setSSLSocketFactory(getTLSSocketFactory());
-        } else if (connectionDisregardTLSCertificate) {
-            builder.setSSLSocketFactory(HttpClientSupport.buildNoTrustTLSSocketFactory());
-        } else {
-            builder.setSSLSocketFactory(HttpClientSupport.buildStrictTLSSocketFactory());
+        builder.setConnectionManager(buildConnectionManager());
+
+        builder.setDefaultRequestConfig(buildDefaultRequestConfig());
+        
+        HttpHost proxyHost = null;
+        if (connectionProxyHost != null) {
+            proxyHost = new HttpHost(connectionProxyHost, connectionProxyPort);
+            builder.setProxy(proxyHost);
         }
 
+        builder.setRoutePlanner(buildRoutePlanner(proxyHost));
+
+        builder.setDefaultCredentialsProvider(buildDefaultCredentialsProvider());
+        
+        handleFluentProperties(builder);
+        
         if (connectionCloseAfterResponse) {
             if (!getFirstRequestInterceptors().stream().anyMatch(RequestConnectionClose.class::isInstance)
                     && !getLastRequestInterceptors().stream().anyMatch(RequestConnectionClose.class::isInstance)) {
-                builder.addInterceptorLast(new RequestConnectionClose());
+                builder.addRequestInterceptorLast(new RequestConnectionClose());
             }
         }
         
-        if (maxConnectionsTotal > 0) {
-            builder.setMaxConnTotal(maxConnectionsTotal);
+        getFirstRequestInterceptors().forEach(builder::addRequestInterceptorFirst);
+        getLastRequestInterceptors().forEach(builder::addRequestInterceptorLast);
+        getFirstResponseInterceptors().forEach(builder::addResponseInterceptorFirst);
+        getLastResponseInterceptors().forEach(builder::addResponseInterceptorLast);
+
+        if (retryStrategy != null) {
+            builder.setRetryStrategy(retryStrategy);
         }
-        
-        if (maxConnectionsPerRoute > 0) {
-            builder.setMaxConnPerRoute(maxConnectionsPerRoute);
+ 
+        if (null != userAgent) {
+            builder.setUserAgent(userAgent);
         }
-        
-        if (retryHandler != null) {
-            builder.setRetryHandler(retryHandler);
+    }
+    
+    /**
+     * Build the instance of {@link HttpRoutePlanner}.
+     * 
+     * This is only necessary if we have a configured local address via
+     * (@link {@link #getSocketLocalAddress()}).
+     * 
+     * @param proxyHost the proxy host, or null
+     * @return the route planner instance, or null
+     */
+    @Nullable protected HttpRoutePlanner buildRoutePlanner(@Nullable final HttpHost proxyHost) {
+        // By default we only need to build this if we have an explicitly-configured local address
+        if (socketLocalAddress == null) {
+            return null;
         }
         
-        if (serviceUnavailStrategy != null) {
-            builder.setServiceUnavailableRetryStrategy(serviceUnavailStrategy);
-        }
+        // This logic for the selection of the route planner impl to use is mirrored from
+        // the Apache HttpClientBuilder#build().
+        final SchemePortResolver resolver = schemePortResolver != null ?
+                schemePortResolver : DefaultSchemePortResolver.INSTANCE;
         
-        // These boolean and interceptor properties can otherwise only be supplied
+        if (proxyHost != null) {
+            return new LocalAddressProxyRoutePlanner(socketLocalAddress, proxyHost, resolver);
+        } else if (isUseSystemProperties()) {
+            return new LocalAddressSystemRoutePlanner(socketLocalAddress, resolver, ProxySelector.getDefault());
+        } else {
+            return new LocalAddressRoutePlanner(socketLocalAddress, resolver);
+        }
+    }
+
+    /**
+     * Handle the fluent (non-DI-friendly) builder properties.
+     * 
+     * @param builder the Apache HttpClientBuilder
+     */
+    protected void handleFluentProperties(org.apache.hc.client5.http.impl.classic.HttpClientBuilder builder ) {
+        // These boolean, interceptor, and eviction properties can otherwise only be supplied
         // to the Apache builder via a fluent-style API.
         
         if (isDisableAuthCaching()) {
@@ -1083,75 +1158,138 @@ public class HttpClientBuilder {
         if (isUseSystemProperties()) {
             builder.useSystemProperties();
         }
+        
+        if (isEvictExpiredConnections()) {
+            builder.evictExpiredConnections();
+        }
 
-        getFirstRequestInterceptors().forEach(builder::addInterceptorFirst);
-        getLastRequestInterceptors().forEach(builder::addInterceptorLast);
-        getFirstResponseInterceptors().forEach(builder::addInterceptorFirst);
-        getLastResponseInterceptors().forEach(builder::addInterceptorLast);
-
-        // RequestConfig params
+        if (isEvictIdleConnections()) {
+            builder.evictIdleConnections(TimeValue.ofMilliseconds(connectionMaxIdleTime.toMillis()));
+        }
+    }
+    
+    /**
+     * Build default {@link CredentialsProvider}.
+     * 
+     * @return the default credentials provider, or null
+     */
+    @Nullable protected CredentialsProvider buildDefaultCredentialsProvider() {
+        if (connectionProxyHost != null && connectionProxyUsername != null && connectionProxyPassword != null) {
+            // Note proxy HttpHost is set separately
+            return CredentialsProviderBuilder.create()
+                    .add(new AuthScope(connectionProxyHost, connectionProxyPort),
+                            new UsernamePasswordCredentials(connectionProxyUsername,
+                                    connectionProxyPassword.toCharArray()))
+                    .build();
+        }
+        
+        return null;
+    }
+    
+    /**
+     * Build the default instance of {@link RequestConfig}
+     * 
+     * @return the request config instance
+     */
+    @Nonnull protected RequestConfig buildDefaultRequestConfig() {
         final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
 
-        if (socketLocalAddress != null) {
-            requestConfigBuilder.setLocalAddress(socketLocalAddress);
+        if (!connectionRequestTimeout.isNegative()) {
+            requestConfigBuilder.setConnectionRequestTimeout(Timeout.ofMilliseconds(connectionRequestTimeout.toMillis()));
         }
 
-        if (!socketTimeout.isNegative()) {
-            requestConfigBuilder.setSocketTimeout((int) socketTimeout.toMillis());
+        if (!responseTimeout.isNegative()) {
+            requestConfigBuilder.setResponseTimeout(Timeout.ofMilliseconds(responseTimeout.toMillis()));
         }
 
-        if (!connectionTimeout.isNegative()) {
-            requestConfigBuilder.setConnectTimeout((int) connectionTimeout.toMillis());
-        }
+        requestConfigBuilder.setRedirectsEnabled(httpFollowRedirects);
         
-        if (!connectionRequestTimeout.isNegative()) {
-            requestConfigBuilder.setConnectionRequestTimeout((int) connectionRequestTimeout.toMillis());
+        return requestConfigBuilder.build();
+    }
+
+    /**
+     * Build an instance of {@link HttpClientConnectionManager}
+     * 
+     * @return the connection manager instance
+     */
+    @Nonnull protected HttpClientConnectionManager buildConnectionManager() {
+        final PoolingHttpClientConnectionManagerBuilder connMgrBuilder =
+                PoolingHttpClientConnectionManagerBuilder.create();
+        
+        connMgrBuilder.setConnectionFactory(buildConnectionFactory());
+        
+        connMgrBuilder.setDefaultConnectionConfig(buildDefaultConnectionConfig());
+
+        if (getTLSSocketFactory() != null) {
+            connMgrBuilder.setSSLSocketFactory(getTLSSocketFactory());
+        } else if (connectionDisregardTLSCertificate) {
+            connMgrBuilder.setSSLSocketFactory(HttpClientSupport.buildNoTrustTLSSocketFactory());
+        } else {
+            connMgrBuilder.setSSLSocketFactory(HttpClientSupport.buildStrictTLSSocketFactory());
+        }
+
+        if (maxConnectionsTotal > 0) {
+            connMgrBuilder.setMaxConnTotal(maxConnectionsTotal);
+        }
+
+        if (maxConnectionsPerRoute > 0) {
+            connMgrBuilder.setMaxConnPerRoute(maxConnectionsPerRoute);
         }
         
-        // TODO: When we remove the deprecated 'connectionStaleCheck' when we move to HC 5,
-        // be sure to also remove the @SuppressWarnings("deprecation") on this method.
-        requestConfigBuilder.setStaleConnectionCheckEnabled(connectionStaleCheck);
+        return connMgrBuilder.build();
+    }
 
-        requestConfigBuilder.setRedirectsEnabled(httpFollowRedirects);
+    /**
+     * Build the default instance of {@link ConnectionConfig}.
+     * 
+     * @return the default connection config instance
+     */
+    protected ConnectionConfig buildDefaultConnectionConfig() {
+        ConnectionConfig.Builder builder = ConnectionConfig.custom();
+        if (!connectionTimeout.isNegative()) {
+            builder.setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout.toMillis()));
+        }
 
-        if (connectionProxyHost != null) {
-            final HttpHost proxyHost = new HttpHost(connectionProxyHost, connectionProxyPort);
-            requestConfigBuilder.setProxy(proxyHost);
-
-            if (connectionProxyUsername != null && connectionProxyPassword != null) {
-                final CredentialsProvider credProvider = new BasicCredentialsProvider();
-                credProvider.setCredentials(new AuthScope(connectionProxyHost, connectionProxyPort),
-                        new UsernamePasswordCredentials(connectionProxyUsername, connectionProxyPassword));
-                builder.setDefaultCredentialsProvider(credProvider);
-            }
+        if (!socketTimeout.isNegative()) {
+            builder.setSocketTimeout(Timeout.ofMilliseconds(socketTimeout.toMillis()));
+        }
+
+        if (validateAfterInactivity != null) {
+                builder.setValidateAfterInactivity(TimeValue.ofMilliseconds(validateAfterInactivity.toMillis()));
         }
 
-        // ConnectionConfig params
-        final ConnectionConfig.Builder connectionConfigBuilder = ConnectionConfig.custom();
+        return builder.build();
+    }
+
+    /**
+     * Build an instance of {@link HttpConnectionFactory}.
+     * 
+     * @return the connection factory instance
+     */
+    @Nonnull protected HttpConnectionFactory<ManagedHttpClientConnection> buildConnectionFactory() {
+        ManagedHttpClientConnectionFactory.Builder builder = ManagedHttpClientConnectionFactory.builder();
 
-        connectionConfigBuilder.setBufferSize(socketBufferSize);
+        builder.http1Config(Http1Config.custom()
+                .setBufferSize(socketBufferSize)
+                .build());
 
         if (httpContentCharSet != null) {
-            connectionConfigBuilder.setCharset(CharsetUtils.get(httpContentCharSet));
+            builder.charCodingConfig(CharCodingConfig.custom()
+                    .setCharset(Charset.forName(httpContentCharSet))
+                    .build());
         }
 
-        builder.setDefaultRequestConfig(requestConfigBuilder.build());
-        builder.setDefaultConnectionConfig(connectionConfigBuilder.build());
-        
-        if (null != userAgent) {
-            builder.setUserAgent(userAgent);
-        }
+        return builder.build();
     }
-    // Checkstyle: CyclomaticComplexity|MethodLength ON
 
     /**
-     * Get the Apache {@link org.apache.http.impl.client.HttpClientBuilder} instance over which this builder will be
-     * layered. Subclasses may override to return a specialized subclass.
+     * Get the Apache {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder} instance over which this builder
+     * will be layered. Subclasses may override to return a specialized subclass.
      * 
      * @return the Apache HttpClientBuilder instance to use
      */
-    protected org.apache.http.impl.client.HttpClientBuilder getApacheBuilder() {
+    @Nonnull org.apache.hc.client5.http.impl.classic.HttpClientBuilder getApacheBuilder() {
         return apacheBuilder;
     }
 
-}
\ No newline at end of file
+}
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientContextHandler.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientContextHandler.java
index 275917c0..eb069719 100644
--- a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientContextHandler.java
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientContextHandler.java
@@ -22,8 +22,8 @@ import java.io.IOException;
 import javax.annotation.Nonnull;
 import javax.annotation.concurrent.ThreadSafe;
 
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.ClassicHttpRequest;
 
 /**
  * Extension that allows the {@link HttpClientContext} to be externally manipulated before use.
@@ -41,7 +41,7 @@ public interface HttpClientContextHandler {
      * 
      * @throws IOException if the call should be aborted
      */
-    void invokeBefore(@Nonnull final HttpClientContext context, @Nonnull final HttpUriRequest request)
+    void invokeBefore(@Nonnull final HttpClientContext context, @Nonnull final ClassicHttpRequest request)
         throws IOException;
     
     /**
@@ -52,7 +52,7 @@ public interface HttpClientContextHandler {
      * 
      * @throws IOException if the call should be aborted
      */
-    void invokeAfter(@Nonnull final HttpClientContext context, @Nonnull final HttpUriRequest request)
+    void invokeAfter(@Nonnull final HttpClientContext context, @Nonnull final ClassicHttpRequest request)
         throws IOException;
     
 }
\ No newline at end of file
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientSupport.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientSupport.java
index 5fc6a466..b4c5b75f 100644
--- a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientSupport.java
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/HttpClientSupport.java
@@ -36,15 +36,14 @@ import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
 
-import org.apache.http.HttpEntity;
-import org.apache.http.ParseException;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
-import org.apache.http.conn.ssl.DefaultHostnameVerifier;
-import org.apache.http.conn.ssl.NoopHostnameVerifier;
-import org.apache.http.entity.ContentType;
-import org.apache.http.protocol.HTTP;
-import org.apache.http.util.CharArrayBuffer;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
+import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier;
+import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.ParseException;
+import org.apache.hc.core5.util.CharArrayBuffer;
 import org.slf4j.Logger;
 
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
@@ -52,7 +51,7 @@ import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
- * Support class for using {@link org.apache.http.client.HttpClient} and related components.
+ * Support class for using {@link org.apache.hc.client5.http.classic.HttpClient} and related components.
  */
 public final class HttpClientSupport {
     
@@ -141,6 +140,7 @@ public final class HttpClientSupport {
     @Nonnull public static List<HttpClientContextHandler> getDynamicContextHandlerList(
             @Nonnull final HttpClientContext context) {
         Constraint.isNotNull(context, "HttpClientContext was null");
+        @SuppressWarnings("unchecked")
         List<HttpClientContextHandler> handlers =
                 context.getAttribute(CONTEXT_KEY_DYNAMIC_CONTEXT_HANDLERS, List.class);
         if (handlers == null) {
@@ -244,7 +244,7 @@ public final class HttpClientSupport {
             }
             Charset charset = null;
             try {
-                final ContentType contentType = ContentType.get(entity);
+                final ContentType contentType = ContentType.parse(entity.getContentType());
                 if (contentType != null) {
                     charset = contentType.getCharset();
                 }
@@ -255,7 +255,7 @@ public final class HttpClientSupport {
                 charset = defaultCharset;
             }
             if (charset == null) {
-                charset = HTTP.DEF_CONTENT_CHARSET;
+                charset = Charset.forName("ISO-8859-1");
             }
             try (final Reader reader = new InputStreamReader(instream, charset)) {
                 final CharArrayBuffer buffer = new CharArrayBuffer(i);
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/IdleConnectionSweeper.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/IdleConnectionSweeper.java
deleted file mode 100644
index 9a8cdd0e..00000000
--- a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/IdleConnectionSweeper.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.shared.httpclient;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import net.shibboleth.shared.component.DestroyedComponentException;
-import net.shibboleth.shared.component.DestructableComponent;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.TimerSupport;
-
-import org.apache.http.conn.HttpClientConnectionManager;
-
-/** A utility that periodically closes idle connections held by an {@link HttpClientConnectionManager}. */
-public class IdleConnectionSweeper implements DestructableComponent {
-
-    /** Whether this sweeper has been destroyed. */
-    private boolean destroyed;
-
-    /**
-     * Whether this sweeper created a {@link TimerTask} to use and thus should destroy when {@link #destroy()} is
-     * invoked.
-     */
-    private boolean createdTimer;
-
-    /** Time at which the sweeper last executed. */
-    @Nullable private Instant executionTime;
-
-    /** HttpClientConnectionManager whose connections will be swept. */
-    @Nonnull private final HttpClientConnectionManager connectionManager;
-
-    /** Timer used to schedule and execute the sweeping task. */
-    @Nonnull private final Timer taskTimer;
-
-    /** Sweeping task executed by the timer. */
-    @Nonnull private final TimerTask sweeper;
-
-    /**
-     * Constructor. This method will create a daemon {@link Timer} and use it to periodically sweep connections.
-     * 
-     * @param manager HTTP client connection manager whose connections will be swept
-     * @param idleTimeout length of time connection may be idle before being closed down
-     * @param sweepInterval length of time between sweeps
-     */
-    public IdleConnectionSweeper(@Nonnull final HttpClientConnectionManager manager,
-            @Nonnull final Duration idleTimeout, @Nonnull final Duration sweepInterval) {
-        this(manager, idleTimeout, sweepInterval,
-                new Timer(TimerSupport.getTimerName(IdleConnectionSweeper.class.getName(), null), true));
-        createdTimer = true;
-    }
-
-    /**
-     * Constructor.
-     * 
-     * @param manager HTTP client connection manager whose connections will be swept
-     * @param idleTimeout length of time connection may be idle before being closed down
-     * @param sweepInterval length of time between sweeps
-     * @param backgroundTimer timer used to schedule the background sweeping task
-     */
-    public IdleConnectionSweeper(@Nonnull final HttpClientConnectionManager manager,
-            @Nonnull final Duration idleTimeout, @Nonnull final Duration sweepInterval,
-            @Nonnull final Timer backgroundTimer) {
-        connectionManager = Constraint.isNotNull(manager, "HttpClientConnectionManager can not be null");
-        taskTimer = Constraint.isNotNull(backgroundTimer, "Sweeper task timer can not be null");
-
-        sweeper = new TimerTask() {
-            public void run() {
-                executionTime = Instant.now();
-                connectionManager.closeIdleConnections(idleTimeout.toMillis(), TimeUnit.MILLISECONDS);
-            }
-        };
-
-        taskTimer.schedule(sweeper, sweepInterval.toMillis(), sweepInterval.toMillis());
-    }
-
-    /**
-     * Gets the time when the sweeper last executed or, if it has not yet executed,
-     * when it was first scheduled to run.
-     * 
-     * @return the time when the sweeper last executed or when it was first scheduled to run
-     */
-    @Nonnull public Instant scheduledExecutionTime() {
-        if (isDestroyed()) {
-            throw new DestroyedComponentException();
-        }
-
-        if (executionTime != null) {
-            return executionTime;
-        }
-        
-        return Instant.ofEpochMilli(sweeper.scheduledExecutionTime());
-    }
-
-    /** {@inheritDoc} */
-    public boolean isDestroyed() {
-        return destroyed;
-    }
-
-    /** {@inheritDoc} */
-    public synchronized void destroy() {
-        sweeper.cancel();
-
-        if (createdTimer) {
-            taskTimer.cancel();
-        }
-
-        destroyed = true;
-    }
-}
\ No newline at end of file
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/InMemoryCachingHttpClientBuilder.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/InMemoryCachingHttpClientBuilder.java
index 4b8c9ca9..3535fe66 100644
--- a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/InMemoryCachingHttpClientBuilder.java
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/InMemoryCachingHttpClientBuilder.java
@@ -19,15 +19,15 @@ package net.shibboleth.shared.httpclient;
 
 import javax.annotation.Nonnull;
 
-import org.apache.http.impl.client.cache.BasicHttpCacheStorage;
-import org.apache.http.impl.client.cache.CacheConfig;
-import org.apache.http.impl.client.cache.CachingHttpClientBuilder;
-import org.apache.http.impl.client.cache.HeapResourceFactory;
+import org.apache.hc.client5.http.impl.cache.BasicHttpCacheStorage;
+import org.apache.hc.client5.http.impl.cache.CacheConfig;
+import org.apache.hc.client5.http.impl.cache.CachingHttpClientBuilder;
+import org.apache.hc.client5.http.impl.cache.HeapResourceFactory;
 
 import net.shibboleth.shared.logic.Constraint;
 
 /**
- * An {@link org.apache.http.client.HttpClient} builder that supports RFC 2616 caching.
+ * An {@link org.apache.hc.client5.http.classic.HttpClient} builder that supports RFC 2616 caching.
  * <p>
  * This client will cache information retrieved from the remote server in memory. The backing store does
  * <strong>not</strong> perform any resource management (e.g., removing content that has nominally expired) so, special
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/LocalAddressProxyRoutePlanner.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/LocalAddressProxyRoutePlanner.java
new file mode 100644
index 00000000..1082aeec
--- /dev/null
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/LocalAddressProxyRoutePlanner.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.shared.httpclient;
+
+import java.net.InetAddress;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.SchemePortResolver;
+import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+/**
+ * Extension of {@link DefaultProxyRoutePlanner} which allows supplying a static local address.
+ */
+public class LocalAddressProxyRoutePlanner extends DefaultProxyRoutePlanner {
+    
+    /** The local address. */
+    @Nullable InetAddress address;
+
+    /**
+     * Constructor.
+     *
+     * @param localAddress the local address
+     * @param proxy the proxy host
+     * @param schemePortResolver the scheme port resolver
+     */
+    public LocalAddressProxyRoutePlanner(@Nullable final InetAddress localAddress, @Nonnull final HttpHost proxy,
+            @Nullable final SchemePortResolver schemePortResolver) {
+        super(proxy, schemePortResolver);
+        address = localAddress;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected InetAddress determineLocalAddress(HttpHost firstHop, HttpContext context) throws HttpException {
+        return address;
+    }
+
+}
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/LocalAddressRoutePlanner.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/LocalAddressRoutePlanner.java
new file mode 100644
index 00000000..63e36fa9
--- /dev/null
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/LocalAddressRoutePlanner.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.shared.httpclient;
+
+import java.net.InetAddress;
+
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.SchemePortResolver;
+import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+/**
+ * Extension of {@link DefaultRoutePlanner} which allows supplying a static local address.
+ */
+public class LocalAddressRoutePlanner extends DefaultRoutePlanner {
+    
+    /** The local address. */
+    @Nullable InetAddress address;
+
+    /**
+     * Constructor.
+     *
+     * @param localAddress the local address
+     * @param schemePortResolver the scheme port resolver
+     */
+    public LocalAddressRoutePlanner(@Nullable final InetAddress localAddress,
+            @Nullable final SchemePortResolver schemePortResolver) {
+        super(schemePortResolver);
+        address = localAddress;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected InetAddress determineLocalAddress(HttpHost firstHop, HttpContext context) throws HttpException {
+        return address;
+    }
+
+}
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/LocalAddressSystemRoutePlanner.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/LocalAddressSystemRoutePlanner.java
new file mode 100644
index 00000000..5b985ee9
--- /dev/null
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/LocalAddressSystemRoutePlanner.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.shared.httpclient;
+
+import java.net.InetAddress;
+import java.net.ProxySelector;
+
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.SchemePortResolver;
+import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+/**
+ * Extension of {@link SystemDefaultRoutePlanner} which allows supplying a static local address.
+ */
+public class LocalAddressSystemRoutePlanner extends SystemDefaultRoutePlanner {
+    
+    /** The local address. */
+    @Nullable InetAddress address;
+
+    /**
+     * Constructor.
+     * 
+     * @param localAddress the local address
+     * @param schemePortResolver the schema port resolver
+     * @param proxySelector the proxy selector
+     */
+    public LocalAddressSystemRoutePlanner(@Nullable final InetAddress localAddress,
+            @Nullable final SchemePortResolver schemePortResolver, @Nullable final ProxySelector proxySelector) {
+        super(schemePortResolver, proxySelector);
+        address = localAddress;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected InetAddress determineLocalAddress(HttpHost firstHop, HttpContext context) throws HttpException {
+        return address;
+    }
+
+}
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/RequestConnectionClose.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/RequestConnectionClose.java
index 28885a93..190b6f4c 100644
--- a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/RequestConnectionClose.java
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/RequestConnectionClose.java
@@ -19,30 +19,48 @@ package net.shibboleth.shared.httpclient;
 
 import java.io.IOException;
 
-import org.apache.http.HttpException;
-import org.apache.http.HttpHeaders;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
-import org.apache.http.HttpVersion;
-import org.apache.http.protocol.HttpContext;
+import javax.annotation.Nonnull;
+
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.HeaderElements;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
  * Adds a <code>Connection: close</code> to all HTTP/1.1 requests.
  * 
  * <p>
  * This interceptor essentially disables connection keep-alive support and, by virtue of the server closing the
- * connection, prevents a {@link org.apache.http.conn.ClientConnectionManager} from holding open and reusing
- * connections. If you'd like to allow the {@link org.apache.http.conn.ClientConnectionManager} to hold open connections
- * for a while and potentially reuse them consider using the {@link IdleConnectionSweeper} to limit the amount of time
+ * connection, prevents a {@link org.apache.hc.client5.http.io.HttpClientConnectionManager} from holding open and
+ * reusing connections. If you'd like to allow the {@link org.apache.hc.client5.http.io.HttpClientConnectionManager}
+ * to hold open connections for a while and potentially reuse them, consider using 
+ * {@link HttpClientBuilder#setEvictIdleConnections(boolean)} and 
+ * {@link HttpClientBuilder#setConnectionMaxIdleTime(java.time.Duration)} to limit the amount of time
  * the connections are held open.
  * </p>
  */
 public class RequestConnectionClose implements HttpRequestInterceptor {
+    
+    /** Logger. */
+    @Nonnull static final Logger log = LoggerFactory.getLogger(RequestConnectionClose.class);
 
     /** {@inheritDoc} */
-    public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
-        if (HttpVersion.HTTP_1_1.equals(request.getProtocolVersion())) {
-            request.addHeader(HttpHeaders.CONNECTION, "close");
+    public void process(final HttpRequest request, EntityDetails entity, final HttpContext context)
+            throws HttpException, IOException {
+        log.trace("Saw negotiated protocol version: {}", context.getProtocolVersion());
+        if (HttpVersion.HTTP_1_1.lessEquals(context.getProtocolVersion())) {
+            log.trace("Adding request header '{}: {}'", HttpHeaders.CONNECTION, HeaderElements.CLOSE);
+            request.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
+        } else {
+            log.warn("Negotiated protocol did not match condition, skipping add of request header '{}: {}', "
+                    + "may cause issues related to pooling", HttpHeaders.CONNECTION, HeaderElements.CLOSE);
         }
     }
 }
\ No newline at end of file
diff --git a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/TLSSocketFactory.java b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/TLSSocketFactory.java
index 87841e53..396d9deb 100644
--- a/shib-networking/src/main/java/net/shibboleth/shared/httpclient/TLSSocketFactory.java
+++ b/shib-networking/src/main/java/net/shibboleth/shared/httpclient/TLSSocketFactory.java
@@ -39,13 +39,13 @@ import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
-import org.apache.http.HttpHost;
-import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
-import org.apache.http.conn.ssl.DefaultHostnameVerifier;
-import org.apache.http.conn.ssl.NoopHostnameVerifier;
-import org.apache.http.conn.util.PublicSuffixMatcher;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.util.Args;
+import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
+import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier;
+import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.TimeValue;
 import org.slf4j.Logger;
 
 
@@ -54,7 +54,7 @@ import org.slf4j.Logger;
  * for TLS sockets.
  * 
  * <p>
- * This class is functionally modeled on {@link org.apache.http.conn.ssl.SSLConnectionSocketFactory},
+ * This class is functionally modeled on {@link org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory},
  * but provides better support for subclassing, as well as specific additional features:
  * </p>
  *
@@ -241,7 +241,7 @@ public class TLSSocketFactory implements LayeredConnectionSocketFactory {
     // Checkstyle: ParameterNumber OFF
     /** {@inheritDoc} */
     public Socket connectSocket(
-            final int connectTimeout,
+            TimeValue connectTimeout,
             final Socket socket,
             final HttpHost host,
             final InetSocketAddress remoteAddress,
@@ -258,10 +258,11 @@ public class TLSSocketFactory implements LayeredConnectionSocketFactory {
             sock.bind(localAddress);
         }
         try {
-            if (connectTimeout > 0 && sock.getSoTimeout() == 0) {
-                sock.setSoTimeout(connectTimeout);
+            int connectTimeoutMillis = connectTimeout.toMillisecondsIntBound();
+            if (connectTimeoutMillis > 0 && sock.getSoTimeout() == 0) {
+                sock.setSoTimeout(connectTimeoutMillis);
             }
-            sock.connect(remoteAddress, connectTimeout);
+            sock.connect(remoteAddress, connectTimeoutMillis);
         } catch (final IOException ex) {
             try {
                 sock.close();
diff --git a/shib-networking/src/main/java/net/shibboleth/utilities/java/support/httpclient/HttpClientSupport.java b/shib-networking/src/main/java/net/shibboleth/utilities/java/support/httpclient/HttpClientSupport.java
index 62c2fca4..d656123d 100644
--- a/shib-networking/src/main/java/net/shibboleth/utilities/java/support/httpclient/HttpClientSupport.java
+++ b/shib-networking/src/main/java/net/shibboleth/utilities/java/support/httpclient/HttpClientSupport.java
@@ -27,11 +27,10 @@ import javax.annotation.Nullable;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.X509TrustManager;
 
-import org.apache.http.HttpEntity;
-import org.apache.http.ParseException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.ParseException;
 
 import net.shibboleth.shared.httpclient.HttpClientContextHandler;
 import net.shibboleth.shared.primitive.DeprecationSupport;
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/ContextHandlingHttpClientTest.java b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/ContextHandlingHttpClientTest.java
index 2348cd3a..33fbc43f 100644
--- a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/ContextHandlingHttpClientTest.java
+++ b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/ContextHandlingHttpClientTest.java
@@ -22,20 +22,17 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.ProtocolVersion;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.message.BasicHttpResponse;
-import org.apache.http.protocol.HttpContext;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.ProtocolVersion;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.io.CloseMode;
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
@@ -44,7 +41,8 @@ import org.testng.annotations.Test;
 @SuppressWarnings("javadoc")
 public class ContextHandlingHttpClientTest {
     
-    public static final CloseableHttpResponse STATIC_RESPONSE_HTTP = new MockCloseableHttpResponse(new ProtocolVersion("HTTP", 1, 1), HttpStatus.SC_OK, "OK");
+    public static final ClassicHttpResponse STATIC_RESPONSE_HTTP = 
+            new MockClassicHttpResponse(new ProtocolVersion("HTTP", 1, 1), HttpStatus.SC_OK, "OK");
         
     public static final Object STATIC_RESPONSE_HANDLER = new Object();
     
@@ -55,9 +53,9 @@ public class ContextHandlingHttpClientTest {
     
     private HttpClientContext context;
     
-    private HttpUriRequest request;
+    private ClassicHttpRequest request;
     private HttpHost target;
-    private ResponseHandler<Object> responseHandler = new MockResponseHandler();
+    private HttpClientResponseHandler<Object> responseHandler = new MockResponseHandler();
     
     @BeforeClass
     public void setupClass() {
@@ -77,7 +75,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testNoHandlers() throws ClientProtocolException, IOException {
+    public void testNoHandlers() throws IOException {
         client = new ContextHandlingHttpClient(new MockHttpClient());
         context = HttpClientContext.create();
         
@@ -89,13 +87,13 @@ public class ContextHandlingHttpClientTest {
         
         //Context execute methods
         Assert.assertSame(client.execute(request, context), STATIC_RESPONSE_HTTP);
-        Assert.assertSame(client.execute(request, responseHandler, context), STATIC_RESPONSE_HANDLER);
+        Assert.assertSame(client.execute(request, context, responseHandler), STATIC_RESPONSE_HANDLER);
         Assert.assertSame(client.execute(target, request, context), STATIC_RESPONSE_HTTP);
-        Assert.assertSame(client.execute(target, request, responseHandler, context), STATIC_RESPONSE_HANDLER);
+        Assert.assertSame(client.execute(target, request, context, responseHandler), STATIC_RESPONSE_HANDLER);
     }
     
     @Test
-    public void testStaticOnly() throws ClientProtocolException, IOException {
+    public void testStaticOnly() throws IOException {
         client = new ContextHandlingHttpClient(new MockHttpClient(), List.of(staticOne, staticTwo, staticThree));
         
         List<String> control = List.of(
@@ -112,7 +110,7 @@ public class ContextHandlingHttpClientTest {
         Assert.assertEquals(context.getAttribute(TestContextHandler.TEST_KEY), control);
         
         context = HttpClientContext.create();
-        Assert.assertSame(client.execute(request, responseHandler, context), STATIC_RESPONSE_HANDLER);
+        Assert.assertSame(client.execute(request, context, responseHandler), STATIC_RESPONSE_HANDLER);
         Assert.assertEquals(context.getAttribute(TestContextHandler.TEST_KEY), control);
         
         context = HttpClientContext.create();
@@ -120,12 +118,12 @@ public class ContextHandlingHttpClientTest {
         Assert.assertEquals(context.getAttribute(TestContextHandler.TEST_KEY), control);
         
         context = HttpClientContext.create();
-        Assert.assertSame(client.execute(target, request, responseHandler, context), STATIC_RESPONSE_HANDLER);
+        Assert.assertSame(client.execute(target, request, context, responseHandler), STATIC_RESPONSE_HANDLER);
         Assert.assertEquals(context.getAttribute(TestContextHandler.TEST_KEY), control);
     }
     
     @Test
-    public void testDynamicOnly() throws ClientProtocolException, IOException {
+    public void testDynamicOnly() throws IOException {
         client = new ContextHandlingHttpClient(new MockHttpClient());
         
         List<String> control = List.of(
@@ -146,7 +144,7 @@ public class ContextHandlingHttpClientTest {
         
         context = HttpClientContext.create();
         HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-        Assert.assertSame(client.execute(request, responseHandler, context), STATIC_RESPONSE_HANDLER);
+        Assert.assertSame(client.execute(request, context, responseHandler), STATIC_RESPONSE_HANDLER);
         Assert.assertEquals(context.getAttribute(TestContextHandler.TEST_KEY), control);
         
         context = HttpClientContext.create();
@@ -156,12 +154,12 @@ public class ContextHandlingHttpClientTest {
         
         context = HttpClientContext.create();
         HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-        Assert.assertSame(client.execute(target, request, responseHandler, context), STATIC_RESPONSE_HANDLER);
+        Assert.assertSame(client.execute(target, request, context, responseHandler), STATIC_RESPONSE_HANDLER);
         Assert.assertEquals(context.getAttribute(TestContextHandler.TEST_KEY), control);
     }
     
     @Test
-    public void testStaticAndDynamic() throws ClientProtocolException, IOException {
+    public void testStaticAndDynamic() throws IOException {
         client = new ContextHandlingHttpClient(new MockHttpClient(), List.of(staticOne, staticTwo, staticThree));
         
         List<String> control = List.of(
@@ -188,7 +186,7 @@ public class ContextHandlingHttpClientTest {
         
         context = HttpClientContext.create();
         HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-        Assert.assertSame(client.execute(request, responseHandler, context), STATIC_RESPONSE_HANDLER);
+        Assert.assertSame(client.execute(request, context, responseHandler), STATIC_RESPONSE_HANDLER);
         Assert.assertEquals(context.getAttribute(TestContextHandler.TEST_KEY), control);
         
         context = HttpClientContext.create();
@@ -198,12 +196,12 @@ public class ContextHandlingHttpClientTest {
         
         context = HttpClientContext.create();
         HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-        Assert.assertSame(client.execute(target, request, responseHandler, context), STATIC_RESPONSE_HANDLER);
+        Assert.assertSame(client.execute(target, request, context, responseHandler), STATIC_RESPONSE_HANDLER);
         Assert.assertEquals(context.getAttribute(TestContextHandler.TEST_KEY), control);
     }
     
     @Test
-    public void testWrappedClientThrowsIOException() throws ClientProtocolException, IOException {
+    public void testWrappedClientThrowsIOException() throws IOException {
         IOException error = new IOException();
         client = new ContextHandlingHttpClient(new MockHttpClient(error), List.of(staticOne, staticTwo, staticThree));
         
@@ -237,7 +235,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, error);
@@ -257,7 +255,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, error);
@@ -266,7 +264,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testWrappedClientThrowsRuntimeException() throws ClientProtocolException, IOException {
+    public void testWrappedClientThrowsRuntimeException() throws IOException {
         RuntimeException error = new RuntimeException();
         client = new ContextHandlingHttpClient(new MockHttpClient(error), List.of(staticOne, staticTwo, staticThree));
         
@@ -300,7 +298,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (RuntimeException e) {
             Assert.assertSame(e, error);
@@ -320,7 +318,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (RuntimeException e) {
             Assert.assertSame(e, error);
@@ -329,7 +327,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testWrappedClientThrowsError() throws ClientProtocolException, IOException {
+    public void testWrappedClientThrowsError() throws IOException {
         Error error = new Error();
         client = new ContextHandlingHttpClient(new MockHttpClient(error), List.of(staticOne, staticTwo, staticThree));
         
@@ -363,7 +361,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (Error e) {
             Assert.assertSame(e, error);
@@ -383,7 +381,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (Error e) {
             Assert.assertSame(e, error);
@@ -392,7 +390,7 @@ public class ContextHandlingHttpClientTest {
     }
      
     @Test
-    public void testSingleStaticHandlerInvokeBeforeThrowsIOException() throws ClientProtocolException, IOException {
+    public void testSingleStaticHandlerInvokeBeforeThrowsIOException() throws IOException {
         IOException error = new IOException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
                 List.of(staticOne, new TestContextHandler("static-2", error, null), staticThree));
@@ -427,7 +425,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, error);
@@ -447,7 +445,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, error);
@@ -456,7 +454,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testSingleStaticHandlerInvokeAfterThrowsIOException() throws ClientProtocolException, IOException {
+    public void testSingleStaticHandlerInvokeAfterThrowsIOException() throws IOException {
         IOException error = new IOException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
                 List.of(staticOne, new TestContextHandler("static-2", null, error), staticThree));
@@ -491,7 +489,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, error);
@@ -511,7 +509,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, error);
@@ -520,7 +518,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testSingleStaticHandlerInvokeBeforeThrowsRuntimeException() throws ClientProtocolException, IOException {
+    public void testSingleStaticHandlerInvokeBeforeThrowsRuntimeException() throws IOException {
         RuntimeException error = new RuntimeException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
                 List.of(staticOne, new TestContextHandler("static-2", error, null), staticThree));
@@ -555,7 +553,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e.getCause(), error);
@@ -575,7 +573,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e.getCause(), error);
@@ -584,7 +582,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testSingleStaticHandlerInvokeAfterThrowsRuntimeException() throws ClientProtocolException, IOException {
+    public void testSingleStaticHandlerInvokeAfterThrowsRuntimeException() throws IOException {
         RuntimeException error = new RuntimeException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
                 List.of(staticOne, new TestContextHandler("static-2", null, error), staticThree));
@@ -619,7 +617,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e.getCause(), error);
@@ -639,7 +637,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e.getCause(), error);
@@ -648,7 +646,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testMultipleStaticHandlersInvokeBeforeThrowIOException() throws ClientProtocolException, IOException {
+    public void testMultipleStaticHandlersInvokeBeforeThrowIOException() throws IOException {
         IOException error1 = new IOException();
         IOException error3 = new IOException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
@@ -687,7 +685,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -713,7 +711,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -725,7 +723,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testMultipleStaticHandlersInvokeBeforeThrowRuntimeException() throws ClientProtocolException, IOException {
+    public void testMultipleStaticHandlersInvokeBeforeThrowRuntimeException() throws IOException {
         RuntimeException error1 = new RuntimeException();
         RuntimeException error3 = new RuntimeException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
@@ -764,7 +762,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -790,7 +788,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -803,7 +801,7 @@ public class ContextHandlingHttpClientTest {
     
     
     @Test
-    public void testMultipleStaticHandlersInvokeAfterThrowIOException() throws ClientProtocolException, IOException {
+    public void testMultipleStaticHandlersInvokeAfterThrowIOException() throws IOException {
         IOException error1 = new IOException();
         IOException error3 = new IOException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
@@ -842,7 +840,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -868,7 +866,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -880,7 +878,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testMultipleStaticHandlersInvokeAfterThrowRuntimeException() throws ClientProtocolException, IOException {
+    public void testMultipleStaticHandlersInvokeAfterThrowRuntimeException() throws IOException {
         RuntimeException error1 = new RuntimeException();
         RuntimeException error3 = new RuntimeException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
@@ -919,7 +917,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -945,7 +943,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -958,7 +956,7 @@ public class ContextHandlingHttpClientTest {
     
     
     @Test
-    public void testSingleDynamicHandlerInvokeBeforeThrowsIOException() throws ClientProtocolException, IOException {
+    public void testSingleDynamicHandlerInvokeBeforeThrowsIOException() throws IOException {
         IOException error = new IOException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
                 List.of(staticOne, staticTwo, staticThree));
@@ -993,7 +991,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, error);
@@ -1013,7 +1011,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, error);
@@ -1022,7 +1020,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testSingleDynamicHandlerInvokeAfterThrowsIOException() throws ClientProtocolException, IOException {
+    public void testSingleDynamicHandlerInvokeAfterThrowsIOException() throws IOException {
         IOException error = new IOException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
                 List.of(staticOne, staticTwo, staticThree));
@@ -1057,7 +1055,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, error);
@@ -1077,7 +1075,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, error);
@@ -1087,7 +1085,7 @@ public class ContextHandlingHttpClientTest {
     
     
     @Test
-    public void testSingleDynamicHandlerInvokeBeforeThrowsRuntimeException() throws ClientProtocolException, IOException {
+    public void testSingleDynamicHandlerInvokeBeforeThrowsRuntimeException() throws IOException {
         RuntimeException error = new RuntimeException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
                 List.of(staticOne, staticTwo, staticThree));
@@ -1122,7 +1120,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e.getCause(), error);
@@ -1142,7 +1140,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e.getCause(), error);
@@ -1151,7 +1149,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testSingleDynamicHandlerInvokeAfterThrowsRuntimeException() throws ClientProtocolException, IOException {
+    public void testSingleDynamicHandlerInvokeAfterThrowsRuntimeException() throws IOException {
         RuntimeException error = new RuntimeException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
                 List.of(staticOne, staticTwo, staticThree));
@@ -1186,7 +1184,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e.getCause(), error);
@@ -1206,7 +1204,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e.getCause(), error);
@@ -1216,7 +1214,7 @@ public class ContextHandlingHttpClientTest {
     
     
     @Test
-    public void testMultipleDynamicHandlersInvokeBeforeThrowIOException() throws ClientProtocolException, IOException {
+    public void testMultipleDynamicHandlersInvokeBeforeThrowIOException() throws IOException {
         IOException error1 = new IOException();
         IOException error3 = new IOException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), List.of(staticOne, staticTwo, staticThree));
@@ -1255,7 +1253,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -1281,7 +1279,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -1293,7 +1291,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testMultipleDynamicHandlersInvokeBeforeThrowRuntimeException() throws ClientProtocolException, IOException {
+    public void testMultipleDynamicHandlersInvokeBeforeThrowRuntimeException() throws IOException {
         RuntimeException error1 = new RuntimeException();
         RuntimeException error3 = new RuntimeException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), List.of(staticOne, staticTwo, staticThree));
@@ -1332,7 +1330,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -1358,7 +1356,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -1370,7 +1368,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testMultipleDynamicHandlersInvokeAfterThrowIOException() throws ClientProtocolException, IOException {
+    public void testMultipleDynamicHandlersInvokeAfterThrowIOException() throws IOException {
         IOException error1 = new IOException();
         IOException error3 = new IOException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), List.of(staticOne, staticTwo, staticThree));
@@ -1409,7 +1407,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -1435,7 +1433,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -1447,7 +1445,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testMultipleDynamicHandlersInvokeAfterThrowRuntimeException() throws ClientProtocolException, IOException {
+    public void testMultipleDynamicHandlersInvokeAfterThrowRuntimeException() throws IOException {
         RuntimeException error1 = new RuntimeException();
         RuntimeException error3 = new RuntimeException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), List.of(staticOne, staticTwo, staticThree));
@@ -1486,7 +1484,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -1512,7 +1510,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertNotSame(e, error1);
@@ -1525,7 +1523,7 @@ public class ContextHandlingHttpClientTest {
     
     
     @Test
-    public void testStaticAndDynamicHandlersThrowIOException() throws ClientProtocolException, IOException {
+    public void testStaticAndDynamicHandlersThrowIOException() throws IOException {
         IOException staticBeforeError = null;
         IOException dynamicAfterError = null;
         
@@ -1575,7 +1573,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(dynamicHandlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, staticBeforeError);
@@ -1613,7 +1611,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(dynamicHandlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, staticBeforeError);
@@ -1624,7 +1622,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testStaticAndDynamicHandlersThrowRuntimeException() throws ClientProtocolException, IOException {
+    public void testStaticAndDynamicHandlersThrowRuntimeException() throws IOException {
         RuntimeException staticBeforeError = new RuntimeException();
         RuntimeException dynamicAfterError = new RuntimeException();
         client = new ContextHandlingHttpClient(new MockHttpClient(), 
@@ -1662,7 +1660,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e.getCause(), staticBeforeError);
@@ -1686,7 +1684,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(handlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e.getCause(), staticBeforeError);
@@ -1697,7 +1695,7 @@ public class ContextHandlingHttpClientTest {
     }
     
     @Test
-    public void testWrappedClientThrowsIOExceptionInvokeAfterThrowsIOException() throws ClientProtocolException, IOException {
+    public void testWrappedClientThrowsIOExceptionInvokeAfterThrowsIOException() throws IOException {
         IOException clientError = null;
         IOException dynamicAfterError = null;
         
@@ -1747,7 +1745,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(dynamicHandlers);
-            client.execute(request, responseHandler, context);
+            client.execute(request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, clientError);
@@ -1785,7 +1783,7 @@ public class ContextHandlingHttpClientTest {
         try {
             context = HttpClientContext.create();
             HttpClientSupport.getDynamicContextHandlerList(context).addAll(dynamicHandlers);
-            client.execute(target, request, responseHandler, context);
+            client.execute(target, request, context, responseHandler);
             Assert.fail("Wrapped client should have thrown");
         } catch (IOException e) {
             Assert.assertSame(e, clientError);
@@ -1821,7 +1819,7 @@ public class ContextHandlingHttpClientTest {
         }
         
         /** {@inheritDoc} */
-        public void invokeBefore(HttpClientContext context, HttpUriRequest request) throws IOException {
+        public void invokeBefore(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             if (name != null) {
                 addValue(context, "before-" + name);
             }
@@ -1829,7 +1827,7 @@ public class ContextHandlingHttpClientTest {
         }
 
         /** {@inheritDoc} */
-        public void invokeAfter(HttpClientContext context, HttpUriRequest request) throws IOException {
+        public void invokeAfter(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             if (name != null) {
                 addValue(context, "after-" + name);
             }
@@ -1847,7 +1845,7 @@ public class ContextHandlingHttpClientTest {
         
     }
     
-    private static class MockHttpClient extends CloseableHttpClient {
+    private static class MockHttpClient extends AbstractHttpClient {
         
         private Throwable error;
         
@@ -1857,20 +1855,6 @@ public class ContextHandlingHttpClientTest {
         public MockHttpClient(final Throwable throwable) {
             error = throwable;
         }
-
-        /** {@inheritDoc} */
-        @Deprecated
-        @Override
-        public org.apache.http.params.HttpParams getParams() {
-            return null;
-        }
-
-        /** {@inheritDoc} */
-        @Deprecated
-        @Override
-        public org.apache.http.conn.ClientConnectionManager getConnectionManager() {
-            return null;
-        }
         
         /** {@inheritDoc} */
         public void close() throws IOException {
@@ -1878,26 +1862,34 @@ public class ContextHandlingHttpClientTest {
         }
 
         /** {@inheritDoc} */
-        protected CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, HttpContext context)
-                throws IOException, ClientProtocolException {
+        public void close(CloseMode closeMode) {
+           // nothing to do 
+        }
+        
+        /** {@inheritDoc} */
+        protected ClassicHttpResponse doExecute(HttpHost target, ClassicHttpRequest request, HttpContext context)
+                throws IOException {
             ThrowableHelper.checkAndThrowError(error);
             return STATIC_RESPONSE_HTTP;
         }
-        
+
     }
 
-    public static class MockResponseHandler implements ResponseHandler<Object> {
+    public static class MockResponseHandler implements HttpClientResponseHandler<Object> {
 
-        public Object handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+        public Object handleResponse(ClassicHttpResponse response) throws IOException {
             return STATIC_RESPONSE_HANDLER;
         }
 
     }
     
-    public static class MockCloseableHttpResponse extends BasicHttpResponse implements CloseableHttpResponse {
+    public static class MockClassicHttpResponse extends BasicClassicHttpResponse {
+
+        private static final long serialVersionUID = -3530926876971302045L;
 
-        public MockCloseableHttpResponse(ProtocolVersion ver, int code, String reason) {
-            super(ver, code, reason);
+        public MockClassicHttpResponse(ProtocolVersion ver, int code, String reason) {
+            super(code, reason);
+            this.setVersion(ver);
         }
 
         public void close() throws IOException {
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/FileCachingHttpClientBuilderTest.java b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/FileCachingHttpClientBuilderTest.java
index b107ecdd..a6d1c115 100644
--- a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/FileCachingHttpClientBuilderTest.java
+++ b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/FileCachingHttpClientBuilderTest.java
@@ -17,7 +17,7 @@
 
 package net.shibboleth.shared.httpclient;
 
-import org.apache.http.client.HttpClient;
+import org.apache.hc.client5.http.classic.HttpClient;
 import org.testng.annotations.Test;
 
 @SuppressWarnings("javadoc")
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/HttpClientBuilderTest.java b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/HttpClientBuilderTest.java
index 497471d0..17b64a34 100644
--- a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/HttpClientBuilderTest.java
+++ b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/HttpClientBuilderTest.java
@@ -22,9 +22,9 @@ import java.io.IOException;
 import java.time.Duration;
 import java.util.List;
 
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -92,12 +92,12 @@ public class HttpClientBuilderTest {
     public class TestContextHandler implements HttpClientContextHandler {
 
         /** {@inheritDoc} */
-        public void invokeBefore(HttpClientContext context, HttpUriRequest request) throws IOException {
+        public void invokeBefore(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             
         }
 
         /** {@inheritDoc} */
-        public void invokeAfter(HttpClientContext context, HttpUriRequest request) throws IOException {
+        public void invokeAfter(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             
         }
         
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/HttpClientSupportTest.java b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/HttpClientSupportTest.java
index 524e6abe..9487fa31 100644
--- a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/HttpClientSupportTest.java
+++ b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/HttpClientSupportTest.java
@@ -21,8 +21,8 @@ import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -134,12 +134,12 @@ public class HttpClientSupportTest {
     public class TestContextHandler implements HttpClientContextHandler {
 
         /** {@inheritDoc} */
-        public void invokeBefore(HttpClientContext context, HttpUriRequest request) throws IOException {
+        public void invokeBefore(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             
         }
 
         /** {@inheritDoc} */
-        public void invokeAfter(HttpClientContext context, HttpUriRequest request) throws IOException {
+        public void invokeAfter(HttpClientContext context, ClassicHttpRequest request) throws IOException {
             
         }
         
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/IdleConectionSweeperTest.java b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/IdleConectionSweeperTest.java
deleted file mode 100644
index 4a7fdd0b..00000000
--- a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/IdleConectionSweeperTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.shared.httpclient;
-
-import java.time.Duration;
-import java.util.Timer;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import net.shibboleth.shared.component.DestroyedComponentException;
-
-/** {@link IdleConnectionSweeper} unit test. */
- at SuppressWarnings("javadoc")
-public class IdleConectionSweeperTest {
-
-    private final Duration SWEEP_INTERVAL = Duration.ofMillis(50);
-
-    @Test public void test() throws Exception {
-        MyCm  connectionManager = new MyCm();
-
-        IdleConnectionSweeper sweeper = new IdleConnectionSweeper(connectionManager, Duration.ofMillis(30), SWEEP_INTERVAL);
-        Thread.yield(); // for luck.
-        if (!connectionManager.isCloseCalled()) {
-            Thread.sleep(25+SWEEP_INTERVAL.toMillis());
-            Thread.yield();
-            if (!connectionManager.isCloseCalled()) {
-                // Windows sometimes takes its time...
-                Thread.sleep(25+SWEEP_INTERVAL.toMillis());
-                Thread.yield();
-                Assert.assertTrue(connectionManager.isCloseCalled());
-            }
-        }
-
-        sweeper.destroy();
-        Assert.assertTrue(sweeper.isDestroyed());
-
-        try {
-            sweeper.scheduledExecutionTime();
-            Assert.fail();
-        } catch (DestroyedComponentException e) {
-            // expected this
-        }
-
-        connectionManager = new MyCm();
-
-        Timer timer = new Timer(true);
-        sweeper = new IdleConnectionSweeper(connectionManager, Duration.ofMillis(30), SWEEP_INTERVAL, timer);
-        Thread.yield();
-        if (!connectionManager.isCloseCalled()) {
-            Thread.sleep(SWEEP_INTERVAL.toMillis());
-            Thread.yield();
-            int loopCount = 0;
-            while (!connectionManager.isCloseCalled()) {
-                // Windows sometimes takes its time...
-                Thread.sleep(SWEEP_INTERVAL.toMillis());
-                Thread.yield();
-                Assert.assertTrue(++loopCount != 5); // 5 timed 50 ms is quarter of a second
-            }
-        }
-
-        sweeper.destroy();
-        Assert.assertTrue(sweeper.isDestroyed());
-
-        try {
-            sweeper.scheduledExecutionTime();
-            Assert.fail();
-        } catch (DestroyedComponentException e) {
-            // expected this
-        }
-        timer.cancel();
-    }
-    
-    private class MyCm extends PoolingHttpClientConnectionManager {
-        private boolean closeCalled;
-        public void closeIdleConnections(long idletime, TimeUnit timeUnit) {
-            closeCalled = true;
-            super.closeIdleConnections(idletime, timeUnit);
-        }
-        public boolean isCloseCalled() {
-            return closeCalled;
-        }
-    }
-}
\ No newline at end of file
diff --git a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/InMemoryCachingHttpClientBuilderTest.java b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/InMemoryCachingHttpClientBuilderTest.java
index ab4de830..0ce59664 100644
--- a/shib-networking/src/test/java/net/shibboleth/shared/httpclient/InMemoryCachingHttpClientBuilderTest.java
+++ b/shib-networking/src/test/java/net/shibboleth/shared/httpclient/InMemoryCachingHttpClientBuilderTest.java
@@ -17,7 +17,7 @@
 
 package net.shibboleth.shared.httpclient;
 
-import org.apache.http.client.HttpClient;
+import org.apache.hc.client5.http.classic.HttpClient;
 import org.testng.annotations.Test;
 
 @SuppressWarnings("javadoc")

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list