You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2024/02/29 16:12:43 UTC

(httpcomponents-client) 02/02: HttpClientContext and HttpCacheContext to use instance variables for standard attributes

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit eb6fd6b1c25c2d345436a9ee64c3c16a57f69680
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Thu Feb 22 11:56:56 2024 +0100

    HttpClientContext and HttpCacheContext to use instance variables for standard attributes
---
 .../hc/client5/http/cache/CacheResponseStatus.java |   5 +-
 .../hc/client5/http/cache/HttpCacheContext.java    | 424 +++++++++++++++++++--
 .../client5/http/impl/cache/AsyncCachingExec.java  |  64 ++--
 .../hc/client5/http/impl/cache/CachingExec.java    |  62 +--
 .../impl/cache/CachingHttpAsyncClientBuilder.java  |   9 +
 .../http/impl/cache/CachingHttpClientBuilder.java  |   9 +
 .../cache/example/AsyncClientCacheControl.java     |  12 +-
 .../http/cache/example/ClientCacheControl.java     |  12 +-
 .../impl/cache/TestProtocolAllowedBehavior.java    |   6 +-
 .../impl/cache/TestProtocolRecommendations.java    |   6 +-
 .../http/impl/cache/TestProtocolRequirements.java  |   6 +-
 .../http/impl/cache/TestRFC5861Compliance.java     |   6 +-
 .../TestHttp1AsyncStatefulConnManagement.java      |   6 +-
 .../testing/sync/TestConnectionManagement.java     |   8 +-
 .../testing/sync/TestDefaultClientTlsStrategy.java |  26 +-
 .../testing/sync/TestStatefulConnManagement.java   |   6 +-
 .../http/impl/async/HttpAsyncClientBuilder.java    |  10 +
 .../async/InternalAbstractHttpAsyncClient.java     |   6 +-
 .../http/impl/async/InternalH2AsyncClient.java     |   3 +-
 .../http/impl/async/InternalHttpAsyncClient.java   |   6 +-
 .../http/impl/async/MinimalH2AsyncClient.java      |   2 +-
 .../http/impl/async/MinimalHttpAsyncClient.java    |   6 +-
 .../http/impl/classic/HttpClientBuilder.java       |   9 +
 .../http/impl/classic/InternalHttpClient.java      |   6 +-
 .../http/impl/classic/MinimalHttpClient.java       |   2 +-
 .../client5/http/protocol/HttpClientContext.java   | 301 +++++++++++++--
 .../examples/ClientMultiThreadedExecution.java     |   2 +-
 .../http/examples/ClientRemoteEndpointDetails.java |   2 +-
 .../impl/TestDefaultConnKeepAliveStrategy.java     |   2 +-
 .../http/impl/auth/TestHttpAuthenticator.java      |   2 +-
 .../auth/TestSystemDefaultCredentialsProvider.java |   2 +-
 .../client5/http/impl/classic/TestConnectExec.java |  18 +-
 .../http/impl/classic/TestInternalHttpClient.java  |   2 +-
 .../http/impl/classic/TestMainClientExec.java      |  18 +-
 .../http/impl/classic/TestProtocolExec.java        |   6 +-
 .../impl/io/TestHttpClientConnectionOperator.java  |  18 +-
 .../impl/routing/TestDefaultProxyRoutePlanner.java |   2 +-
 .../http/impl/routing/TestDefaultRoutePlanner.java |  10 +-
 .../routing/TestSystemDefaultRoutePlanner.java     |   6 +-
 .../http/protocol/TestRequestDefaultHeaders.java   |   6 +-
 .../http/protocol/TestRequestExpectContinue.java   |   8 +-
 .../http/protocol/TestRequestValidateTrace.java    |   2 +-
 42 files changed, 880 insertions(+), 244 deletions(-)

diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheResponseStatus.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheResponseStatus.java
index 5d17cc6a0..14c7a8a86 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheResponseStatus.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheResponseStatus.java
@@ -28,10 +28,7 @@ package org.apache.hc.client5.http.cache;
 
 /**
  * This enumeration represents the various ways a response can be generated
- * by the caching {@link org.apache.hc.client5.http.classic.HttpClient};
- * if a request is executed with an {@link HttpCacheContext}
- * then a parameter with one of these values will be registered in the
- * context under the key  {@link HttpCacheContext#CACHE_RESPONSE_STATUS}.
+ * by an HTTP cache.
  */
 public enum CacheResponseStatus {
 
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheContext.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheContext.java
index 65936b619..34df6b79a 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheContext.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheContext.java
@@ -26,51 +26,103 @@
  */
 package org.apache.hc.client5.http.cache;
 
+import java.util.Map;
+
+import javax.net.ssl.SSLSession;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.RouteInfo;
+import org.apache.hc.client5.http.auth.AuthCache;
+import org.apache.hc.client5.http.auth.AuthExchange;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthSchemeFactory;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.cookie.CookieOrigin;
+import org.apache.hc.client5.http.cookie.CookieSpec;
+import org.apache.hc.client5.http.cookie.CookieSpecFactory;
+import org.apache.hc.client5.http.cookie.CookieStore;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.protocol.RedirectLocations;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.http.EndpointDetails;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.ProtocolVersion;
+import org.apache.hc.core5.http.config.Lookup;
 import org.apache.hc.core5.http.protocol.HttpContext;
 
 /**
- * Adaptor class that provides convenience type safe setters and getters
- * for caching {@link HttpContext} attributes.
+ * Cache execution {@link HttpContext}. This class can be re-used for
+ * multiple consecutive logically related request executions that represent
+ * a single communication session. This context may not be used concurrently.
+ * <p>
+ * IMPORTANT: This class is NOT thread-safe and MUST NOT be used concurrently by
+ * multiple message exchanges.
  *
  * @since 4.3
  */
 public class HttpCacheContext extends HttpClientContext {
 
     /**
-     * This is the name under which the {@link CacheResponseStatus} of a request
-     * (for example, whether it resulted in a cache hit) will be recorded if an
-     * {@link HttpContext} is provided during execution.
+     * @deprecated Use getter methods
      */
+    @Deprecated
     public static final String CACHE_RESPONSE_STATUS = "http.cache.response.status";
+    static final String REQUEST_CACHE_CONTROL = "http.cache.request-control";
+    static final String RESPONSE_CACHE_CONTROL = "http.cache.response-control";
+    static final String CACHE_ENTRY = "http.cache.entry";
 
     /**
-     * @since 5.4
+     * @deprecated Use {@link #castOrCreate(HttpContext)}.
      */
-    public static final String CACHE_ENTRY = "http.cache.entry";
-
-    /**
-     * @since 5.4
-     */
-    public static final String CACHE_REQUEST_CONTROL = "http.cache.request.control";
+    @Deprecated
+    public static HttpCacheContext adapt(final HttpContext context) {
+        if (context instanceof HttpCacheContext) {
+            return (HttpCacheContext) context;
+        }
+        return new Delegate(HttpClientContext.castOrCreate(context));
+    }
 
     /**
+     * Casts the given generic {@link HttpContext} as {@link HttpCacheContext} or
+     * throws an {@link IllegalStateException} if the given context is not suitable.
+     *
      * @since 5.4
      */
-    public static final String CACHE_RESPONSE_CONTROL = "http.cache.response.control";
-
-    public static HttpCacheContext adapt(final HttpContext context) {
+    public static HttpCacheContext cast(final HttpContext context) {
+        if (context == null) {
+            return null;
+        }
         if (context instanceof HttpCacheContext) {
             return (HttpCacheContext) context;
+        } else if (context instanceof HttpClientContext) {
+            return new Delegate((HttpClientContext) context);
         } else {
-            return new HttpCacheContext(context);
+            return new Delegate(HttpClientContext.cast(context));
         }
     }
 
+    /**
+     * Casts the given generic {@link HttpContext} as {@link HttpCacheContext} or
+     * creates new {@link HttpCacheContext} if the given context is null..
+     *
+     * @since 5.4
+     */
+    public static HttpCacheContext castOrCreate(final HttpContext context) {
+        return context != null ? cast(context) : create();
+    }
+
     public static HttpCacheContext create() {
         return new HttpCacheContext();
     }
 
+    private CacheResponseStatus responseStatus;
+    private RequestCacheControl requestCacheControl;
+    private ResponseCacheControl responseCacheControl;
+    private HttpCacheEntry cacheEntry;
+
     public HttpCacheContext(final HttpContext context) {
         super(context);
     }
@@ -79,59 +131,377 @@ public class HttpCacheContext extends HttpClientContext {
         super();
     }
 
+    /**
+     * Represents an outcome of the cache operation and the way the response has been
+     * generated.
+     * <p>
+     * This context attribute is expected to be populated by the protocol handler.
+     */
     public CacheResponseStatus getCacheResponseStatus() {
-        return getAttribute(CACHE_RESPONSE_STATUS, CacheResponseStatus.class);
+        return responseStatus;
     }
 
     /**
      * @since 5.4
      */
-    public void setCacheResponseStatus(final CacheResponseStatus status) {
-        setAttribute(CACHE_RESPONSE_STATUS, status);
+    @Internal
+    public void setCacheResponseStatus(final CacheResponseStatus responseStatus) {
+        this.responseStatus = responseStatus;
     }
 
     /**
+     * Represents cache control requested by the client.
+     * <p>
+     * This context attribute is expected to be set by the caller.
+     *
      * @since 5.4
      */
     public RequestCacheControl getRequestCacheControl() {
-        final RequestCacheControl cacheControl = getAttribute(CACHE_REQUEST_CONTROL, RequestCacheControl.class);
+        return requestCacheControl;
+    }
+
+    /**
+     * Returns cache control requested by the client or {@link RequestCacheControl#DEFAULT}
+     * if not explicitly set in the context.
+     *
+     * @since 5.4
+     */
+    public final RequestCacheControl getRequestCacheControlOrDefault() {
+        final RequestCacheControl cacheControl = getRequestCacheControl();
         return cacheControl != null ? cacheControl : RequestCacheControl.DEFAULT;
     }
 
     /**
      * @since 5.4
      */
+    @Internal
     public void setRequestCacheControl(final RequestCacheControl requestCacheControl) {
-        setAttribute(CACHE_REQUEST_CONTROL, requestCacheControl);
+        this.requestCacheControl = requestCacheControl;
     }
 
     /**
+     * Represents cache control enforced by the server.
+     * <p>
+     * This context attribute is expected to be populated by the protocol handler.
+     *
      * @since 5.4
      */
     public ResponseCacheControl getResponseCacheControl() {
-        final ResponseCacheControl cacheControl = getAttribute(CACHE_RESPONSE_CONTROL, ResponseCacheControl.class);
+        return responseCacheControl;
+    }
+
+    /**
+     * Represents cache control enforced by the server or {@link ResponseCacheControl#DEFAULT}
+     * if not explicitly set in the context.
+     *
+     * @since 5.4
+     */
+    public final ResponseCacheControl getResponseCacheControlOrDefault() {
+        final ResponseCacheControl cacheControl = getResponseCacheControl();
         return cacheControl != null ? cacheControl : ResponseCacheControl.DEFAULT;
     }
 
     /**
      * @since 5.4
      */
+    @Internal
     public void setResponseCacheControl(final ResponseCacheControl responseCacheControl) {
-        setAttribute(CACHE_RESPONSE_CONTROL, responseCacheControl);
+        this.responseCacheControl = responseCacheControl;
     }
 
     /**
+     * Represents the cache entry the resource of which has been used to generate the response.
+     * <p>
+     * This context attribute is expected to be populated by the protocol handler.
+     *
      * @since 5.4
      */
     public HttpCacheEntry getCacheEntry() {
-        return getAttribute(CACHE_ENTRY, HttpCacheEntry.class);
+        return cacheEntry;
     }
 
     /**
      * @since 5.4
      */
-    public void setCacheEntry(final HttpCacheEntry entry) {
-        setAttribute(CACHE_ENTRY, entry);
+    @Internal
+    public void setCacheEntry(final HttpCacheEntry cacheEntry) {
+        this.cacheEntry = cacheEntry;
+    }
+
+    /**
+     * Internal adaptor class that delegates all its method calls to {@link HttpClientContext}.
+     * To be removed in the future.
+     */
+    @SuppressWarnings("deprecation")
+    @Internal
+    static class Delegate extends HttpCacheContext {
+
+        private final HttpClientContext clientContext;
+
+        Delegate(final HttpClientContext clientContext) {
+            super(null);
+            this.clientContext = clientContext;
+        }
+
+        @Override
+        public CacheResponseStatus getCacheResponseStatus() {
+            return clientContext.getAttribute(CACHE_RESPONSE_STATUS, CacheResponseStatus.class);
+        }
+
+        @Override
+        public void setCacheResponseStatus(final CacheResponseStatus responseStatus) {
+            clientContext.setAttribute(CACHE_RESPONSE_STATUS, responseStatus);
+        }
+
+        @Override
+        public RequestCacheControl getRequestCacheControl() {
+            return clientContext.getAttribute(REQUEST_CACHE_CONTROL, RequestCacheControl.class);
+        }
+
+        @Override
+        public void setRequestCacheControl(final RequestCacheControl requestCacheControl) {
+            clientContext.setAttribute(REQUEST_CACHE_CONTROL, requestCacheControl);
+        }
+
+        @Override
+        public ResponseCacheControl getResponseCacheControl() {
+            return clientContext.getAttribute(RESPONSE_CACHE_CONTROL, ResponseCacheControl.class);
+        }
+
+        @Override
+        public void setResponseCacheControl(final ResponseCacheControl responseCacheControl) {
+            clientContext.setAttribute(RESPONSE_CACHE_CONTROL, responseCacheControl);
+        }
+
+        @Override
+        public HttpCacheEntry getCacheEntry() {
+            return clientContext.getAttribute(CACHE_ENTRY, HttpCacheEntry.class);
+        }
+
+        @Override
+        public void setCacheEntry(final HttpCacheEntry cacheEntry) {
+            clientContext.setAttribute(CACHE_ENTRY, cacheEntry);
+        }
+
+        @Override
+        public RouteInfo getHttpRoute() {
+            return clientContext.getHttpRoute();
+        }
+
+        @Override
+        @Internal
+        public void setRoute(final HttpRoute route) {
+            clientContext.setRoute(route);
+        }
+
+        @Override
+        public RedirectLocations getRedirectLocations() {
+            return clientContext.getRedirectLocations();
+        }
+
+        @Override
+        @Internal
+        public void setRedirectLocations(final RedirectLocations redirectLocations) {
+            clientContext.setRedirectLocations(redirectLocations);
+        }
+
+        @Override
+        public CookieStore getCookieStore() {
+            return clientContext.getCookieStore();
+        }
+
+        @Override
+        public void setCookieStore(final CookieStore cookieStore) {
+            clientContext.setCookieStore(cookieStore);
+        }
+
+        @Override
+        public CookieSpec getCookieSpec() {
+            return clientContext.getCookieSpec();
+        }
+
+        @Override
+        @Internal
+        public void setCookieSpec(final CookieSpec cookieSpec) {
+            clientContext.setCookieSpec(cookieSpec);
+        }
+
+        @Override
+        public CookieOrigin getCookieOrigin() {
+            return clientContext.getCookieOrigin();
+        }
+
+        @Override
+        @Internal
+        public void setCookieOrigin(final CookieOrigin cookieOrigin) {
+            clientContext.setCookieOrigin(cookieOrigin);
+        }
+
+        @Override
+        public Lookup<CookieSpecFactory> getCookieSpecRegistry() {
+            return clientContext.getCookieSpecRegistry();
+        }
+
+        @Override
+        public void setCookieSpecRegistry(final Lookup<CookieSpecFactory> lookup) {
+            clientContext.setCookieSpecRegistry(lookup);
+        }
+
+        @Override
+        public Lookup<AuthSchemeFactory> getAuthSchemeRegistry() {
+            return clientContext.getAuthSchemeRegistry();
+        }
+
+        @Override
+        public void setAuthSchemeRegistry(final Lookup<AuthSchemeFactory> lookup) {
+            clientContext.setAuthSchemeRegistry(lookup);
+        }
+
+        @Override
+        public CredentialsProvider getCredentialsProvider() {
+            return clientContext.getCredentialsProvider();
+        }
+
+        @Override
+        public void setCredentialsProvider(final CredentialsProvider credentialsProvider) {
+            clientContext.setCredentialsProvider(credentialsProvider);
+        }
+
+        @Override
+        public AuthCache getAuthCache() {
+            return clientContext.getAuthCache();
+        }
+
+        @Override
+        public void setAuthCache(final AuthCache authCache) {
+            clientContext.setAuthCache(authCache);
+        }
+
+        @Override
+        public Map<HttpHost, AuthExchange> getAuthExchanges() {
+            return clientContext.getAuthExchanges();
+        }
+
+        @Override
+        public AuthExchange getAuthExchange(final HttpHost host) {
+            return clientContext.getAuthExchange(host);
+        }
+
+        @Override
+        public void setAuthExchange(final HttpHost host, final AuthExchange authExchange) {
+            clientContext.setAuthExchange(host, authExchange);
+        }
+
+        @Override
+        public void resetAuthExchange(final HttpHost host, final AuthScheme authScheme) {
+            clientContext.resetAuthExchange(host, authScheme);
+        }
+
+        @Override
+        public Object getUserToken() {
+            return clientContext.getUserToken();
+        }
+
+        @Override
+        public void setUserToken(final Object userToken) {
+            clientContext.setUserToken(userToken);
+        }
+
+        @Override
+        public RequestConfig getRequestConfig() {
+            return clientContext.getRequestConfig();
+        }
+
+        @Override
+        public void setRequestConfig(final RequestConfig requestConfig) {
+            clientContext.setRequestConfig(requestConfig);
+        }
+
+        @Override
+        public String getExchangeId() {
+            return clientContext.getExchangeId();
+        }
+
+        @Override
+        public void setExchangeId(final String exchangeId) {
+            clientContext.setExchangeId(exchangeId);
+        }
+
+        @Override
+        public HttpRequest getRequest() {
+            return clientContext.getRequest();
+        }
+
+        @Override
+        public void setRequest(final HttpRequest request) {
+            clientContext.setRequest(request);
+        }
+
+        @Override
+        public HttpResponse getResponse() {
+            return clientContext.getResponse();
+        }
+
+        @Override
+        public void setResponse(final HttpResponse response) {
+            clientContext.setResponse(response);
+        }
+
+        @Override
+        public EndpointDetails getEndpointDetails() {
+            return clientContext.getEndpointDetails();
+        }
+
+        @Override
+        public void setEndpointDetails(final EndpointDetails endpointDetails) {
+            clientContext.setEndpointDetails(endpointDetails);
+        }
+
+        @Override
+        public SSLSession getSSLSession() {
+            return clientContext.getSSLSession();
+        }
+
+        @Override
+        public void setSSLSession(final SSLSession sslSession) {
+            clientContext.setSSLSession(sslSession);
+        }
+
+        @Override
+        public ProtocolVersion getProtocolVersion() {
+            return clientContext.getProtocolVersion();
+        }
+
+        @Override
+        public void setProtocolVersion(final ProtocolVersion version) {
+            clientContext.setProtocolVersion(version);
+        }
+
+        @Override
+        public Object getAttribute(final String id) {
+            return clientContext.getAttribute(id);
+        }
+
+        @Override
+        public Object setAttribute(final String id, final Object obj) {
+            return clientContext.setAttribute(id, obj);
+        }
+
+        @Override
+        public Object removeAttribute(final String id) {
+            return clientContext.removeAttribute(id);
+        }
+
+        @Override
+        public <T> T getAttribute(final String id, final Class<T> clazz) {
+            return clientContext.getAttribute(id, clazz);
+        }
+
+        @Override
+        public String toString() {
+            return clientContext.toString();
+        }
+
     }
 
-}
+}
\ No newline at end of file
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
index 752fc4e0e..074529e41 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
@@ -238,18 +238,18 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
 
         final String exchangeId = scope.exchangeId;
-        final HttpCacheContext context = HttpCacheContext.adapt(scope.clientContext);
+        final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
         final CancellableDependency operation = scope.cancellableDependency;
 
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} request via cache: {}", exchangeId, new RequestLine(request));
         }
 
-        context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MISS);
-        context.setAttribute(HttpCacheContext.CACHE_ENTRY, null);
+        context.setCacheResponseStatus(CacheResponseStatus.CACHE_MISS);
+        context.setCacheEntry(null);
 
         if (clientRequestsOurOptions(request)) {
-            context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+            context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
             triggerResponse(SimpleHttpResponse.create(HttpStatus.SC_NOT_IMPLEMENTED), scope, asyncExecCallback);
             return;
         }
@@ -259,7 +259,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
             context.setRequestCacheControl(requestCacheControl);
         } else {
-            requestCacheControl = context.getRequestCacheControl();
+            requestCacheControl = context.getRequestCacheControlOrDefault();
             CacheControlHeaderGenerator.INSTANCE.generate(requestCacheControl, request);
         }
 
@@ -549,7 +549,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
 
         void triggerNewCacheEntryResponse(final HttpResponse backendResponse, final Instant responseDate, final ByteArrayBuffer buffer) {
             final String exchangeId = scope.exchangeId;
-            final HttpClientContext context = scope.clientContext;
+            final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
             final CancellableDependency operation = scope.cancellableDependency;
             operation.setDependency(responseCache.store(
                     target,
@@ -567,7 +567,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                             }
                             try {
                                 final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
-                                context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                                context.setCacheEntry(hit.entry);
                                 triggerResponse(cacheResponse, scope, asyncExecCallback);
                             } catch (final ResourceIOException ex) {
                                 asyncExecCallback.failed(ex);
@@ -589,10 +589,10 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
         }
 
         void triggerCachedResponse(final HttpCacheEntry entry) {
-            final HttpClientContext context = scope.clientContext;
+            final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
             try {
                 final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, entry);
-                context.setAttribute(HttpCacheContext.CACHE_ENTRY, entry);
+                context.setCacheEntry(entry);
                 triggerResponse(cacheResponse, scope, asyncExecCallback);
             } catch (final ResourceIOException ex) {
                 asyncExecCallback.failed(ex);
@@ -722,14 +722,14 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             final AsyncExecChain.Scope scope,
             final AsyncExecChain chain,
             final AsyncExecCallback asyncExecCallback) {
-        final HttpClientContext context  = scope.clientContext;
+        final HttpCacheContext context  = HttpCacheContext.cast(scope.clientContext);
         final String exchangeId = scope.exchangeId;
 
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} cache hit: {}", exchangeId, new RequestLine(request));
         }
 
-        context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_HIT);
+        context.setCacheResponseStatus(CacheResponseStatus.CACHE_HIT);
         cacheHits.getAndIncrement();
 
         final Instant now = getCurrentDate();
@@ -744,18 +744,18 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             }
             try {
                 final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, now);
-                context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                context.setCacheEntry(hit.entry);
                 triggerResponse(cacheResponse, scope, asyncExecCallback);
             } catch (final ResourceIOException ex) {
                 if (requestCacheControl.isOnlyIfCached()) {
                     if (LOG.isDebugEnabled()) {
                         LOG.debug("{} request marked only-if-cached", exchangeId);
                     }
-                    context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+                    context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                     final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
                     triggerResponse(cacheResponse, scope, asyncExecCallback);
                 } else {
-                    context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.FAILURE);
+                    context.setCacheResponseStatus(CacheResponseStatus.FAILURE);
                     try {
                         chain.proceed(request, entityProducer, scope, asyncExecCallback);
                     } catch (final HttpException | IOException ex2) {
@@ -768,7 +768,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("{} cache entry not is not fresh and only-if-cached requested", exchangeId);
                 }
-                context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+                context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                 final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
                 triggerResponse(cacheResponse, scope, asyncExecCallback);
             } else if (cacheSuitability == CacheSuitability.MISMATCH) {
@@ -815,9 +815,9 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                                 hit.getEntryKey(),
                                 asyncExecCallback,
                                 c -> revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, fork, chain, c));
-                        context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+                        context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                         final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
-                        context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                        context.setCacheEntry(hit.entry);
                         triggerResponse(cacheResponse, scope, asyncExecCallback);
                     } catch (final IOException ex) {
                         asyncExecCallback.failed(ex);
@@ -856,7 +856,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                 responseCacheControl,
                 BasicRequestBuilder.copy(request).build(),
                 hit.entry);
-        final HttpClientContext context = scope.clientContext;
+        final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
         chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
 
             final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
@@ -876,7 +876,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                             public void completed(final CacheHit updated) {
                                 try {
                                     final SimpleHttpResponse cacheResponse = generateCachedResponse(request, updated.entry, responseDate);
-                                    context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                                    context.setCacheEntry(hit.entry);
                                     triggerResponse(cacheResponse, scope, asyncExecCallback);
                                 } catch (final ResourceIOException ex) {
                                     asyncExecCallback.failed(ex);
@@ -899,7 +899,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             AsyncExecCallback evaluateResponse(final HttpResponse backendResponse, final Instant responseDate) {
                 final int statusCode = backendResponse.getCode();
                 if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
-                    context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.VALIDATED);
+                    context.setCacheResponseStatus(CacheResponseStatus.VALIDATED);
                     cacheUpdates.getAndIncrement();
                 }
                 if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
@@ -1015,7 +1015,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             final AsyncExecChain chain,
             final AsyncExecCallback asyncExecCallback) {
         final String exchangeId = scope.exchangeId;
-        final HttpClientContext context = scope.clientContext;
+        final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
         revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, scope, chain, new AsyncExecCallback() {
 
             private final AtomicBoolean committed = new AtomicBoolean();
@@ -1044,7 +1044,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                         LOG.debug("{} I/O error while revalidating cache entry", exchangeId, cause);
                     }
                     final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
-                    context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+                    context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                     triggerResponse(cacheResponse, scope, asyncExecCallback);
                 } else {
                     asyncExecCallback.failed(cause);
@@ -1065,7 +1065,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             final AsyncExecChain chain,
             final AsyncExecCallback asyncExecCallback) {
         final String exchangeId = scope.exchangeId;
-        final HttpClientContext context = scope.clientContext;
+        final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
         revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, scope, chain, new AsyncExecCallback() {
 
             private final AtomicReference<HttpResponse> committed = new AtomicReference<>();
@@ -1095,9 +1095,9 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                 final HttpResponse response = committed.get();
                 if (response == null) {
                     try {
-                        context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+                        context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                         final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
-                        context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                        context.setCacheEntry(hit.entry);
                         triggerResponse(cacheResponse, scope, asyncExecCallback);
                     } catch (final IOException ex) {
                         asyncExecCallback.failed(ex);
@@ -1114,7 +1114,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                     if (LOG.isDebugEnabled()) {
                         LOG.debug("{} I/O error while revalidating cache entry", exchangeId, cause);
                     }
-                    context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+                    context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                     if (cause instanceof IOException &&
                             suitabilityChecker.isSuitableIfError(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())) {
                         if (LOG.isDebugEnabled()) {
@@ -1122,7 +1122,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                         }
                         try {
                             final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
-                            context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                            context.setCacheEntry(hit.entry);
                             triggerResponse(cacheResponse, scope, asyncExecCallback);
                         } catch (final IOException ex) {
                             asyncExecCallback.failed(cause);
@@ -1159,8 +1159,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             if (LOG.isDebugEnabled()) {
                 LOG.debug("{} request marked only-if-cached", exchangeId);
             }
-            final HttpClientContext context = scope.clientContext;
-            context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+            final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
+            context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
             final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
             triggerResponse(cacheResponse, scope, asyncExecCallback);
         }
@@ -1223,8 +1223,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
 
             void updateVariantCacheEntry(final HttpResponse backendResponse, final Instant responseDate, final CacheHit match) {
-                final HttpClientContext context = scope.clientContext;
-                context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.VALIDATED);
+                final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
+                context.setCacheResponseStatus(CacheResponseStatus.VALIDATED);
                 cacheUpdates.getAndIncrement();
 
                 operation.setDependency(responseCache.storeFromNegotiated(
@@ -1240,7 +1240,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                             public void completed(final CacheHit hit) {
                                 try {
                                     final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, responseDate);
-                                    context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                                    context.setCacheEntry(hit.entry);
                                     triggerResponse(cacheResponse, scope, asyncExecCallback);
                                 } catch (final ResourceIOException ex) {
                                     asyncExecCallback.failed(ex);
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
index e76e891a5..185446a0e 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
@@ -144,17 +144,17 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             final ExecChain.Scope scope,
             final ExecChain chain) throws IOException, HttpException {
         final String exchangeId = scope.exchangeId;
-        final HttpCacheContext context = HttpCacheContext.adapt(scope.clientContext);
+        final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
 
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} request via cache: {}", exchangeId, new RequestLine(request));
         }
 
-        context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MISS);
-        context.setAttribute(HttpCacheContext.CACHE_ENTRY, null);
+        context.setCacheResponseStatus(CacheResponseStatus.CACHE_MISS);
+        context.setCacheEntry(null);
 
         if (clientRequestsOurOptions(request)) {
-            context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+            context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
             return new BasicClassicHttpResponse(HttpStatus.SC_NOT_IMPLEMENTED);
         }
 
@@ -162,7 +162,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
         if (request.containsHeader(HttpHeaders.CACHE_CONTROL)) {
             requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
         } else {
-            requestCacheControl = context.getRequestCacheControl();
+            requestCacheControl = context.getRequestCacheControlOrDefault();
             CacheControlHeaderGenerator.INSTANCE.generate(requestCacheControl, request);
         }
 
@@ -245,13 +245,13 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             final ExecChain.Scope scope,
             final ExecChain chain) throws IOException, HttpException {
         final String exchangeId = scope.exchangeId;
-        final HttpClientContext context  = scope.clientContext;
+        final HttpCacheContext context  = HttpCacheContext.cast(scope.clientContext);
 
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} cache hit: {}", exchangeId, new RequestLine(request));
         }
 
-        context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_HIT);
+        context.setCacheResponseStatus(CacheResponseStatus.CACHE_HIT);
         cacheHits.getAndIncrement();
 
         final Instant now = getCurrentDate();
@@ -266,17 +266,17 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             }
             try {
                 final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, now);
-                context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                context.setCacheEntry(hit.entry);
                 return convert(cacheResponse);
             } catch (final ResourceIOException ex) {
                 if (requestCacheControl.isOnlyIfCached()) {
                     if (LOG.isDebugEnabled()) {
                         LOG.debug("{} request marked only-if-cached", exchangeId);
                     }
-                    context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+                    context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                     return convert(generateGatewayTimeout());
                 }
-                context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.FAILURE);
+                context.setCacheResponseStatus(CacheResponseStatus.FAILURE);
                 return chain.proceed(request, scope);
             }
         } else {
@@ -284,7 +284,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("{} cache entry not is not fresh and only-if-cached requested", exchangeId);
                 }
-                context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+                context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                 return convert(generateGatewayTimeout());
             } else if (cacheSuitability == CacheSuitability.MISMATCH) {
                 if (LOG.isDebugEnabled()) {
@@ -325,9 +325,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
                     cacheRevalidator.revalidateCacheEntry(
                             hit.getEntryKey(),
                             () -> revalidateCacheEntry(responseCacheControl, hit, target, request, fork, chain));
-                    context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+                    context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                     final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
-                    context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                    context.setCacheEntry(hit.entry);
                     return convert(cacheResponse);
                 } else {
                     if (LOG.isDebugEnabled()) {
@@ -356,7 +356,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             final ClassicHttpRequest request,
             final ExecChain.Scope scope,
             final ExecChain chain) throws IOException, HttpException {
-        final HttpClientContext context = scope.clientContext;
+        final HttpCacheContext context  = HttpCacheContext.cast(scope.clientContext);
         Instant requestDate = getCurrentDate();
         final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(
                 responseCacheControl, request, hit.entry);
@@ -376,13 +376,13 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
 
             final int statusCode = backendResponse.getCode();
             if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
-                context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.VALIDATED);
+                context.setCacheResponseStatus(CacheResponseStatus.VALIDATED);
                 cacheUpdates.getAndIncrement();
             }
             if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                 final CacheHit updated = responseCache.update(hit, target, request, backendResponse, requestDate, responseDate);
                 final SimpleHttpResponse cacheResponse = generateCachedResponse(request, updated.entry, responseDate);
-                context.setAttribute(HttpCacheContext.CACHE_ENTRY, updated.entry);
+                context.setCacheEntry(updated.entry);
                 return convert(cacheResponse);
             }
             return handleBackendResponse(target, conditionalRequest, scope, requestDate, responseDate, backendResponse);
@@ -400,14 +400,14 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             final ExecChain.Scope scope,
             final ExecChain chain) throws HttpException {
         final String exchangeId = scope.exchangeId;
-        final HttpClientContext context = scope.clientContext;
+        final HttpCacheContext context  = HttpCacheContext.cast(scope.clientContext);
         try {
             return revalidateCacheEntry(responseCacheControl, hit, target, request, scope, chain);
         } catch (final IOException ex) {
             if (LOG.isDebugEnabled()) {
                 LOG.debug("{} I/O error while revalidating cache entry", exchangeId, ex);
             }
-            context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+            context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
             return convert(generateGatewayTimeout());
         }
     }
@@ -421,7 +421,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             final ExecChain.Scope scope,
             final ExecChain chain) throws HttpException, IOException {
         final String exchangeId = scope.exchangeId;
-        final HttpClientContext context = scope.clientContext;
+        final HttpCacheContext context  = HttpCacheContext.cast(scope.clientContext);
         final ClassicHttpResponse response;
         try {
             response = revalidateCacheEntry(responseCacheControl, hit, target, request, scope, chain);
@@ -429,13 +429,13 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             if (LOG.isDebugEnabled()) {
                 LOG.debug("{} I/O error while revalidating cache entry", exchangeId, ex);
             }
-            context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+            context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
             if (suitabilityChecker.isSuitableIfError(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())) {
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("{} serving stale response due to IOException and stale-if-error enabled", exchangeId);
                 }
                 final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
-                context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                context.setCacheEntry(hit.entry);
                 return convert(cacheResponse);
             } else {
                 return convert(generateGatewayTimeout());
@@ -448,9 +448,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
                 LOG.debug("{} serving stale response due to {} status and stale-if-error enabled", exchangeId, status);
             }
             EntityUtils.consume(response.getEntity());
-            context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+            context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
             final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
-            context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+            context.setCacheEntry(hit.entry);
             return convert(cacheResponse);
         }
         return response;
@@ -495,7 +495,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             final Instant requestSent,
             final Instant responseReceived) throws IOException {
         final String exchangeId = scope.exchangeId;
-        final HttpClientContext context = scope.clientContext;
+        final HttpCacheContext context  = HttpCacheContext.cast(scope.clientContext);
         final int statusCode = backendResponse.getCode();
         // handle 304 Not Modified responses
         if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
@@ -510,7 +510,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
                         requestSent,
                         responseReceived);
                 final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updated.entry);
-                context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+                context.setCacheEntry(hit.entry);
                 return convert(cacheResponse);
             }
         }
@@ -560,7 +560,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             }
         }
         final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
-        context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+        context.setCacheEntry(hit.entry);
         return convert(cacheResponse);
     }
 
@@ -578,12 +578,12 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
         }
         cacheMisses.getAndIncrement();
 
-        final HttpClientContext context = scope.clientContext;
+        final HttpCacheContext context  = HttpCacheContext.cast(scope.clientContext);
         if (requestCacheControl.isOnlyIfCached()) {
             if (LOG.isDebugEnabled()) {
                 LOG.debug("{} request marked only-if-cached", exchangeId);
             }
-            context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+            context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
             return convert(generateGatewayTimeout());
         }
         if (partialMatch != null && partialMatch.entry.hasVariants() && request.getEntity() == null) {
@@ -649,13 +649,13 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
                 return callBackend(target, unconditional, scope, chain);
             }
 
-            final HttpClientContext context = scope.clientContext;
-            context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, CacheResponseStatus.VALIDATED);
+            final HttpCacheContext context  = HttpCacheContext.cast(scope.clientContext);
+            context.setCacheResponseStatus(CacheResponseStatus.VALIDATED);
             cacheUpdates.getAndIncrement();
 
             final CacheHit hit = responseCache.storeFromNegotiated(match, target, request, backendResponse, requestDate, responseDate);
             final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, responseDate);
-            context.setAttribute(HttpCacheContext.CACHE_ENTRY, hit.entry);
+            context.setCacheEntry(hit.entry);
             return convert(cacheResponse);
         } catch (final IOException | RuntimeException ex) {
             backendResponse.close();
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java
index ab6249db9..c736c8de9 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java
@@ -29,19 +29,23 @@ package org.apache.hc.client5.http.impl.cache;
 import java.io.File;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.function.Function;
 
 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
 import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
 import org.apache.hc.client5.http.cache.HttpAsyncCacheStorageAdaptor;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
 import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
 import org.apache.hc.client5.http.cache.HttpCacheStorage;
 import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.impl.ChainElement;
 import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
 import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.schedule.SchedulingStrategy;
 import org.apache.hc.core5.annotation.Experimental;
 import org.apache.hc.core5.http.config.NamedElementChain;
+import org.apache.hc.core5.http.protocol.HttpContext;
 
 /**
  * Builder for {@link org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient}
@@ -162,4 +166,9 @@ public class CachingHttpAsyncClientBuilder extends HttpAsyncClientBuilder {
         execChainDefinition.addBefore(ChainElement.PROTOCOL.name(), cachingExec, ChainElement.CACHING.name());
     }
 
+    @Override
+    protected Function<HttpContext, HttpClientContext> contextAdaptor() {
+        return HttpCacheContext::castOrCreate;
+    }
+
 }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
index b12485895..303e1f05b 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
@@ -29,7 +29,9 @@ package org.apache.hc.client5.http.impl.cache;
 import java.io.File;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.function.Function;
 
+import org.apache.hc.client5.http.cache.HttpCacheContext;
 import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
 import org.apache.hc.client5.http.cache.HttpCacheStorage;
 import org.apache.hc.client5.http.cache.ResourceFactory;
@@ -37,8 +39,10 @@ import org.apache.hc.client5.http.classic.ExecChainHandler;
 import org.apache.hc.client5.http.impl.ChainElement;
 import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
 import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.schedule.SchedulingStrategy;
 import org.apache.hc.core5.http.config.NamedElementChain;
+import org.apache.hc.core5.http.protocol.HttpContext;
 
 /**
  * Builder for {@link org.apache.hc.client5.http.impl.classic.CloseableHttpClient}
@@ -153,4 +157,9 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
         execChainDefinition.addBefore(ChainElement.PROTOCOL.name(), cachingExec, ChainElement.CACHING.name());
     }
 
+    @Override
+    protected Function<HttpContext, HttpClientContext> contextAdaptor() {
+        return HttpCacheContext::castOrCreate;
+    }
+
 }
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/AsyncClientCacheControl.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/AsyncClientCacheControl.java
index 4535cebcd..f06cc62fa 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/AsyncClientCacheControl.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/AsyncClientCacheControl.java
@@ -87,8 +87,8 @@ public class AsyncClientCacheControl {
                         public void completed(final SimpleHttpResponse response) {
                             System.out.println(httpget1 + "->" + new StatusLine(response));
                             System.out.println("Cache status: " + context.getCacheResponseStatus());
-                            System.out.println("Request cache control: " + context.getRequestCacheControl());
-                            System.out.println("Response cache control: " + context.getResponseCacheControl());
+                            System.out.println("Request cache control: " + context.getRequestCacheControlOrDefault());
+                            System.out.println("Response cache control: " + context.getResponseCacheControlOrDefault());
                             final HttpCacheEntry cacheEntry = context.getCacheEntry();
                             if (cacheEntry != null) {
                                 System.out.println("Cache entry resource: " + cacheEntry.getResource());
@@ -132,8 +132,8 @@ public class AsyncClientCacheControl {
                         public void completed(final SimpleHttpResponse response) {
                             System.out.println(httpget2 + "->" + new StatusLine(response));
                             System.out.println("Cache status: " + context.getCacheResponseStatus());
-                            System.out.println("Request cache control: " + context.getRequestCacheControl());
-                            System.out.println("Response cache control: " + context.getResponseCacheControl());
+                            System.out.println("Request cache control: " + context.getRequestCacheControlOrDefault());
+                            System.out.println("Response cache control: " + context.getResponseCacheControlOrDefault());
                             final HttpCacheEntry cacheEntry = context.getCacheEntry();
                             if (cacheEntry != null) {
                                 System.out.println("Cache entry resource: " + cacheEntry.getResource());
@@ -179,8 +179,8 @@ public class AsyncClientCacheControl {
                         public void completed(final SimpleHttpResponse response) {
                             System.out.println(httpget3 + "->" + new StatusLine(response));
                             System.out.println("Cache status: " + context.getCacheResponseStatus());
-                            System.out.println("Request cache control: " + context.getRequestCacheControl());
-                            System.out.println("Response cache control: " + context.getResponseCacheControl());
+                            System.out.println("Request cache control: " + context.getRequestCacheControlOrDefault());
+                            System.out.println("Response cache control: " + context.getResponseCacheControlOrDefault());
                             final HttpCacheEntry cacheEntry = context.getCacheEntry();
                             if (cacheEntry != null) {
                                 System.out.println("Cache entry resource: " + cacheEntry.getResource());
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/ClientCacheControl.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/ClientCacheControl.java
index b5ea29cd3..311842c48 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/ClientCacheControl.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/example/ClientCacheControl.java
@@ -74,8 +74,8 @@ public class ClientCacheControl {
                 System.out.println(httpget1 + "->" + new StatusLine(response));
                 EntityUtils.consume(response.getEntity());
                 System.out.println("Cache status: " + context.getCacheResponseStatus());
-                System.out.println("Request cache control: " + context.getRequestCacheControl());
-                System.out.println("Response cache control: " + context.getResponseCacheControl());
+                System.out.println("Request cache control: " + context.getRequestCacheControlOrDefault());
+                System.out.println("Response cache control: " + context.getResponseCacheControlOrDefault());
                 final HttpCacheEntry cacheEntry = context.getCacheEntry();
                 if (cacheEntry != null) {
                     System.out.println("Cache entry resource: " + cacheEntry.getResource());
@@ -102,8 +102,8 @@ public class ClientCacheControl {
                 System.out.println(httpget2 + "->" + new StatusLine(response));
                 EntityUtils.consume(response.getEntity());
                 System.out.println("Cache status: " + context.getCacheResponseStatus());
-                System.out.println("Request cache control: " + context.getRequestCacheControl());
-                System.out.println("Response cache control: " + context.getResponseCacheControl());
+                System.out.println("Request cache control: " + context.getRequestCacheControlOrDefault());
+                System.out.println("Response cache control: " + context.getResponseCacheControlOrDefault());
                 final HttpCacheEntry cacheEntry = context.getCacheEntry();
                 if (cacheEntry != null) {
                     System.out.println("Cache entry resource: " + cacheEntry.getResource());
@@ -132,8 +132,8 @@ public class ClientCacheControl {
                 System.out.println(httpget3 + "->" + new StatusLine(response));
                 EntityUtils.consume(response.getEntity());
                 System.out.println("Cache status: " + context.getCacheResponseStatus());
-                System.out.println("Request cache control: " + context.getRequestCacheControl());
-                System.out.println("Response cache control: " + context.getResponseCacheControl());
+                System.out.println("Request cache control: " + context.getRequestCacheControlOrDefault());
+                System.out.println("Response cache control: " + context.getResponseCacheControlOrDefault());
                 final HttpCacheEntry cacheEntry = context.getCacheEntry();
                 if (cacheEntry != null) {
                     System.out.println("Cache entry resource: " + cacheEntry.getResource());
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolAllowedBehavior.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolAllowedBehavior.java
index 3bfffac31..81669953c 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolAllowedBehavior.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolAllowedBehavior.java
@@ -29,9 +29,9 @@ package org.apache.hc.client5.http.impl.cache;
 import java.io.IOException;
 
 import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
 import org.apache.hc.client5.http.classic.ExecChain;
 import org.apache.hc.client5.http.classic.ExecRuntime;
-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.HttpException;
@@ -59,7 +59,7 @@ public class TestProtocolAllowedBehavior {
 
     HttpHost host;
     HttpRoute route;
-    HttpClientContext context;
+    HttpCacheContext context;
     @Mock
     ExecChain mockExecChain;
     @Mock
@@ -81,7 +81,7 @@ public class TestProtocolAllowedBehavior {
 
         request = new BasicClassicHttpRequest("GET", "/foo");
 
-        context = HttpClientContext.create();
+        context = HttpCacheContext.create();
 
         originResponse = HttpTestUtils.make200Response();
 
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRecommendations.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRecommendations.java
index d5c1fb520..607f35533 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRecommendations.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRecommendations.java
@@ -38,9 +38,9 @@ import java.util.Iterator;
 
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
 import org.apache.hc.client5.http.classic.ExecChain;
 import org.apache.hc.client5.http.classic.ExecRuntime;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
@@ -77,7 +77,7 @@ public class TestProtocolRecommendations {
     HttpHost host;
     HttpRoute route;
     HttpEntity body;
-    HttpClientContext context;
+    HttpCacheContext context;
     @Mock
     ExecChain mockExecChain;
     @Mock
@@ -102,7 +102,7 @@ public class TestProtocolRecommendations {
 
         request = new BasicClassicHttpRequest("GET", "/foo");
 
-        context = HttpClientContext.create();
+        context = HttpCacheContext.create();
 
         originResponse = HttpTestUtils.make200Response();
 
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRequirements.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRequirements.java
index 53a420fec..e813b5ea6 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRequirements.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRequirements.java
@@ -39,10 +39,10 @@ import java.util.Random;
 
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.classic.ExecChain;
 import org.apache.hc.client5.http.classic.ExecRuntime;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
@@ -81,7 +81,7 @@ public class TestProtocolRequirements {
     HttpHost host;
     HttpRoute route;
     HttpEntity body;
-    HttpClientContext context;
+    HttpCacheContext context;
     @Mock
     ExecChain mockExecChain;
     @Mock
@@ -105,7 +105,7 @@ public class TestProtocolRequirements {
 
         request = new BasicClassicHttpRequest("GET", "/");
 
-        context = HttpClientContext.create();
+        context = HttpCacheContext.create();
 
         originResponse = HttpTestUtils.make200Response();
 
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRFC5861Compliance.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRFC5861Compliance.java
index 85a4f7acd..6d7114d16 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRFC5861Compliance.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRFC5861Compliance.java
@@ -37,9 +37,9 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
 import org.apache.hc.client5.http.classic.ExecChain;
 import org.apache.hc.client5.http.classic.ExecRuntime;
-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.HttpEntity;
@@ -70,7 +70,7 @@ public class TestRFC5861Compliance {
     HttpHost host;
     HttpRoute route;
     HttpEntity body;
-    HttpClientContext context;
+    HttpCacheContext context;
     @Mock
     ExecChain mockExecChain;
     @Mock
@@ -94,7 +94,7 @@ public class TestRFC5861Compliance {
 
         request = new BasicClassicHttpRequest("GET", "/foo");
 
-        context = HttpClientContext.create();
+        context = HttpCacheContext.create();
 
         originResponse = HttpTestUtils.make200Response();
 
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java
index 68e2577af..04880fe57 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java
@@ -206,7 +206,7 @@ public class TestHttp1AsyncStatefulConnManagement extends AbstractIntegrationTes
         connManager.setDefaultMaxPerRoute(maxConn);
 
         // Bottom of the pool : a *keep alive* connection to Route 1.
-        final HttpContext context1 = new HttpClientContext();
+        final HttpContext context1 = HttpClientContext.create();
         context1.setAttribute("user", "stuff");
 
         final SimpleHttpRequest request1 = SimpleRequestBuilder.get()
@@ -226,7 +226,7 @@ public class TestHttp1AsyncStatefulConnManagement extends AbstractIntegrationTes
 
         // Send a very simple HTTP get (it MUST be simple, no auth, no proxy, no 302, no 401, ...)
         // Send it to another route. Must be a keepalive.
-        final HttpContext context2 = new HttpClientContext();
+        final HttpContext context2 = HttpClientContext.create();
 
         final SimpleHttpRequest request2 = SimpleRequestBuilder.get()
                 .setScheme(target.getSchemeName())
@@ -249,7 +249,7 @@ public class TestHttp1AsyncStatefulConnManagement extends AbstractIntegrationTes
         // So the ConnPoolByRoute will need to kill one connection (it is maxed out globally).
         // The killed conn is the oldest, which means the first HTTPGet ([localhost][stuff]).
         // When this happens, the RouteSpecificPool becomes empty.
-        final HttpContext context3 = new HttpClientContext();
+        final HttpContext context3 = HttpClientContext.create();
 
         final SimpleHttpRequest request3 = SimpleRequestBuilder.get()
                 .setHttpHost(target)
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java
index 3fa5186fb..8d366d3a6 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java
@@ -128,7 +128,7 @@ public class TestConnectionManagement {
         final String uri = "/random/" + rsplen;
 
         final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", target, uri);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
 
         final LeaseRequest leaseRequest1 = connManager.lease("id1", route, null);
         final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS);
@@ -192,7 +192,7 @@ public class TestConnectionManagement {
         final String uri = "/random/" + rsplen;
 
         final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", target, uri);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
 
         final LeaseRequest leaseRequest1 = connManager.lease("id1", route, null);
         final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS);
@@ -258,7 +258,7 @@ public class TestConnectionManagement {
         connManager.setMaxTotal(1);
 
         final HttpRoute route = new HttpRoute(target, null, false);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
 
         final LeaseRequest leaseRequest1 = connManager.lease("id1", route, null);
         final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS);
@@ -311,7 +311,7 @@ public class TestConnectionManagement {
         connManager.setMaxTotal(1);
 
         final HttpRoute route = new HttpRoute(target, null, false);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
 
         final LeaseRequest leaseRequest1 = connManager.lease("id1", route, null);
         final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS);
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestDefaultClientTlsStrategy.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestDefaultClientTlsStrategy.java
index 8895f12e1..31090af44 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestDefaultClientTlsStrategy.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestDefaultClientTlsStrategy.java
@@ -104,7 +104,7 @@ public class TestDefaultClientTlsStrategy {
         // @formatter:on
         this.server.start();
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
                 SSLTestContexts.createClientSSLContext(), hostVerifier);
@@ -133,7 +133,7 @@ public class TestDefaultClientTlsStrategy {
         // @formatter:on
         this.server.start();
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(SSLTestContexts.createClientSSLContext());
         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
         try (final Socket socket = new Socket(target.getHostName(), target.getPort())) {
@@ -159,7 +159,7 @@ public class TestDefaultClientTlsStrategy {
         // @formatter:on
         this.server.start();
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
                 SSLTestContexts.createClientSSLContext(), hostVerifier);
@@ -189,7 +189,7 @@ public class TestDefaultClientTlsStrategy {
         // @formatter:on
         this.server.start();
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
                 SSLTestContexts.createClientSSLContext(), hostVerifier);
@@ -221,7 +221,7 @@ public class TestDefaultClientTlsStrategy {
         // @formatter:on
         this.server.start();
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         // Use default SSL context
         final SSLContext defaultSslContext = SSLContexts.createDefault();
 
@@ -254,7 +254,7 @@ public class TestDefaultClientTlsStrategy {
         // @formatter:on
         this.server.start();
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
 
         // @formatter:off
         final SSLContext sslContext = SSLContexts.custom()
@@ -287,7 +287,7 @@ public class TestDefaultClientTlsStrategy {
         // @formatter:on
         this.server.start();
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
                 SSLTestContexts.createClientSSLContext());
         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
@@ -339,7 +339,7 @@ public class TestDefaultClientTlsStrategy {
         // @formatter:on
         this.server.start();
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
                 SSLTestContexts.createClientSSLContext());
         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
@@ -369,7 +369,7 @@ public class TestDefaultClientTlsStrategy {
                     SSLTestContexts.createClientSSLContext(),
                     HostnameVerificationPolicy.CLIENT,
                     HttpsSupport.getDefaultHostnameVerifier());
-            final HttpClientContext context = new HttpClientContext();
+            final HttpClientContext context = HttpClientContext.create();
             final SSLSocket upgradedSocket = tlsStrategy.upgrade(
                     socket,
                     target1.getHostName(),
@@ -387,7 +387,7 @@ public class TestDefaultClientTlsStrategy {
                     SSLTestContexts.createClientSSLContext(),
                     HostnameVerificationPolicy.CLIENT,
                     HttpsSupport.getDefaultHostnameVerifier());
-            final HttpClientContext context = new HttpClientContext();
+            final HttpClientContext context = HttpClientContext.create();
             Assertions.assertThrows(SSLPeerUnverifiedException.class, () ->
                     tlsStrategy.upgrade(
                             socket,
@@ -402,7 +402,7 @@ public class TestDefaultClientTlsStrategy {
                     SSLTestContexts.createClientSSLContext(),
                     HostnameVerificationPolicy.CLIENT,
                     NoopHostnameVerifier.INSTANCE);
-            final HttpClientContext context = new HttpClientContext();
+            final HttpClientContext context = HttpClientContext.create();
             final SSLSocket upgradedSocket = tlsStrategy.upgrade(
                     socket,
                     target1.getHostName(),
@@ -430,7 +430,7 @@ public class TestDefaultClientTlsStrategy {
                     SSLTestContexts.createClientSSLContext(),
                     HostnameVerificationPolicy.BUILTIN,
                     NoopHostnameVerifier.INSTANCE);
-            final HttpClientContext context = new HttpClientContext();
+            final HttpClientContext context = HttpClientContext.create();
             final SSLSocket upgradedSocket = tlsStrategy.upgrade(
                     socket,
                     target1.getHostName(),
@@ -448,7 +448,7 @@ public class TestDefaultClientTlsStrategy {
                     SSLTestContexts.createClientSSLContext(),
                     HostnameVerificationPolicy.BUILTIN,
                     NoopHostnameVerifier.INSTANCE);
-            final HttpClientContext context = new HttpClientContext();
+            final HttpClientContext context = HttpClientContext.create();
             Assertions.assertThrows(SSLHandshakeException.class, () ->
                     tlsStrategy.upgrade(
                             socket,
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestStatefulConnManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestStatefulConnManagement.java
index 5a8254caf..d31f50dce 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestStatefulConnManagement.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestStatefulConnManagement.java
@@ -216,7 +216,7 @@ public class TestStatefulConnManagement {
         );
 
         // Bottom of the pool : a *keep alive* connection to Route 1.
-        final HttpContext context1 = new HttpClientContext();
+        final HttpContext context1 = HttpClientContext.create();
         context1.setAttribute("user", "stuff");
         client.execute(target, new HttpGet("/"), context1, response -> {
             EntityUtils.consume(response.getEntity());
@@ -231,7 +231,7 @@ public class TestStatefulConnManagement {
 
         // Send a very simple HTTP get (it MUST be simple, no auth, no proxy, no 302, no 401, ...)
         // Send it to another route. Must be a keepalive.
-        final HttpContext context2 = new HttpClientContext();
+        final HttpContext context2 = HttpClientContext.create();
         client.execute(new HttpHost("127.0.0.1", server.getPort()), new HttpGet("/"), context2, response -> {
             EntityUtils.consume(response.getEntity());
             return null;
@@ -247,7 +247,7 @@ public class TestStatefulConnManagement {
         // So the ConnPoolByRoute will need to kill one connection (it is maxed out globally).
         // The killed conn is the oldest, which means the first HTTPGet ([localhost][stuff]).
         // When this happens, the RouteSpecificPool becomes empty.
-        final HttpContext context3 = new HttpClientContext();
+        final HttpContext context3 = HttpClientContext.create();
         client.execute(target, new HttpGet("/"), context3, response -> {
             EntityUtils.consume(response.getEntity());
             return null;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
index f06a234b8..2784b0b74 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
@@ -36,6 +36,7 @@ import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.ThreadFactory;
+import java.util.function.Function;
 
 import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
@@ -72,6 +73,7 @@ import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
 import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
 import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
 import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.protocol.RedirectStrategy;
 import org.apache.hc.client5.http.protocol.RequestAddCookies;
 import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
@@ -96,6 +98,7 @@ import org.apache.hc.core5.http.config.NamedElementChain;
 import org.apache.hc.core5.http.config.RegistryBuilder;
 import org.apache.hc.core5.http.nio.command.ShutdownCommand;
 import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
+import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http.protocol.HttpProcessor;
 import org.apache.hc.core5.http.protocol.HttpProcessorBuilder;
 import org.apache.hc.core5.http.protocol.RequestTargetHost;
@@ -777,6 +780,12 @@ public class HttpAsyncClientBuilder {
         }
         closeables.add(closeable);
     }
+
+    @Internal
+    protected Function<HttpContext, HttpClientContext> contextAdaptor() {
+        return HttpClientContext::castOrCreate;
+    }
+
     @SuppressWarnings("deprecated")
     public CloseableHttpAsyncClient build() {
         AsyncClientConnectionManager connManagerCopy = this.connManager;
@@ -1047,6 +1056,7 @@ public class HttpAsyncClientBuilder {
                 authSchemeRegistryCopy,
                 cookieStoreCopy,
                 credentialsProviderCopy,
+                contextAdaptor(),
                 defaultRequestConfig,
                 closeablesCopy);
     }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java
index 041224364..74bae9c24 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java
@@ -38,6 +38,7 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
 
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.async.AsyncExecCallback;
@@ -90,6 +91,7 @@ abstract class InternalAbstractHttpAsyncClient extends AbstractHttpAsyncClientBa
     private final Lookup<AuthSchemeFactory> authSchemeRegistry;
     private final CookieStore cookieStore;
     private final CredentialsProvider credentialsProvider;
+    private final Function<HttpContext, HttpClientContext> contextAdaptor;
     private final RequestConfig defaultConfig;
     private final ConcurrentLinkedQueue<Closeable> closeables;
     private final ScheduledExecutorService scheduledExecutorService;
@@ -104,6 +106,7 @@ abstract class InternalAbstractHttpAsyncClient extends AbstractHttpAsyncClientBa
             final Lookup<AuthSchemeFactory> authSchemeRegistry,
             final CookieStore cookieStore,
             final CredentialsProvider credentialsProvider,
+            final Function<HttpContext, HttpClientContext> contextAdaptor,
             final RequestConfig defaultConfig,
             final List<Closeable> closeables) {
         super(ioReactor, pushConsumerRegistry, threadFactory);
@@ -112,6 +115,7 @@ abstract class InternalAbstractHttpAsyncClient extends AbstractHttpAsyncClientBa
         this.authSchemeRegistry = authSchemeRegistry;
         this.cookieStore = cookieStore;
         this.credentialsProvider = credentialsProvider;
+        this.contextAdaptor = contextAdaptor;
         this.defaultConfig = defaultConfig;
         this.closeables = closeables != null ? new ConcurrentLinkedQueue<>(closeables) : null;
         this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(SCHEDULER_THREAD_FACTORY);
@@ -200,7 +204,7 @@ abstract class InternalAbstractHttpAsyncClient extends AbstractHttpAsyncClientBa
             if (!isRunning()) {
                 throw new CancellationException("Request execution cancelled");
             }
-            final HttpClientContext clientContext = HttpClientContext.adapt(context);
+            final HttpClientContext clientContext = contextAdaptor.apply(context);
             requestProducer.sendRequest((request, entityDetails, c) -> {
 
                 RequestConfig requestConfig = null;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java
index 72b372cd7..709a4b642 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java
@@ -84,7 +84,8 @@ public final class InternalH2AsyncClient extends InternalAbstractHttpAsyncClient
             final RequestConfig defaultConfig,
             final List<Closeable> closeables) {
         super(ioReactor, pushConsumerRegistry, threadFactory, execChain,
-                cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, defaultConfig, closeables);
+                cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, HttpClientContext::castOrCreate,
+                defaultConfig, closeables);
         this.connPool = connPool;
         this.routePlanner = routePlanner;
     }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
index 364589546..e02be0605 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
@@ -29,6 +29,7 @@ package org.apache.hc.client5.http.impl.async;
 import java.io.Closeable;
 import java.util.List;
 import java.util.concurrent.ThreadFactory;
+import java.util.function.Function;
 
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.async.AsyncExecRuntime;
@@ -50,6 +51,7 @@ import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.config.Lookup;
 import org.apache.hc.core5.http.nio.AsyncPushConsumer;
 import org.apache.hc.core5.http.nio.HandlerFactory;
+import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -86,10 +88,12 @@ public final class InternalHttpAsyncClient extends InternalAbstractHttpAsyncClie
             final Lookup<AuthSchemeFactory> authSchemeRegistry,
             final CookieStore cookieStore,
             final CredentialsProvider credentialsProvider,
+            final Function<HttpContext, HttpClientContext> contextAdaptor,
             final RequestConfig defaultConfig,
             final List<Closeable> closeables) {
         super(ioReactor, pushConsumerRegistry, threadFactory, execChain,
-                cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, defaultConfig, closeables);
+                cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, contextAdaptor,
+                defaultConfig, closeables);
         this.manager = manager;
         this.routePlanner = routePlanner;
         this.tlsConfig = tlsConfig;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalH2AsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalH2AsyncClient.java
index cd430d103..da5a47410 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalH2AsyncClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalH2AsyncClient.java
@@ -129,7 +129,7 @@ public final class MinimalH2AsyncClient extends AbstractMinimalHttpAsyncClientBa
             if (!isRunning()) {
                 throw new CancellationException("Request execution cancelled");
             }
-            final HttpClientContext clientContext = HttpClientContext.adapt(context);
+            final HttpClientContext clientContext = HttpClientContext.castOrCreate(context);
             exchangeHandler.produceRequest((request, entityDetails, context1) -> {
                 RequestConfig requestConfig = null;
                 if (request instanceof Configurable) {
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalHttpAsyncClient.java
index 0c71ca1c0..0ae21c666 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalHttpAsyncClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalHttpAsyncClient.java
@@ -218,7 +218,7 @@ public final class MinimalHttpAsyncClient extends AbstractMinimalHttpAsyncClient
             future.failed(new CancellationException("Connection lease cancelled"));
             return future;
         }
-        final HttpClientContext clientContext = HttpClientContext.adapt(context);
+        final HttpClientContext clientContext = HttpClientContext.castOrCreate(context);
         final RequestConfig requestConfig = clientContext.getRequestConfigOrDefault();
         final Timeout connectionRequestTimeout = requestConfig.getConnectionRequestTimeout();
         @SuppressWarnings("deprecation")
@@ -259,7 +259,7 @@ public final class MinimalHttpAsyncClient extends AbstractMinimalHttpAsyncClient
             if (!isRunning()) {
                 throw new CancellationException("Request execution cancelled");
             }
-            final HttpClientContext clientContext = HttpClientContext.adapt(context);
+            final HttpClientContext clientContext = HttpClientContext.castOrCreate(context);
             exchangeHandler.produceRequest((request, entityDetails, context1) -> {
                 RequestConfig requestConfig = null;
                 if (request instanceof Configurable) {
@@ -455,7 +455,7 @@ public final class MinimalHttpAsyncClient extends AbstractMinimalHttpAsyncClient
                 final HttpContext context) {
             Asserts.check(!released.get(), "Endpoint has already been released");
 
-            final HttpClientContext clientContext = HttpClientContext.adapt(context);
+            final HttpClientContext clientContext = HttpClientContext.castOrCreate(context);
             final String exchangeId = ExecSupport.getNextExchangeId();
             clientContext.setExchangeId(exchangeId);
             if (LOG.isDebugEnabled()) {
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
index 5c7ec95d1..633d37ef2 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
@@ -37,6 +37,7 @@ import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 
 import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
@@ -75,6 +76,7 @@ import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
 import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
 import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
 import org.apache.hc.client5.http.io.HttpClientConnectionManager;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.protocol.RedirectStrategy;
 import org.apache.hc.client5.http.protocol.RequestAddCookies;
 import org.apache.hc.client5.http.protocol.RequestClientConnControl;
@@ -96,6 +98,7 @@ import org.apache.hc.core5.http.config.Registry;
 import org.apache.hc.core5.http.config.RegistryBuilder;
 import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
 import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
+import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http.protocol.HttpProcessor;
 import org.apache.hc.core5.http.protocol.HttpProcessorBuilder;
 import org.apache.hc.core5.http.protocol.RequestContent;
@@ -742,6 +745,11 @@ public class HttpClientBuilder {
         closeables.add(closeable);
     }
 
+    @Internal
+    protected Function<HttpContext, HttpClientContext> contextAdaptor() {
+        return HttpClientContext::castOrCreate;
+    }
+
     public CloseableHttpClient build() {
         // Create main request executor
         // We copy the instance fields to avoid changing them, and rename to avoid accidental use of the wrong version
@@ -1024,6 +1032,7 @@ public class HttpClientBuilder {
                 authSchemeRegistryCopy,
                 defaultCookieStore,
                 defaultCredentialsProvider,
+                contextAdaptor(),
                 defaultRequestConfig != null ? defaultRequestConfig : RequestConfig.DEFAULT,
                 closeablesCopy);
     }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java
index 15bd5d089..4215d4285 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java
@@ -31,6 +31,7 @@ import java.io.Closeable;
 import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.function.Function;
 
 import org.apache.hc.client5.http.ClientProtocolException;
 import org.apache.hc.client5.http.HttpRoute;
@@ -89,6 +90,7 @@ class InternalHttpClient extends CloseableHttpClient implements Configurable {
     private final Lookup<AuthSchemeFactory> authSchemeRegistry;
     private final CookieStore cookieStore;
     private final CredentialsProvider credentialsProvider;
+    private final Function<HttpContext, HttpClientContext> contextAdaptor;
     private final RequestConfig defaultConfig;
     private final ConcurrentLinkedQueue<Closeable> closeables;
 
@@ -101,6 +103,7 @@ class InternalHttpClient extends CloseableHttpClient implements Configurable {
             final Lookup<AuthSchemeFactory> authSchemeRegistry,
             final CookieStore cookieStore,
             final CredentialsProvider credentialsProvider,
+            final Function<HttpContext, HttpClientContext> contextAdaptor,
             final RequestConfig defaultConfig,
             final List<Closeable> closeables) {
         super();
@@ -112,6 +115,7 @@ class InternalHttpClient extends CloseableHttpClient implements Configurable {
         this.authSchemeRegistry = authSchemeRegistry;
         this.cookieStore = cookieStore;
         this.credentialsProvider = credentialsProvider;
+        this.contextAdaptor = contextAdaptor;
         this.defaultConfig = defaultConfig;
         this.closeables = closeables != null ?  new ConcurrentLinkedQueue<>(closeables) : null;
     }
@@ -145,7 +149,7 @@ class InternalHttpClient extends CloseableHttpClient implements Configurable {
             final HttpContext context) throws IOException {
         Args.notNull(request, "HTTP request");
         try {
-            final HttpClientContext localcontext = HttpClientContext.adapt(context);
+            final HttpClientContext localcontext = contextAdaptor.apply(context);
             RequestConfig config = null;
             if (request instanceof Configurable) {
                 config = ((Configurable) request).getConfig();
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MinimalHttpClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MinimalHttpClient.java
index 9dd1dab55..cceb61af3 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MinimalHttpClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MinimalHttpClient.java
@@ -118,7 +118,7 @@ public class MinimalHttpClient extends CloseableHttpClient {
         if (request.getAuthority() == null) {
             request.setAuthority(new URIAuthority(target));
         }
-        final HttpClientContext clientContext = HttpClientContext.adapt(context);
+        final HttpClientContext clientContext = HttpClientContext.castOrCreate(context);
         RequestConfig config = null;
         if (request instanceof Configurable) {
             config = ((Configurable) request).getConfig();
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/HttpClientContext.java b/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/HttpClientContext.java
index a8c1e7547..c0c2734ab 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/HttpClientContext.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/HttpClientContext.java
@@ -55,9 +55,12 @@ import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http.protocol.HttpCoreContext;
 
 /**
- * Adaptor class that provides convenience type safe setters and getters
- * for common {@link HttpContext} attributes used in the course
- * of HTTP request execution.
+ * Client execution {@link HttpContext}. This class can be re-used for
+ * multiple consecutive logically related request executions that represent
+ * a single communication session. This context may not be used concurrently.
+ * <p>
+ * IMPORTANT: This class is NOT thread-safe and MUST NOT be used concurrently by
+ * multiple message exchanges.
  *
  * @since 4.3
  */
@@ -141,18 +144,23 @@ public class HttpClientContext extends HttpCoreContext {
     @Deprecated
     public static final String EXCHANGE_ID = "http.exchange-id";
 
-    @SuppressWarnings("deprecation")
+    /**
+     * @deprecated Use {@link #castOrCreate(HttpContext)}.
+     */
+    @Deprecated
     public static HttpClientContext adapt(final HttpContext context) {
+        if (context == null) {
+            return new HttpClientContext();
+        }
         if (context instanceof HttpClientContext) {
             return (HttpClientContext) context;
         }
-        return new HttpClientContext.Delegate(HttpCoreContext.adapt(context));
+        return new HttpClientContext(context);
     }
 
     /**
-     * Casts the given generic {@link HttpContext} as {@link HttpClientContext}
-     * or creates a new {@link HttpClientContext} with the given {@link HttpContext}
-     * as a parent.
+     * Casts the given generic {@link HttpContext} as {@link HttpClientContext} or
+     * throws an {@link IllegalStateException} if the given context is not suitable.
      *
      * @since 5.4
      */
@@ -163,11 +171,20 @@ public class HttpClientContext extends HttpCoreContext {
         if (context instanceof HttpClientContext) {
             return (HttpClientContext) context;
         } else {
-            throw new IllegalStateException("Unexpected context type: " + context.getClass().getSimpleName() +
-                    "; required context type: " + HttpClientContext.class.getSimpleName());
+            return new Delegate(context);
         }
     }
 
+    /**
+     * Casts the given generic {@link HttpContext} as {@link HttpClientContext} or
+     * creates new {@link HttpClientContext} if the given context is null..
+     *
+     * @since 5.4
+     */
+    public static HttpClientContext castOrCreate(final HttpContext context) {
+        return context != null ? cast(context) : create();
+    }
+
     public static HttpClientContext create() {
         return new HttpClientContext();
     }
@@ -198,8 +215,7 @@ public class HttpClientContext extends HttpCoreContext {
     /**
      * Represents current route used to execute message exchanges.
      * <p>
-     * This context attribute is expected to be populated by the protocol handler
-     * in the course of request execution.
+     * This context attribute is expected to be populated by the protocol handler.
      */
     public RouteInfo getHttpRoute() {
         return route;
@@ -214,10 +230,9 @@ public class HttpClientContext extends HttpCoreContext {
     }
 
     /**
-     * Represents a collection of all redirects executed in the course of request execution.
+     * Represents a collection of all redirects executed in the context of request execution.
      * <p>
-     * This context attribute is expected to be populated by the protocol handler
-     * in the course of request execution.
+     * This context attribute is expected to be populated by the protocol handler.
      */
     public RedirectLocations getRedirectLocations() {
         if (this.redirectLocations == null) {
@@ -234,6 +249,11 @@ public class HttpClientContext extends HttpCoreContext {
         this.redirectLocations = redirectLocations;
     }
 
+    /**
+     * Represents a {@link CookieStore} used in the context of the request execution.
+     * <p>
+     * This context attribute can be set by the caller.
+     */
     public CookieStore getCookieStore() {
         return cookieStore;
     }
@@ -242,6 +262,11 @@ public class HttpClientContext extends HttpCoreContext {
         this.cookieStore = cookieStore;
     }
 
+    /**
+     * Represents a {@link CookieSpec} chosen in the context of request execution.
+     * <p>
+     * This context attribute is expected to be populated by the protocol handler.
+     */
     public CookieSpec getCookieSpec() {
         return cookieSpec;
     }
@@ -254,6 +279,11 @@ public class HttpClientContext extends HttpCoreContext {
         this.cookieSpec = cookieSpec;
     }
 
+    /**
+     * Represents a {@link CookieOrigin} produced in the context of request execution.
+     * <p>
+     * This context attribute is expected to be populated by the protocol handler.
+     */
     public CookieOrigin getCookieOrigin() {
         return cookieOrigin;
     }
@@ -266,6 +296,11 @@ public class HttpClientContext extends HttpCoreContext {
         this.cookieOrigin = cookieOrigin;
     }
 
+    /**
+     * Represents a {@link CookieSpecFactory} registry used in the context of the request execution.
+     * <p>
+     * This context attribute can be set by the caller.
+     */
     public Lookup<CookieSpecFactory> getCookieSpecRegistry() {
         return cookieSpecFactoryLookup;
     }
@@ -274,6 +309,11 @@ public class HttpClientContext extends HttpCoreContext {
         this.cookieSpecFactoryLookup = lookup;
     }
 
+    /**
+     * Represents a {@link AuthSchemeFactory} registry used in the context of the request execution.
+     * <p>
+     * This context attribute can be set by the caller.
+     */
     public Lookup<AuthSchemeFactory> getAuthSchemeRegistry() {
         return authSchemeFactoryLookup;
     }
@@ -282,6 +322,11 @@ public class HttpClientContext extends HttpCoreContext {
         this.authSchemeFactoryLookup = lookup;
     }
 
+    /**
+     * Represents a {@link CredentialsProvider} registry used in the context of the request execution.
+     * <p>
+     * This context attribute can be set by the caller.
+     */
     public CredentialsProvider getCredentialsProvider() {
         return credentialsProvider;
     }
@@ -290,6 +335,11 @@ public class HttpClientContext extends HttpCoreContext {
         this.credentialsProvider = credentialsProvider;
     }
 
+    /**
+     * Represents a {@link AuthCache} used in the context of the request execution.
+     * <p>
+     * This context attribute can be set by the caller.
+     */
     public AuthCache getAuthCache() {
         return authCache;
     }
@@ -299,6 +349,11 @@ public class HttpClientContext extends HttpCoreContext {
     }
 
     /**
+     * Represents a map of {@link AuthExchange}s performed in the context of the request
+     * execution.
+     * <p>
+     * This context attribute is expected to be populated by the protocol handler.
+     *
      * @since 5.0
      */
     public Map<HttpHost, AuthExchange> getAuthExchanges() {
@@ -340,6 +395,12 @@ public class HttpClientContext extends HttpCoreContext {
         return (T) getUserToken();
     }
 
+    /**
+     * Represents an arbitrary user token that identifies the user in the context
+     * of the request execution.
+     * <p>
+     * This context attribute can be set by the caller.
+     */
     public Object getUserToken() {
         return userToken;
     }
@@ -348,13 +409,18 @@ public class HttpClientContext extends HttpCoreContext {
         this.userToken = userToken;
     }
 
+    /**
+     * Represents an {@link RequestConfig used} in the context of the request execution.
+     * <p>
+     * This context attribute can be set by the caller.
+     */
     public RequestConfig getRequestConfig() {
         return requestConfig;
     }
 
     /**
      * Returns {@link RequestConfig} set in the context or {@link RequestConfig#DEFAULT}
-     * if not present in the context.
+     * if not explicitly set in the context.
      *
      * @since 5.4
      */
@@ -368,6 +434,10 @@ public class HttpClientContext extends HttpCoreContext {
     }
 
     /**
+     * Represents an identifier generated for the current message exchange executed
+     * in the given context.
+     * <p>
+     * This context attribute is expected to be populated by the protocol handler.
      * @since 5.1
      */
     public String getExchangeId() {
@@ -381,88 +451,237 @@ public class HttpClientContext extends HttpCoreContext {
         this.exchangeId = exchangeId;
     }
 
+    /**
+     * Internal adaptor class that delegates all its method calls to a plain {@link HttpContext}.
+     * To be removed in the future.
+     */
+    @SuppressWarnings("deprecation")
+    @Internal
     static class Delegate extends HttpClientContext {
 
-        final private HttpCoreContext parent;
+        private final HttpContext httpContext;
+
+        Delegate(final HttpContext httpContext) {
+            super(null);
+            this.httpContext = httpContext;
+        }
 
-        Delegate(final HttpCoreContext parent) {
-            super(parent);
-            this.parent = parent;
+        <T> T getAttr(final String id, final Class<T> clazz) {
+            final Object obj = httpContext.getAttribute(id);
+            if (obj == null) {
+                return null;
+            }
+            return clazz.cast(obj);
         }
 
         @Override
-        public ProtocolVersion getProtocolVersion() {
-            return parent.getProtocolVersion();
+        public RouteInfo getHttpRoute() {
+            return getAttr(HTTP_ROUTE, RouteInfo.class);
         }
 
         @Override
-        public void setProtocolVersion(final ProtocolVersion version) {
-            parent.setProtocolVersion(version);
+        public void setRoute(final HttpRoute route) {
+            httpContext.setAttribute(HTTP_ROUTE, route);
         }
 
         @Override
-        public Object getAttribute(final String id) {
-            return parent.getAttribute(id);
+        public RedirectLocations getRedirectLocations() {
+            RedirectLocations redirectLocations = getAttr(REDIRECT_LOCATIONS, RedirectLocations.class);
+            if (redirectLocations == null) {
+                redirectLocations = new RedirectLocations();
+                httpContext.setAttribute(REDIRECT_LOCATIONS, redirectLocations);
+            }
+            return redirectLocations;
         }
 
         @Override
-        public Object setAttribute(final String id, final Object obj) {
-            return parent.setAttribute(id, obj);
+        public void setRedirectLocations(final RedirectLocations redirectLocations) {
+            httpContext.setAttribute(REDIRECT_LOCATIONS, redirectLocations);
         }
 
         @Override
-        public Object removeAttribute(final String id) {
-            return parent.removeAttribute(id);
+        public CookieStore getCookieStore() {
+            return getAttr(COOKIE_STORE, CookieStore.class);
         }
 
         @Override
-        public <T> T getAttribute(final String id, final Class<T> clazz) {
-            return parent.getAttribute(id, clazz);
+        public void setCookieStore(final CookieStore cookieStore) {
+            httpContext.setAttribute(COOKIE_STORE, cookieStore);
+        }
+
+        @Override
+        public CookieSpec getCookieSpec() {
+            return getAttr(COOKIE_SPEC, CookieSpec.class);
+        }
+
+        @Override
+        public void setCookieSpec(final CookieSpec cookieSpec) {
+            httpContext.setAttribute(COOKIE_SPEC, cookieSpec);
+        }
+
+        @Override
+        public CookieOrigin getCookieOrigin() {
+            return getAttr(COOKIE_ORIGIN, CookieOrigin.class);
+        }
+
+        @Override
+        public void setCookieOrigin(final CookieOrigin cookieOrigin) {
+            httpContext.setAttribute(COOKIE_ORIGIN, cookieOrigin);
+        }
+
+        @Override
+        public Lookup<CookieSpecFactory> getCookieSpecRegistry() {
+            return getAttr(COOKIESPEC_REGISTRY, Lookup.class);
+        }
+
+        @Override
+        public void setCookieSpecRegistry(final Lookup<CookieSpecFactory> lookup) {
+            httpContext.setAttribute(COOKIESPEC_REGISTRY, lookup);
+        }
+
+        @Override
+        public Lookup<AuthSchemeFactory> getAuthSchemeRegistry() {
+            return getAttr(AUTHSCHEME_REGISTRY, Lookup.class);
+        }
+
+        @Override
+        public void setAuthSchemeRegistry(final Lookup<AuthSchemeFactory> lookup) {
+            httpContext.setAttribute(AUTHSCHEME_REGISTRY, lookup);
+        }
+
+        @Override
+        public CredentialsProvider getCredentialsProvider() {
+            return getAttr(CREDS_PROVIDER, CredentialsProvider.class);
+        }
+
+        @Override
+        public void setCredentialsProvider(final CredentialsProvider credentialsProvider) {
+            httpContext.setAttribute(CREDS_PROVIDER, credentialsProvider);
+        }
+
+        @Override
+        public AuthCache getAuthCache() {
+            return getAttr(AUTH_CACHE, AuthCache.class);
+        }
+
+        @Override
+        public void setAuthCache(final AuthCache authCache) {
+            httpContext.setAttribute(AUTH_CACHE, authCache);
+        }
+
+        @Override
+        public Map<HttpHost, AuthExchange> getAuthExchanges() {
+            Map<HttpHost, AuthExchange> map = getAttr(AUTH_EXCHANGE_MAP, Map.class);
+            if (map == null) {
+                map = new HashMap<>();
+                httpContext.setAttribute(AUTH_EXCHANGE_MAP, map);
+            }
+            return map;
+        }
+
+        @Override
+        public Object getUserToken() {
+            return httpContext.getAttribute(USER_TOKEN);
+        }
+
+        @Override
+        public void setUserToken(final Object userToken) {
+            httpContext.setAttribute(USER_TOKEN, userToken);
+        }
+
+        @Override
+        public RequestConfig getRequestConfig() {
+            return getAttr(REQUEST_CONFIG, RequestConfig.class);
+        }
+
+        @Override
+        public void setRequestConfig(final RequestConfig requestConfig) {
+            httpContext.setAttribute(REQUEST_CONFIG, requestConfig);
+        }
+
+        @Override
+        public String getExchangeId() {
+            return getAttr(EXCHANGE_ID, String.class);
+        }
+
+        @Override
+        public void setExchangeId(final String exchangeId) {
+            httpContext.setAttribute(EXCHANGE_ID, exchangeId);
         }
 
         @Override
         public HttpRequest getRequest() {
-            return parent.getRequest();
+            return getAttr(HttpCoreContext.HTTP_REQUEST, HttpRequest.class);
         }
 
         @Override
         public void setRequest(final HttpRequest request) {
-            parent.setRequest(request);
+            httpContext.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
         }
 
         @Override
         public HttpResponse getResponse() {
-            return parent.getResponse();
+            return getAttr(HttpCoreContext.HTTP_RESPONSE, HttpResponse.class);
         }
 
         @Override
         public void setResponse(final HttpResponse response) {
-            parent.setResponse(response);
+            httpContext.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
         }
 
         @Override
         public EndpointDetails getEndpointDetails() {
-            return parent.getEndpointDetails();
+            return getAttr(HttpCoreContext.CONNECTION_ENDPOINT, EndpointDetails.class);
         }
 
         @Override
         public void setEndpointDetails(final EndpointDetails endpointDetails) {
-            parent.setEndpointDetails(endpointDetails);
+            httpContext.setAttribute(CONNECTION_ENDPOINT, endpointDetails);
         }
 
         @Override
         public SSLSession getSSLSession() {
-            return parent.getSSLSession();
+            return getAttr(HttpCoreContext.SSL_SESSION, SSLSession.class);
         }
 
         @Override
         public void setSSLSession(final SSLSession sslSession) {
-            parent.setSSLSession(sslSession);
+            httpContext.setAttribute(HttpCoreContext.SSL_SESSION, sslSession);
+        }
+
+        @Override
+        public ProtocolVersion getProtocolVersion() {
+            return httpContext.getProtocolVersion();
+        }
+
+        @Override
+        public void setProtocolVersion(final ProtocolVersion version) {
+            httpContext.setProtocolVersion(version);
+        }
+
+        @Override
+        public Object getAttribute(final String id) {
+            return httpContext.getAttribute(id);
+        }
+
+        @Override
+        public Object setAttribute(final String id, final Object obj) {
+            return httpContext.setAttribute(id, obj);
+        }
+
+        @Override
+        public Object removeAttribute(final String id) {
+            return httpContext.removeAttribute(id);
+        }
+
+        @Override
+        public <T> T getAttribute(final String id, final Class<T> clazz) {
+            return getAttr(id, clazz);
         }
 
         @Override
         public String toString() {
-            return parent.toString();
+            return httpContext.toString();
         }
 
     }
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientMultiThreadedExecution.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientMultiThreadedExecution.java
index 4fe237580..411f7d959 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientMultiThreadedExecution.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientMultiThreadedExecution.java
@@ -90,7 +90,7 @@ public class ClientMultiThreadedExecution {
 
         public GetThread(final CloseableHttpClient httpClient, final HttpGet httpget, final int id) {
             this.httpClient = httpClient;
-            this.context = new HttpClientContext();
+            this.context = HttpClientContext.create();
             this.httpget = httpget;
             this.id = id;
         }
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientRemoteEndpointDetails.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientRemoteEndpointDetails.java
index 07e5a776c..15e5cb96f 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientRemoteEndpointDetails.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientRemoteEndpointDetails.java
@@ -44,7 +44,7 @@ public class ClientRemoteEndpointDetails {
     public static void main(final String[] args) throws Exception {
         try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
             // Create local HTTP context
-            final HttpClientContext localContext = new HttpClientContext();
+            final HttpClientContext localContext = HttpClientContext.create();
 
             final HttpGet httpget = new HttpGet("http://httpbin.org/get");
             System.out.println("Executing request " + httpget.getMethod() + " " + httpget.getUri());
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultConnKeepAliveStrategy.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultConnKeepAliveStrategy.java
index e6ba86e44..4bc92aa2d 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultConnKeepAliveStrategy.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultConnKeepAliveStrategy.java
@@ -43,7 +43,7 @@ public class TestDefaultConnKeepAliveStrategy {
 
     @Test
     public void testIllegalResponseArg() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy();
         Assertions.assertThrows(NullPointerException.class, () ->
                 keepAliveStrat.getKeepAliveDuration(null, context));
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestHttpAuthenticator.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestHttpAuthenticator.java
index 7773d9887..fcf9e0249 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestHttpAuthenticator.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestHttpAuthenticator.java
@@ -86,7 +86,7 @@ public class TestHttpAuthenticator {
         this.authScheme = Mockito.mock(CacheableAuthState.class, Mockito.withSettings()
                 .defaultAnswer(Answers.CALLS_REAL_METHODS));
         Mockito.when(this.authScheme.isChallengeComplete()).thenReturn(Boolean.TRUE);
-        this.context = new HttpClientContext();
+        this.context = HttpClientContext.create();
         this.defaultHost = new HttpHost("localhost", 80);
         this.credentialsProvider = Mockito.mock(CredentialsProvider.class);
         this.context.setCredentialsProvider(this.credentialsProvider);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java
index f0afa7900..c2cbc4286 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java
@@ -94,7 +94,7 @@ public class TestSystemDefaultCredentialsProvider {
 
         final URL httpRequestUrl = new URL(TARGET_SCHEME1, TARGET_HOST1, TARGET_PORT1, "/");
         final AuthScope authScope = new AuthScope(PROXY_PROTOCOL1, PROXY_HOST1, PROXY_PORT1, PROMPT1, StandardAuthScheme.BASIC);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         context.setRequest(new HttpGet(httpRequestUrl.toURI()));
 
         final Credentials receivedCredentials =
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestConnectExec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestConnectExec.java
index e9b9db15d..d9118ee0f 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestConnectExec.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestConnectExec.java
@@ -94,7 +94,7 @@ public class TestConnectExec {
     @Test
     public void testExecAcquireConnection() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         try (final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK")) {
             response.setEntity(EntityBuilder.create()
@@ -113,7 +113,7 @@ public class TestConnectExec {
     @Test
     public void testEstablishDirectRoute() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
 
         final ConnectionState connectionState = new ConnectionState();
@@ -133,7 +133,7 @@ public class TestConnectExec {
     @Test
     public void testEstablishRouteDirectProxy() throws Exception {
         final HttpRoute route = new HttpRoute(target, null, proxy, false);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
 
         final ConnectionState connectionState = new ConnectionState();
@@ -153,7 +153,7 @@ public class TestConnectExec {
     @Test
     public void testEstablishRouteViaProxyTunnel() throws Exception {
         final HttpRoute route = new HttpRoute(target, null, proxy, true);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
 
@@ -184,7 +184,7 @@ public class TestConnectExec {
     @Test
     public void testEstablishRouteViaProxyTunnelUnexpectedResponse() throws Exception {
         final HttpRoute route = new HttpRoute(target, null, proxy, true);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         final ClassicHttpResponse response = new BasicClassicHttpResponse(101, "Lost");
 
@@ -204,7 +204,7 @@ public class TestConnectExec {
     @Test
     public void testEstablishRouteViaProxyTunnelFailure() throws Exception {
         final HttpRoute route = new HttpRoute(target, null, proxy, true);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         final ClassicHttpResponse response = new BasicClassicHttpResponse(500, "Boom");
         response.setEntity(new StringEntity("Ka-boom"));
@@ -228,7 +228,7 @@ public class TestConnectExec {
     @Test
     public void testEstablishRouteViaProxyTunnelRetryOnAuthChallengePersistentConnection() throws Exception {
         final HttpRoute route = new HttpRoute(target, null, proxy, true);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(407, "Huh?");
         response1.setHeader(HttpHeaders.PROXY_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
@@ -269,7 +269,7 @@ public class TestConnectExec {
     @Test
     public void testEstablishRouteViaProxyTunnelRetryOnAuthChallengeNonPersistentConnection() throws Exception {
         final HttpRoute route = new HttpRoute(target, null, proxy, true);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(407, "Huh?");
         response1.setHeader(HttpHeaders.PROXY_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
@@ -311,7 +311,7 @@ public class TestConnectExec {
         final HttpHost proxy2 = new HttpHost("that", 8888);
         final HttpRoute route = new HttpRoute(target, null, new HttpHost[] {proxy1, proxy2},
                 true, RouteInfo.TunnelType.TUNNELLED, RouteInfo.LayerType.LAYERED);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
 
         final ConnectionState connectionState = new ConnectionState();
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestInternalHttpClient.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestInternalHttpClient.java
index 73b0f374e..501a89a3b 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestInternalHttpClient.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestInternalHttpClient.java
@@ -89,7 +89,7 @@ public class TestInternalHttpClient {
         MockitoAnnotations.openMocks(this);
         client = new InternalHttpClient(connManager, requestExecutor, new ExecChainElement(execChain, null), routePlanner,
                 cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider,
-                defaultConfig, Arrays.asList(closeable1, closeable2));
+                HttpClientContext::castOrCreate, defaultConfig, Arrays.asList(closeable1, closeable2));
 
     }
 
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestMainClientExec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestMainClientExec.java
index 1e7c3a65d..078b3a4b0 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestMainClientExec.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestMainClientExec.java
@@ -114,7 +114,7 @@ public class TestMainClientExec {
     @Test
     public void testExecRequestNonPersistentConnection() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
         response.setEntity(EntityBuilder.create()
@@ -145,7 +145,7 @@ public class TestMainClientExec {
     @Test
     public void testExecRequestNonPersistentConnectionNoResponseEntity() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
         response.setEntity(null);
@@ -174,7 +174,7 @@ public class TestMainClientExec {
     @Test
     public void testExecRequestPersistentConnection() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
         // The entity is streaming
@@ -210,7 +210,7 @@ public class TestMainClientExec {
     @Test
     public void testExecRequestPersistentConnectionNoResponseEntity() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
 
@@ -240,7 +240,7 @@ public class TestMainClientExec {
     @Test
     public void testExecRequestConnectionRelease() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
         final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
         // The entity is streaming
@@ -275,7 +275,7 @@ public class TestMainClientExec {
     @Test
     public void testExecConnectionShutDown() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
 
         Mockito.when(execRuntime.execute(
@@ -293,7 +293,7 @@ public class TestMainClientExec {
     @Test
     public void testExecRuntimeException() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
 
         Mockito.when(execRuntime.execute(
@@ -311,7 +311,7 @@ public class TestMainClientExec {
     @Test
     public void testExecHttpException() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
 
         Mockito.when(execRuntime.execute(
@@ -329,7 +329,7 @@ public class TestMainClientExec {
     @Test
     public void testExecIOException() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://bar/test");
 
         Mockito.when(execRuntime.execute(
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestProtocolExec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestProtocolExec.java
index 71350020f..1cc8b8d70 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestProtocolExec.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestProtocolExec.java
@@ -151,7 +151,7 @@ public class TestProtocolExec {
     @Test
     public void testExecRequestRetryOnAuthChallenge() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new HttpGet("http://foo/test");
         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
         response1.setHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
@@ -204,7 +204,7 @@ public class TestProtocolExec {
                 .setStream(inStream2)
                 .build());
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
 
         final AuthExchange authExchange = new AuthExchange();
         authExchange.setState(AuthExchange.State.SUCCESS);
@@ -245,7 +245,7 @@ public class TestProtocolExec {
     @Test
     public void testExecEntityEnclosingRequest() throws Exception {
         final HttpRoute route = new HttpRoute(target);
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpPost request = new HttpPost("http://foo/test");
         final InputStream inStream0 = new ByteArrayInputStream(new byte[] {1, 2, 3});
         request.setEntity(EntityBuilder.create()
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestHttpClientConnectionOperator.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestHttpClientConnectionOperator.java
index aba080dfc..64aff2612 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestHttpClientConnectionOperator.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestHttpClientConnectionOperator.java
@@ -83,7 +83,7 @@ public class TestHttpClientConnectionOperator {
 
     @Test
     public void testConnect() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpHost host = new HttpHost("somehost");
         final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
         final InetAddress ip1 = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
@@ -116,7 +116,7 @@ public class TestHttpClientConnectionOperator {
 
     @Test
     public void testConnectWithTLSUpgrade() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpHost host = new HttpHost("https", "somehost");
         final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
         final InetAddress ip1 = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
@@ -152,7 +152,7 @@ public class TestHttpClientConnectionOperator {
 
     @Test
     public void testConnectTimeout() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpHost host = new HttpHost("somehost");
         final InetAddress ip1 = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
         final InetAddress ip2 = InetAddress.getByAddress(new byte[] {10, 0, 0, 2});
@@ -169,7 +169,7 @@ public class TestHttpClientConnectionOperator {
 
     @Test
     public void testConnectFailure() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpHost host = new HttpHost("somehost");
         final InetAddress ip1 = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
         final InetAddress ip2 = InetAddress.getByAddress(new byte[] {10, 0, 0, 2});
@@ -186,7 +186,7 @@ public class TestHttpClientConnectionOperator {
 
     @Test
     public void testConnectFailover() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpHost host = new HttpHost("somehost");
         final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
         final InetAddress ip1 = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
@@ -213,7 +213,7 @@ public class TestHttpClientConnectionOperator {
 
     @Test
     public void testConnectExplicitAddress() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
         final InetAddress ip = InetAddress.getByAddress(new byte[] {127, 0, 0, 23});
         final HttpHost host = new HttpHost(ip);
@@ -235,7 +235,7 @@ public class TestHttpClientConnectionOperator {
 
     @Test
     public void testUpgrade() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpHost host = new HttpHost("https", "somehost", -1);
 
         Mockito.when(conn.isOpen()).thenReturn(true);
@@ -257,7 +257,7 @@ public class TestHttpClientConnectionOperator {
 
     @Test
     public void testUpgradeUpsupportedScheme() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpHost host = new HttpHost("httpsssss", "somehost", -1);
 
         Mockito.when(conn.isOpen()).thenReturn(true);
@@ -269,7 +269,7 @@ public class TestHttpClientConnectionOperator {
 
     @Test
     public void testUpgradeNonLayeringScheme() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpHost host = new HttpHost("http", "somehost", -1);
 
         Mockito.when(conn.isOpen()).thenReturn(true);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestDefaultProxyRoutePlanner.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestDefaultProxyRoutePlanner.java
index 66feb9f36..7fd414f20 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestDefaultProxyRoutePlanner.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestDefaultProxyRoutePlanner.java
@@ -59,7 +59,7 @@ public class TestDefaultProxyRoutePlanner {
     public void testDefaultProxyDirect() throws Exception {
         final HttpHost target = new HttpHost("http", "somehost", 80);
 
-        final HttpContext context = new HttpClientContext();
+        final HttpContext context = HttpClientContext.create();
         final HttpRoute route = routePlanner.determineRoute(target, context);
 
         Assertions.assertEquals(target, route.getTargetHost());
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestDefaultRoutePlanner.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestDefaultRoutePlanner.java
index 6ec687f96..eda0397ac 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestDefaultRoutePlanner.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestDefaultRoutePlanner.java
@@ -60,7 +60,7 @@ public class TestDefaultRoutePlanner {
     public void testDirect() throws Exception {
         final HttpHost target = new HttpHost("http", "somehost", 80);
 
-        final HttpContext context = new HttpClientContext();
+        final HttpContext context = HttpClientContext.create();
         final HttpRoute route = routePlanner.determineRoute(target, context);
 
         Assertions.assertEquals(target, route.getTargetHost());
@@ -74,7 +74,7 @@ public class TestDefaultRoutePlanner {
         final HttpHost target = new HttpHost("https", "somehost", -1);
         Mockito.when(schemePortResolver.resolve(target)).thenReturn(443);
 
-        final HttpContext context = new HttpClientContext();
+        final HttpContext context = HttpClientContext.create();
         final HttpRoute route = routePlanner.determineRoute(target, context);
 
         Assertions.assertEquals(new HttpHost("https", "somehost", 443), route.getTargetHost());
@@ -101,7 +101,7 @@ public class TestDefaultRoutePlanner {
 
     @Test
     public void testNullTarget() throws Exception {
-        final HttpContext context = new HttpClientContext();
+        final HttpContext context = HttpClientContext.create();
         Assertions.assertThrows(ProtocolException.class, () ->
                 routePlanner.determineRoute(null, context));
     }
@@ -112,7 +112,7 @@ public class TestDefaultRoutePlanner {
         final URIAuthority virtualHost = new URIAuthority("someotherhost", 443);
         final HttpRequest request = new BasicHttpRequest("https", "GET", virtualHost, "/");
 
-        final HttpContext context = new HttpClientContext();
+        final HttpContext context = HttpClientContext.create();
         final HttpRoute route = routePlanner.determineRoute(target, request, context);
 
         Assertions.assertEquals(target, route.getTargetHost());
@@ -128,7 +128,7 @@ public class TestDefaultRoutePlanner {
         final URIAuthority virtualHost = new URIAuthority("someotherhost", 80);
         final HttpRequest request = new BasicHttpRequest("http", "GET", virtualHost, "/");
 
-        final HttpContext context = new HttpClientContext();
+        final HttpContext context = HttpClientContext.create();
         final HttpRoute route = routePlanner.determineRoute(target, request, context);
 
         Assertions.assertEquals(target, route.getTargetHost());
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestSystemDefaultRoutePlanner.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestSystemDefaultRoutePlanner.java
index aaf87e3bd..0d3bd3127 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestSystemDefaultRoutePlanner.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/routing/TestSystemDefaultRoutePlanner.java
@@ -66,7 +66,7 @@ public class TestSystemDefaultRoutePlanner {
     public void testDirect() throws Exception {
         final HttpHost target = new HttpHost("http", "somehost", 80);
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpRoute route = routePlanner.determineRoute(target, context);
 
         Assertions.assertEquals(target, route.getTargetHost());
@@ -80,7 +80,7 @@ public class TestSystemDefaultRoutePlanner {
         final HttpHost target = new HttpHost("https", "somehost", -1);
         Mockito.when(schemePortResolver.resolve(target)).thenReturn(443);
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpRoute route = routePlanner.determineRoute(target, context);
 
         Assertions.assertEquals(new HttpHost("https", "somehost", 443), route.getTargetHost());
@@ -105,7 +105,7 @@ public class TestSystemDefaultRoutePlanner {
 
         final HttpHost target = new HttpHost("http", "somehost", 80);
 
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final HttpRoute route = routePlanner.determineRoute(target, context);
 
         Assertions.assertEquals(target, route.getTargetHost());
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestDefaultHeaders.java b/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestDefaultHeaders.java
index 5330caf9a..928603794 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestDefaultHeaders.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestDefaultHeaders.java
@@ -42,7 +42,7 @@ public class TestRequestDefaultHeaders {
 
     @Test
     public void testRequestParameterCheck() throws Exception {
-        final HttpContext context = new HttpClientContext();
+        final HttpContext context = HttpClientContext.create();
         final HttpRequestInterceptor interceptor = RequestDefaultHeaders.INSTANCE;
         Assertions.assertThrows(NullPointerException.class, () ->
                 interceptor.process(null, null, context));
@@ -53,7 +53,7 @@ public class TestRequestDefaultHeaders {
         final HttpRequest request = new BasicHttpRequest("CONNECT", "www.somedomain.com");
         final List<Header> defheaders = new ArrayList<>();
         defheaders.add(new BasicHeader("custom", "stuff"));
-        final HttpContext context = new HttpClientContext();
+        final HttpContext context = HttpClientContext.create();
 
         final HttpRequestInterceptor interceptor = new RequestDefaultHeaders(defheaders);
         interceptor.process(request, null, context);
@@ -67,7 +67,7 @@ public class TestRequestDefaultHeaders {
         request.addHeader("custom", "stuff");
         final List<Header> defheaders = new ArrayList<>();
         defheaders.add(new BasicHeader("custom", "other stuff"));
-        final HttpContext context = new HttpClientContext();
+        final HttpContext context = HttpClientContext.create();
 
         final HttpRequestInterceptor interceptor = new RequestDefaultHeaders(defheaders);
         interceptor.process(request, null, context);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestExpectContinue.java b/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestExpectContinue.java
index 7b91db8fa..5a0068c7f 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestExpectContinue.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestExpectContinue.java
@@ -60,7 +60,7 @@ public class TestRequestExpectContinue {
 
     @Test
     public void testRequestExpectContinueNotGenerated() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final RequestConfig config = RequestConfig.custom().setExpectContinueEnabled(false).build();
         context.setRequestConfig(config);
         final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
@@ -75,7 +75,7 @@ public class TestRequestExpectContinue {
 
     @Test
     public void testRequestExpectContinueHTTP10() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final RequestConfig config = RequestConfig.custom().setExpectContinueEnabled(true).build();
         context.setRequestConfig(config);
         final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
@@ -91,7 +91,7 @@ public class TestRequestExpectContinue {
 
     @Test
     public void testRequestExpectContinueZeroContent() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final RequestConfig config = RequestConfig.custom().setExpectContinueEnabled(true).build();
         context.setRequestConfig(config);
         final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
@@ -112,7 +112,7 @@ public class TestRequestExpectContinue {
 
     @Test
     public void testRequestExpectContinueIgnoreNonenclosingRequests() throws Exception {
-        final HttpClientContext context = new HttpClientContext();
+        final HttpClientContext context = HttpClientContext.create();
         final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
         final RequestExpectContinue interceptor = new RequestExpectContinue();
         interceptor.process(request, null, context);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestValidateTrace.java b/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestValidateTrace.java
index 9b334ba74..6ab8c654d 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestValidateTrace.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/protocol/TestRequestValidateTrace.java
@@ -53,7 +53,7 @@ class TestRequestValidateTrace {
     @BeforeEach
     void setUp() {
         interceptor = new RequestValidateTrace();
-        context = new HttpClientContext();
+        context = HttpClientContext.create();
     }
 
     @Test