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 2023/06/29 07:37:26 UTC

[httpcomponents-client] branch 5.3.x updated (7c719fdea -> 7df675eb6)

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

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


    from 7c719fdea HTTPCLIENT-2277: Revision and optimization of cache key generation
     new 80299b004 Better request method check (no functional changes)
     new 7df675eb6 HTTPCLIENT-2277: optimization of internal cache operations

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../hc/client5/http/cache/HeaderConstants.java     |  28 ++
 .../client5/http/impl/cache/AsyncCachingExec.java  | 214 +++++----
 .../http/impl/cache/BasicHttpAsyncCache.java       | 530 +++++++++------------
 .../hc/client5/http/impl/cache/BasicHttpCache.java | 310 ++++++------
 .../impl/cache/{Variant.java => CacheHit.java}     |  27 +-
 .../http/impl/cache/CacheInvalidatorBase.java      |   8 +-
 .../impl/cache/{Variant.java => CacheMatch.java}   |  32 +-
 .../http/impl/cache/CacheableRequestPolicy.java    |   4 +-
 .../impl/cache/CachedHttpResponseGenerator.java    |   6 +-
 .../cache/CachedResponseSuitabilityChecker.java    |  23 +-
 .../hc/client5/http/impl/cache/CachingExec.java    | 148 +++---
 .../client5/http/impl/cache/CachingExecBase.java   |  15 +-
 .../http/impl/cache/ConditionalRequestBuilder.java |   6 +-
 .../hc/client5/http/impl/cache/HttpAsyncCache.java |  90 ++--
 .../hc/client5/http/impl/cache/HttpCache.java      |  74 ++-
 .../http/impl/cache/RequestProtocolCompliance.java |   9 +-
 .../http/impl/cache/ResponseCachingPolicy.java     |   5 +-
 .../impl/cache/ResponseProtocolCompliance.java     |   4 +-
 .../hc/client5/http/cache/TestHttpCacheEntry.java  |   3 +-
 .../http/impl/cache/ContainsHeaderMatcher.java     |   2 +-
 .../hc/client5/http/impl/cache/HttpTestUtils.java  |   5 +
 .../http/impl/cache/TestBasicHttpCache.java        | 135 ++----
 .../impl/cache/TestCacheableRequestPolicy.java     |   2 +-
 .../http/impl/cache/TestCachingExecChain.java      |  35 +-
 .../impl/cache/TestConditionalRequestBuilder.java  |  10 +-
 .../http/impl/cache/TestProtocolRequirements.java  |  43 +-
 .../impl/cache/TestRequestProtocolCompliance.java  |  15 +-
 .../http/impl/cache/TestResponseCachingPolicy.java |   2 +-
 28 files changed, 866 insertions(+), 919 deletions(-)
 copy httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/{Variant.java => CacheHit.java} (68%)
 rename httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/{Variant.java => CacheMatch.java} (71%)


[httpcomponents-client] 02/02: HTTPCLIENT-2277: optimization of internal cache operations

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 7df675eb60a5d369f079229e0b87d2e0ce2287fd
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Thu Jun 22 23:02:02 2023 +0200

    HTTPCLIENT-2277: optimization of internal cache operations
---
 .../client5/http/impl/cache/AsyncCachingExec.java  | 214 +++++----
 .../http/impl/cache/BasicHttpAsyncCache.java       | 530 +++++++++------------
 .../hc/client5/http/impl/cache/BasicHttpCache.java | 310 ++++++------
 .../impl/cache/{Variant.java => CacheHit.java}     |  27 +-
 .../impl/cache/{Variant.java => CacheMatch.java}   |  32 +-
 .../impl/cache/CachedHttpResponseGenerator.java    |   2 +-
 .../hc/client5/http/impl/cache/CachingExec.java    | 148 +++---
 .../client5/http/impl/cache/CachingExecBase.java   |  11 +-
 .../http/impl/cache/ConditionalRequestBuilder.java |   6 +-
 .../hc/client5/http/impl/cache/HttpAsyncCache.java |  90 ++--
 .../hc/client5/http/impl/cache/HttpCache.java      |  74 ++-
 .../http/impl/cache/ContainsHeaderMatcher.java     |   2 +-
 .../hc/client5/http/impl/cache/HttpTestUtils.java  |   5 +
 .../http/impl/cache/TestBasicHttpCache.java        | 135 ++----
 .../http/impl/cache/TestCachingExecChain.java      |  35 +-
 .../impl/cache/TestConditionalRequestBuilder.java  |  10 +-
 .../http/impl/cache/TestProtocolRequirements.java  |  43 +-
 17 files changed, 795 insertions(+), 879 deletions(-)

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 24562c3ac..d113861cd 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
@@ -30,6 +30,8 @@ import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.nio.ByteBuffer;
 import java.time.Instant;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ScheduledExecutorService;
@@ -44,7 +46,6 @@ import org.apache.hc.client5.http.async.methods.SimpleBody;
 import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
 import org.apache.hc.client5.http.cache.CacheResponseStatus;
 import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
-import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.cache.ResourceIOException;
 import org.apache.hc.client5.http.impl.ExecSupport;
@@ -64,6 +65,7 @@ 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.HttpStatus;
+import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.impl.BasicEntityDetails;
 import org.apache.hc.core5.http.nio.AsyncDataConsumer;
 import org.apache.hc.core5.http.nio.AsyncEntityProducer;
@@ -256,22 +258,24 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
 
             }));
         } else {
-            operation.setDependency(responseCache.getCacheEntry(target, request, new FutureCallback<HttpCacheEntry>() {
+            operation.setDependency(responseCache.match(target, request, new FutureCallback<CacheMatch>() {
 
                 @Override
-                public void completed(final HttpCacheEntry entry) {
-                    final SimpleHttpResponse fatalErrorResponse = getFatallyNonCompliantResponse(request, context, entry != null);
+                public void completed(final CacheMatch result) {
+                    final CacheHit hit = result != null ? result.hit : null;
+                    final CacheHit root = result != null ? result.root : null;
+                    final SimpleHttpResponse fatalErrorResponse = getFatallyNonCompliantResponse(request, context, hit != null);
                     if (fatalErrorResponse != null) {
                         triggerResponse(fatalErrorResponse, scope, asyncExecCallback);
                         return;
                     }
 
-                    if (entry == null) {
+                    if (hit == null) {
                         LOG.debug("Cache miss");
-                        handleCacheMiss(requestCacheControl, target, request, entityProducer, scope, chain, asyncExecCallback);
+                        handleCacheMiss(requestCacheControl, root, target, request, entityProducer, scope, chain, asyncExecCallback);
                     } else {
-                        final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
-                        handleCacheHit(requestCacheControl, responseCacheControl, target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+                        final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(hit.entry);
+                        handleCacheHit(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
                     }
                 }
 
@@ -498,22 +502,24 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                 storeRequestIfModifiedSinceFor304Response(request, backendResponse);
             } else {
                 LOG.debug("Backend response is not cacheable");
-                responseCache.flushCacheEntriesFor(target, request, new FutureCallback<Boolean>() {
+                if (!Method.isSafe(request.getMethod())) {
+                    responseCache.flushCacheEntriesFor(target, request, new FutureCallback<Boolean>() {
 
-                    @Override
-                    public void completed(final Boolean result) {
-                    }
+                        @Override
+                        public void completed(final Boolean result) {
+                        }
 
-                    @Override
-                    public void failed(final Exception ex) {
-                        LOG.warn("Unable to flush invalidated entries from cache", ex);
-                    }
+                        @Override
+                        public void failed(final Exception ex) {
+                            LOG.warn("Unable to flush invalidated entries from cache", ex);
+                        }
 
-                    @Override
-                    public void cancelled() {
-                    }
+                        @Override
+                        public void cancelled() {
+                        }
 
-                });
+                    });
+                }
             }
             final CachingAsyncDataConsumer cachingDataConsumer = cachingConsumerRef.get();
             if (cachingDataConsumer != null) {
@@ -530,20 +536,20 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
 
         void triggerNewCacheEntryResponse(final HttpResponse backendResponse, final Instant responseDate, final ByteArrayBuffer buffer) {
             final CancellableDependency operation = scope.cancellableDependency;
-            operation.setDependency(responseCache.createEntry(
+            operation.setDependency(responseCache.store(
                     target,
                     request,
                     backendResponse,
                     buffer,
                     requestDate,
                     responseDate,
-                    new FutureCallback<HttpCacheEntry>() {
+                    new FutureCallback<CacheHit>() {
 
                         @Override
-                        public void completed(final HttpCacheEntry newEntry) {
+                        public void completed(final CacheHit hit) {
                             LOG.debug("Backend response successfully cached");
                             try {
-                                final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, newEntry);
+                                final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
                                 triggerResponse(cacheResponse, scope, asyncExecCallback);
                             } catch (final ResourceIOException ex) {
                                 asyncExecCallback.failed(ex);
@@ -572,14 +578,15 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                 final HttpResponse backendResponse = cachingDataConsumer.backendResponse;
                 if (cacheConfig.isFreshnessCheckEnabled()) {
                     final CancellableDependency operation = scope.cancellableDependency;
-                    operation.setDependency(responseCache.getCacheEntry(target, request, new FutureCallback<HttpCacheEntry>() {
+                    operation.setDependency(responseCache.match(target, request, new FutureCallback<CacheMatch>() {
 
                         @Override
-                        public void completed(final HttpCacheEntry existingEntry) {
-                            if (DateSupport.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) {
+                        public void completed(final CacheMatch result) {
+                            final CacheHit hit = result != null ? result.hit : null;
+                            if (DateSupport.isAfter(hit != null ? hit.entry : null, backendResponse, HttpHeaders.DATE)) {
                                 LOG.debug("Backend already contains fresher cache entry");
                                 try {
-                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, existingEntry);
+                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
                                     triggerResponse(cacheResponse, scope, asyncExecCallback);
                                 } catch (final ResourceIOException ex) {
                                     asyncExecCallback.failed(ex);
@@ -618,13 +625,13 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
     private void handleCacheHit(
             final RequestCacheControl requestCacheControl,
             final ResponseCacheControl responseCacheControl,
+            final CacheHit hit,
             final HttpHost target,
             final HttpRequest request,
             final AsyncEntityProducer entityProducer,
             final AsyncExecChain.Scope scope,
             final AsyncExecChain chain,
-            final AsyncExecCallback asyncExecCallback,
-            final HttpCacheEntry entry) {
+            final AsyncExecCallback asyncExecCallback) {
         final HttpClientContext context  = scope.clientContext;
         recordCacheHit(target, request);
         final Instant now = getCurrentDate();
@@ -634,21 +641,19 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             if (LOG.isDebugEnabled()) {
                 LOG.debug("Revalidating with server due to no-cache directive in response.");
             }
-            revalidateCacheEntry(requestCacheControl, responseCacheControl,
-                    target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+            revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
             return;
         }
 
-        if (suitabilityChecker.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now)) {
-            if (responseCachingPolicy.responseContainsNoCacheDirective(responseCacheControl, entry)) {
+        if (suitabilityChecker.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, hit.entry, now)) {
+            if (responseCachingPolicy.responseContainsNoCacheDirective(responseCacheControl, hit.entry)) {
                 // Revalidate with the server due to no-cache directive in response
-                revalidateCacheEntry(requestCacheControl, responseCacheControl,
-                        target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+                revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
                 return;
             }
             LOG.debug("Cache hit");
             try {
-                final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, request, context, entry, now);
+                final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
                 triggerResponse(cacheResponse, scope, asyncExecCallback);
             } catch (final ResourceIOException ex) {
                 recordCacheFailure(target, request);
@@ -668,15 +673,15 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             LOG.debug("Cache entry not suitable but only-if-cached requested");
             final SimpleHttpResponse cacheResponse = generateGatewayTimeout(context);
             triggerResponse(cacheResponse, scope, asyncExecCallback);
-        } else if (!(entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
+        } else if (!(hit.entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
             LOG.debug("Revalidating cache entry");
-            final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, entry);
+            final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, hit.entry);
             if (cacheRevalidator != null
-                    && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)
-                    && (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now) || staleIfErrorEnabled)) {
+                    && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, now)
+                    && (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, hit.entry, now) || staleIfErrorEnabled)) {
                 LOG.debug("Serving stale with asynchronous revalidation");
                 try {
-                    final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, request, context, entry, now);
+                    final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
                     final String exchangeId = ExecSupport.getNextExchangeId();
                     context.setExchangeId(exchangeId);
                     final AsyncExecChain.Scope fork = new AsyncExecChain.Scope(
@@ -689,10 +694,10 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                             scope.scheduler,
                             scope.execCount);
                     cacheRevalidator.revalidateCacheEntry(
-                            responseCache.generateKey(target, request, entry),
+                            hit.getEntryKey(),
                             asyncExecCallback,
                             asyncExecCallback1 -> revalidateCacheEntry(requestCacheControl, responseCacheControl,
-                                    target, request, entityProducer, fork, chain, asyncExecCallback1, entry));
+                                    hit, target, request, entityProducer, fork, chain, asyncExecCallback1));
                     triggerResponse(cacheResponse, scope, asyncExecCallback);
                 } catch (final ResourceIOException ex) {
                     if (staleIfErrorEnabled) {
@@ -700,8 +705,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                             LOG.debug("Serving stale response due to IOException and stale-if-error enabled");
                         }
                         try {
-                            final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl,
-                                    request, context, entry, now);
+                            final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
                             triggerResponse(cacheResponse, scope, asyncExecCallback);
                         } catch (final ResourceIOException ex2) {
                             if (LOG.isDebugEnabled()) {
@@ -714,8 +718,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                     }
                 }
             } else {
-                revalidateCacheEntry(requestCacheControl, responseCacheControl,
-                        target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+                revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
             }
         } else {
             LOG.debug("Cache entry not usable; calling backend");
@@ -726,18 +729,18 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
     void revalidateCacheEntry(
             final RequestCacheControl requestCacheControl,
             final ResponseCacheControl responseCacheControl,
+            final CacheHit hit,
             final HttpHost target,
             final HttpRequest request,
             final AsyncEntityProducer entityProducer,
             final AsyncExecChain.Scope scope,
             final AsyncExecChain chain,
-            final AsyncExecCallback asyncExecCallback,
-            final HttpCacheEntry cacheEntry) {
+            final AsyncExecCallback asyncExecCallback) {
         final Instant requestDate = getCurrentDate();
         final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(
                 responseCacheControl,
                 BasicRequestBuilder.copy(scope.originalRequest).build(),
-                cacheEntry);
+                hit.entry);
         chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
 
             final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
@@ -745,24 +748,23 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             void triggerUpdatedCacheEntryResponse(final HttpResponse backendResponse, final Instant responseDate) {
                 final CancellableDependency operation = scope.cancellableDependency;
                 recordCacheUpdate(scope.clientContext);
-                operation.setDependency(responseCache.updateEntry(
-                        target,
+                operation.setDependency(responseCache.update(
+                        hit,
                         request,
-                        cacheEntry,
                         backendResponse,
                         requestDate,
                         responseDate,
-                        new FutureCallback<HttpCacheEntry>() {
+                        new FutureCallback<CacheHit>() {
 
                             @Override
-                            public void completed(final HttpCacheEntry updatedEntry) {
+                            public void completed(final CacheHit updated) {
                                 if (suitabilityChecker.isConditional(request)
-                                        && suitabilityChecker.allConditionalsMatch(request, updatedEntry, Instant.now())) {
-                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(updatedEntry);
+                                        && suitabilityChecker.allConditionalsMatch(request, updated.entry, Instant.now())) {
+                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(updated.entry);
                                     triggerResponse(cacheResponse, scope, asyncExecCallback);
                                 } else {
                                     try {
-                                        final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updatedEntry);
+                                        final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updated.entry);
                                         triggerResponse(cacheResponse, scope, asyncExecCallback);
                                     } catch (final ResourceIOException ex) {
                                         asyncExecCallback.failed(ex);
@@ -785,7 +787,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
 
             void triggerResponseStaleCacheEntry() {
                 try {
-                    final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, cacheEntry);
+                    final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
                     cacheResponse.addHeader(HttpHeaders.WARNING, "110 localhost \"Response is stale\"");
                     triggerResponse(cacheResponse, scope, asyncExecCallback);
                 } catch (final ResourceIOException ex) {
@@ -804,8 +806,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                     return new AsyncExecCallbackWrapper(asyncExecCallback, () -> triggerUpdatedCacheEntryResponse(backendResponse, responseDate));
                 }
                 if (staleIfErrorAppliesTo(statusCode)
-                        && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, cacheEntry, getCurrentDate())
-                        && validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, cacheEntry, responseDate)) {
+                        && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())
+                        && validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, hit.entry, responseDate)) {
                     return new AsyncExecCallbackWrapper(asyncExecCallback, this::triggerResponseStaleCacheEntry);
                 }
                 return new BackendResponseHandler(target, conditionalRequest, requestDate, responseDate, scope, asyncExecCallback);
@@ -819,7 +821,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                 final Instant responseDate1 = getCurrentDate();
 
                 final AsyncExecCallback callback1;
-                if (revalidationResponseIsTooOld(backendResponse1, cacheEntry)
+                if (revalidationResponseIsTooOld(backendResponse1, hit.entry)
                         && (entityProducer == null || entityProducer.isRepeatable())) {
 
                     final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
@@ -911,6 +913,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
 
     private void handleCacheMiss(
             final RequestCacheControl requestCacheControl,
+            final CacheHit partialMatch,
             final HttpHost target,
             final HttpRequest request,
             final AsyncEntityProducer entityProducer,
@@ -919,15 +922,19 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             final AsyncExecCallback asyncExecCallback) {
         recordCacheMiss(target, request);
 
-        if (mayCallBackend(requestCacheControl)) {
-            final CancellableDependency operation = scope.cancellableDependency;
-            operation.setDependency(responseCache.getVariantCacheEntriesWithEtags(
-                    target,
-                    request,
-                    new FutureCallback<Map<String, Variant>>() {
+        final CancellableDependency operation = scope.cancellableDependency;
+        if (!mayCallBackend(requestCacheControl)) {
+            final SimpleHttpResponse cacheResponse = SimpleHttpResponse.create(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
+            triggerResponse(cacheResponse, scope, asyncExecCallback);
+        }
+
+        if (partialMatch != null && partialMatch.entry.isVariantRoot()) {
+            operation.setDependency(responseCache.getVariants(
+                    partialMatch,
+                    new FutureCallback<Collection<CacheHit>>() {
 
                         @Override
-                        public void completed(final Map<String, Variant> variants) {
+                        public void completed(final Collection<CacheHit> variants) {
                             if (variants != null && !variants.isEmpty() && (entityProducer == null || entityProducer.isRepeatable())) {
                                 negotiateResponseFromVariants(target, request, entityProducer, scope, chain, asyncExecCallback, variants);
                             } else {
@@ -947,8 +954,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
 
                     }));
         } else {
-            final SimpleHttpResponse cacheResponse = SimpleHttpResponse.create(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
-            triggerResponse(cacheResponse, scope, asyncExecCallback);
+            callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
         }
     }
 
@@ -959,48 +965,52 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
             final AsyncExecChain.Scope scope,
             final AsyncExecChain chain,
             final AsyncExecCallback asyncExecCallback,
-            final Map<String, Variant> variants) {
+            final Collection<CacheHit> variants) {
         final CancellableDependency operation = scope.cancellableDependency;
+        final Map<String, CacheHit> variantMap = new HashMap<>();
+        for (final CacheHit variant : variants) {
+            final Header header = variant.entry.getFirstHeader(HttpHeaders.ETAG);
+            if (header != null) {
+                variantMap.put(header.getValue(), variant);
+            }
+        }
         final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
                 BasicRequestBuilder.copy(request).build(),
-                variants);
+                variantMap.keySet());
 
         final Instant requestDate = getCurrentDate();
         chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
 
             final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
 
-            void updateVariantCacheEntry(final HttpResponse backendResponse, final Instant responseDate, final Variant matchingVariant) {
+            void updateVariantCacheEntry(final HttpResponse backendResponse, final Instant responseDate, final CacheHit match) {
                 recordCacheUpdate(scope.clientContext);
-                final HttpCacheEntry variantEntry = matchingVariant.getEntry();
-                operation.setDependency(responseCache.updateVariantEntry(
-                        target,
-                        conditionalRequest,
+                operation.setDependency(responseCache.update(
+                        match,
                         backendResponse,
-                        variantEntry,
                         requestDate,
                         responseDate,
-                        new FutureCallback<HttpCacheEntry>() {
+                        new FutureCallback<CacheHit>() {
 
                             @Override
-                            public void completed(final HttpCacheEntry responseEntry) {
-                                if (shouldSendNotModifiedResponse(request, responseEntry)) {
-                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(responseEntry);
+                            public void completed(final CacheHit hit) {
+                                if (shouldSendNotModifiedResponse(request, hit.entry)) {
+                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(hit.entry);
                                     triggerResponse(cacheResponse, scope, asyncExecCallback);
                                 } else {
                                     try {
-                                        final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, responseEntry);
-                                        operation.setDependency(responseCache.reuseVariantEntryFor(
+                                        final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
+                                        operation.setDependency(responseCache.storeReusing(
+                                                hit,
                                                 target,
                                                 request,
                                                 backendResponse,
-                                                responseEntry,
                                                 requestDate,
                                                 responseDate,
-                                                new FutureCallback<Boolean>() {
+                                                new FutureCallback<CacheHit>() {
 
                                                     @Override
-                                                    public void completed(final Boolean result) {
+                                                    public void completed(final CacheHit result) {
                                                         triggerResponse(cacheResponse, scope, asyncExecCallback);
                                                     }
 
@@ -1044,29 +1054,29 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                 final AsyncExecCallback callback;
                 // Handle 304 Not Modified responses
                 if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
-                    responseCache.getCacheEntry(target, request, new FutureCallback<HttpCacheEntry>() {
+                    responseCache.match(target, request, new FutureCallback<CacheMatch>() {
                         @Override
-                        public void completed(final HttpCacheEntry existingEntry) {
-                            if (existingEntry != null) {
+                        public void completed(final CacheMatch result) {
+                            final CacheHit hit = result != null ? result.hit : null;
+                            if (hit != null) {
                                 if (LOG.isDebugEnabled()) {
                                     LOG.debug("Existing cache entry found, updating cache entry");
                                 }
-                                responseCache.updateEntry(
-                                        target,
+                                responseCache.update(
+                                        hit,
                                         request,
-                                        existingEntry,
                                         backendResponse,
                                         requestDate,
                                         responseDate,
-                                        new FutureCallback<HttpCacheEntry>() {
+                                        new FutureCallback<CacheHit>() {
 
                                             @Override
-                                            public void completed(final HttpCacheEntry updatedEntry) {
+                                            public void completed(final CacheHit updated) {
                                                 try {
                                                     if (LOG.isDebugEnabled()) {
                                                         LOG.debug("Cache entry updated, generating response from updated entry");
                                                     }
-                                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updatedEntry);
+                                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updated.entry);
                                                     triggerResponse(cacheResponse, scope, asyncExecCallback);
                                                 } catch (final ResourceIOException ex) {
                                                     asyncExecCallback.failed(ex);
@@ -1113,18 +1123,18 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
                         callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback));
                     } else {
                         final String resultEtag = resultEtagHeader.getValue();
-                        final Variant matchingVariant = variants.get(resultEtag);
-                        if (matchingVariant == null) {
+                        final CacheHit match = variantMap.get(resultEtag);
+                        if (match == null) {
                             LOG.debug("304 response did not contain ETag matching one sent in If-None-Match");
                             callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback));
                         } else {
-                            if (revalidationResponseIsTooOld(backendResponse, matchingVariant.getEntry())) {
+                            if (revalidationResponseIsTooOld(backendResponse, match.entry)) {
                                 final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
                                         BasicRequestBuilder.copy(request).build());
                                 scope.clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, unconditional);
                                 callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback));
                             } else {
-                                callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> updateVariantCacheEntry(backendResponse, responseDate, matchingVariant));
+                                callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> updateVariantCacheEntry(backendResponse, responseDate, match));
                             }
                         }
                     }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
index 4c3f84046..f3a07c6af 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
@@ -27,24 +27,26 @@
 package org.apache.hc.client5.http.impl.cache;
 
 import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.apache.hc.client5.http.cache.HttpAsyncCacheInvalidator;
 import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
 import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
+import org.apache.hc.client5.http.cache.Resource;
 import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.cache.ResourceIOException;
 import org.apache.hc.client5.http.impl.Operations;
 import org.apache.hc.core5.concurrent.Cancellable;
 import org.apache.hc.core5.concurrent.ComplexCancellable;
 import org.apache.hc.core5.concurrent.FutureCallback;
-import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
@@ -90,111 +92,152 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
     }
 
     @Override
-    public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
-        final String root = cacheKeyGenerator.generateKey(host, request);
-        if (cacheEntry != null && cacheEntry.isVariantRoot()) {
-            final List<String> variantNames = CacheKeyGenerator.variantNames(cacheEntry);
-            if (!variantNames.isEmpty()) {
-                final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
-                return variantKey + root;
-            }
-        }
-        return root;
-    }
-
-    @Override
-    public Cancellable flushCacheEntriesFor(
-            final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
+    public Cancellable match(final HttpHost host, final HttpRequest request, final FutureCallback<CacheMatch> callback) {
+        final String rootKey = cacheKeyGenerator.generateKey(host, request);
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request));
+            LOG.debug("Get cache entry: {}", rootKey);
         }
-        if (!Method.isSafe(request.getMethod())) {
-            final String rootKey = cacheKeyGenerator.generateKey(host, request);
-            return storage.removeEntry(rootKey, new FutureCallback<Boolean>() {
+        final ComplexCancellable complexCancellable = new ComplexCancellable();
+        complexCancellable.setDependency(storage.getEntry(rootKey, new FutureCallback<HttpCacheEntry>() {
 
-                @Override
-                public void completed(final Boolean result) {
-                    callback.completed(result);
-                }
+            @Override
+            public void completed(final HttpCacheEntry root) {
+                if (root != null) {
+                    if (root.isVariantRoot()) {
+                        final List<String> variantNames = CacheKeyGenerator.variantNames(root);
+                        final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
+                        final String cacheKey = root.getVariantMap().get(variantKey);
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("Get cache variant entry: {}", cacheKey);
+                        }
+                        if (cacheKey != null) {
+                            complexCancellable.setDependency(storage.getEntry(
+                                    cacheKey,
+                                    new FutureCallback<HttpCacheEntry>() {
+
+                                        @Override
+                                        public void completed(final HttpCacheEntry entry) {
+                                            callback.completed(new CacheMatch(
+                                                    entry != null ? new CacheHit(rootKey, cacheKey, entry) : null,
+                                                    new CacheHit(rootKey, root)));
+                                        }
+
+                                        @Override
+                                        public void failed(final Exception ex) {
+                                            if (ex instanceof ResourceIOException) {
+                                                if (LOG.isWarnEnabled()) {
+                                                    LOG.warn("I/O error retrieving cache entry with key {}", cacheKey);
+                                                }
+                                                callback.completed(null);
+                                            } else {
+                                                callback.failed(ex);
+                                            }
+                                        }
+
+                                        @Override
+                                        public void cancelled() {
+                                            callback.cancelled();
+                                        }
 
-                @Override
-                public void failed(final Exception ex) {
-                    if (ex instanceof ResourceIOException) {
-                        if (LOG.isWarnEnabled()) {
-                            LOG.warn("I/O error removing cache entry with key {}", rootKey);
+                                    }));
+                            return;
                         }
-                        callback.completed(Boolean.TRUE);
-                    } else {
-                        callback.failed(ex);
                     }
+                    callback.completed(new CacheMatch(new CacheHit(rootKey, root), null));
+                } else {
+                    callback.completed(null);
                 }
+            }
 
-                @Override
-                public void cancelled() {
-                    callback.cancelled();
+            @Override
+            public void failed(final Exception ex) {
+                if (ex instanceof ResourceIOException) {
+                    if (LOG.isWarnEnabled()) {
+                        LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
+                    }
+                    callback.completed(null);
+                } else {
+                    callback.failed(ex);
                 }
+            }
 
-            });
-        }
-        callback.completed(Boolean.TRUE);
-        return Operations.nonCancellable();
-    }
+            @Override
+            public void cancelled() {
+                callback.cancelled();
+            }
 
-    @Override
-    public Cancellable flushCacheEntriesInvalidatedByRequest(
-            final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
-        }
-        return cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage, callback);
+        }));
+        return complexCancellable;
     }
 
     @Override
-    public Cancellable flushCacheEntriesInvalidatedByExchange(
-            final HttpHost host, final HttpRequest request, final HttpResponse response, final FutureCallback<Boolean> callback) {
+    public Cancellable getVariants(
+            final CacheHit hit, final FutureCallback<Collection<CacheHit>> callback) {
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
+            LOG.debug("Get variant cache entries: {}", hit.rootKey);
         }
-        if (!Method.isSafe(request.getMethod())) {
-            return cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage, callback);
-        }
-        callback.completed(Boolean.TRUE);
-        return Operations.nonCancellable();
-    }
+        final ComplexCancellable complexCancellable = new ComplexCancellable();
+        final HttpCacheEntry root = hit.entry;
+        if (root != null && root.isVariantRoot()) {
+            final Set<String> variantCacheKeys = root.getVariantMap().keySet();
+            complexCancellable.setDependency(storage.getEntries(
+                    variantCacheKeys,
+                    new FutureCallback<Map<String, HttpCacheEntry>>() {
 
-    Cancellable storeInCache(
-            final HttpRequest request,
-            final HttpResponse originResponse,
-            final Instant requestSent,
-            final Instant responseReceived,
-            final String rootKey,
-            final HttpCacheEntry entry,
-            final FutureCallback<Boolean> callback) {
-        if (entry.hasVariants()) {
-            return storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry, callback);
+                        @Override
+                        public void completed(final Map<String, HttpCacheEntry> resultMap) {
+                            final List<CacheHit> cacheHits = resultMap.entrySet().stream()
+                                    .map(e -> new CacheHit(hit.rootKey, e.getKey(), e.getValue()))
+                                    .collect(Collectors.toList());
+                            callback.completed(cacheHits);
+                        }
+
+                        @Override
+                        public void failed(final Exception ex) {
+                            if (ex instanceof ResourceIOException) {
+                                if (LOG.isWarnEnabled()) {
+                                    LOG.warn("I/O error retrieving cache entry with keys {}", variantCacheKeys);
+                                }
+                                callback.completed(Collections.emptyList());
+                            } else {
+                                callback.failed(ex);
+                            }
+                        }
+
+                        @Override
+                        public void cancelled() {
+                            callback.cancelled();
+                        }
+
+                    }));
         } else {
-            return storeEntry(rootKey, entry, callback);
+            callback.completed(Collections.emptyList());
         }
+        return complexCancellable;
     }
 
     Cancellable storeEntry(
-            final String cacheKey,
+            final String rootKey,
             final HttpCacheEntry entry,
-            final FutureCallback<Boolean> callback) {
-        return storage.putEntry(cacheKey, entry, new FutureCallback<Boolean>() {
+            final FutureCallback<CacheHit> callback) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Put entry in cache: {}", rootKey);
+        }
+
+        return storage.putEntry(rootKey, entry, new FutureCallback<Boolean>() {
 
             @Override
             public void completed(final Boolean result) {
-                callback.completed(result);
+                callback.completed(new CacheHit(rootKey, entry));
             }
 
             @Override
             public void failed(final Exception ex) {
                 if (ex instanceof ResourceIOException) {
                     if (LOG.isWarnEnabled()) {
-                        LOG.warn("I/O error storing cache entry with key {}", cacheKey);
+                        LOG.warn("I/O error storing cache entry with key {}", rootKey);
                     }
-                    callback.completed(Boolean.TRUE);
+                    callback.completed(new CacheHit(rootKey, entry));
                 } else {
                     callback.failed(ex);
                 }
@@ -208,21 +251,30 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
         });
     }
 
-    Cancellable storeVariantEntry(
+    Cancellable storeVariant(
             final HttpRequest request,
             final HttpResponse originResponse,
             final Instant requestSent,
             final Instant responseReceived,
             final String rootKey,
             final HttpCacheEntry entry,
-            final FutureCallback<Boolean> callback) {
+            final FutureCallback<CacheHit> callback) {
         final List<String> variantNames = CacheKeyGenerator.variantNames(entry);
         final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
         final String variantCacheKey = variantKey + rootKey;
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Put variant entry in cache: {}", variantCacheKey);
+        }
+
         return storage.putEntry(variantCacheKey, entry, new FutureCallback<Boolean>() {
 
             @Override
             public void completed(final Boolean result) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Update root entry: {}", rootKey);
+                }
+
                 storage.updateEntry(rootKey,
                         existing -> {
                             final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>();
@@ -233,7 +285,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
 
                             @Override
                             public void completed(final Boolean result) {
-                                callback.completed(result);
+                                callback.completed(new CacheHit(rootKey, variantCacheKey, entry));
                             }
 
                             @Override
@@ -265,7 +317,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
                     if (LOG.isWarnEnabled()) {
                         LOG.warn("I/O error updating cache entry with key {}", variantCacheKey);
                     }
-                    callback.completed(Boolean.TRUE);
+                    callback.completed(new CacheHit(rootKey, variantCacheKey, entry));
                 } else {
                     callback.failed(ex);
                 }
@@ -279,217 +331,152 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
         });
     }
 
+    Cancellable store(
+            final HttpRequest request,
+            final HttpResponse originResponse,
+            final Instant requestSent,
+            final Instant responseReceived,
+            final String rootKey,
+            final HttpCacheEntry entry,
+            final FutureCallback<CacheHit> callback) {
+        if (entry.hasVariants()) {
+            return storeVariant(request, originResponse, requestSent, responseReceived, rootKey, entry, callback);
+        } else {
+            return storeEntry(rootKey, entry, callback);
+        }
+    }
+
     @Override
-    public Cancellable reuseVariantEntryFor(
+    public Cancellable store(
             final HttpHost host,
             final HttpRequest request,
             final HttpResponse originResponse,
-            final HttpCacheEntry entry,
+            final ByteArrayBuffer content,
             final Instant requestSent,
             final Instant responseReceived,
-            final FutureCallback<Boolean> callback) {
+            final FutureCallback<CacheHit> callback) {
+        final String rootKey = cacheKeyGenerator.generateKey(host, request);
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), entry);
+            LOG.debug("Create cache entry: {}", rootKey);
         }
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
-        return storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry, callback);
+        final Resource resource;
+        try {
+            resource = content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null;
+        } catch (final ResourceIOException ex) {
+            if (LOG.isWarnEnabled()) {
+                LOG.warn("I/O error creating cache entry with key {}", rootKey);
+            }
+            final HttpCacheEntry backup = cacheEntryFactory.create(
+                    requestSent,
+                    responseReceived,
+                    request,
+                    originResponse,
+                    content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
+            callback.completed(new CacheHit(rootKey, backup));
+            return Operations.nonCancellable();
+        }
+        final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse, resource);
+        return store(
+                request,
+                originResponse,
+                requestSent,
+                responseReceived,
+                rootKey,
+                entry,
+                callback);
     }
 
     @Override
-    public Cancellable updateEntry(
-            final HttpHost host,
+    public Cancellable update(
+            final CacheHit stale,
             final HttpRequest request,
-            final HttpCacheEntry stale,
             final HttpResponse originResponse,
             final Instant requestSent,
             final Instant responseReceived,
-            final FutureCallback<HttpCacheEntry> callback) {
+            final FutureCallback<CacheHit> callback) {
+        final String entryKey = stale.getEntryKey();
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
+            LOG.debug("Update cache entry: {}", entryKey);
         }
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
         final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
                 requestSent,
                 responseReceived,
                 originResponse,
-                stale);
-        return storeInCache(
+                stale.entry);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Put entry in cache: {}", entryKey);
+        }
+
+        return store(
                 request,
                 originResponse,
                 requestSent,
                 responseReceived,
-                rootKey,
+                entryKey,
                 updatedEntry,
-                new FutureCallback<Boolean>() {
-
-                    @Override
-                    public void completed(final Boolean result) {
-                        callback.completed(updatedEntry);
-                    }
-
-                    @Override
-                    public void failed(final Exception ex) {
-                        callback.failed(ex);
-                    }
-
-                    @Override
-                    public void cancelled() {
-                        callback.cancelled();
-                    }
-
-                });
+                callback);
     }
 
     @Override
-    public Cancellable updateVariantEntry(
-            final HttpHost host,
-            final HttpRequest request,
+    public Cancellable update(
+            final CacheHit stale,
             final HttpResponse originResponse,
-            final HttpCacheEntry entry,
             final Instant requestSent,
             final Instant responseReceived,
-            final FutureCallback<HttpCacheEntry> callback) {
+            final FutureCallback<CacheHit> callback) {
+        final String entryKey = stale.getEntryKey();
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), entry);
+            LOG.debug("Update cache entry (no root): {}", entryKey);
         }
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
         final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
                 requestSent,
                 responseReceived,
                 originResponse,
-                entry);
-        return storeEntry(rootKey, updatedEntry, new FutureCallback<Boolean>() {
-
-            @Override
-            public void completed(final Boolean result) {
-                callback.completed(updatedEntry);
-            }
-
-            @Override
-            public void failed(final Exception ex) {
-                callback.failed(ex);
-            }
-
-            @Override
-            public void cancelled() {
-                callback.cancelled();
-            }
-
-        });
+                stale.entry);
+        return storeEntry(
+                entryKey,
+                updatedEntry,
+                callback);
     }
 
     @Override
-    public Cancellable createEntry(
+    public Cancellable storeReusing(
+            final CacheHit hit,
             final HttpHost host,
             final HttpRequest request,
             final HttpResponse originResponse,
-            final ByteArrayBuffer content,
             final Instant requestSent,
             final Instant responseReceived,
-            final FutureCallback<HttpCacheEntry> callback) {
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request));
-        }
+            final FutureCallback<CacheHit> callback) {
         final String rootKey = cacheKeyGenerator.generateKey(host, request);
-        try {
-            final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse,
-                    content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null);
-            return storeInCache(
-                    request,
-                    originResponse,
-                    requestSent,
-                    responseReceived,
-                    rootKey,
-                    entry, new FutureCallback<Boolean>() {
-
-                        @Override
-                        public void completed(final Boolean result) {
-                            callback.completed(entry);
-                        }
-
-                        @Override
-                        public void failed(final Exception ex) {
-                            callback.failed(ex);
-                        }
-
-                        @Override
-                        public void cancelled() {
-                            callback.cancelled();
-                        }
-
-                    });
-        } catch (final ResourceIOException ex) {
-            if (LOG.isWarnEnabled()) {
-                LOG.warn("I/O error creating cache entry with key {}", rootKey);
-            }
-            callback.completed(cacheEntryFactory.create(
-                    requestSent,
-                    responseReceived,
-                    request,
-                    originResponse,
-                    content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null));
-            return Operations.nonCancellable();
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Store cache entry using existing entry: {} -> {}", rootKey, hit.rootKey);
         }
+        return store(request, originResponse, requestSent, responseReceived, rootKey, hit.entry, callback);
     }
 
     @Override
-    public Cancellable getCacheEntry(final HttpHost host, final HttpRequest request, final FutureCallback<HttpCacheEntry> callback) {
+    public Cancellable flushCacheEntriesFor(
+            final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
+        final String rootKey = cacheKeyGenerator.generateKey(host, request);
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request));
+            LOG.debug("Flush cache entries: {}", rootKey);
         }
-        final ComplexCancellable complexCancellable = new ComplexCancellable();
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
-        complexCancellable.setDependency(storage.getEntry(rootKey, new FutureCallback<HttpCacheEntry>() {
+        return storage.removeEntry(rootKey, new FutureCallback<Boolean>() {
 
             @Override
-            public void completed(final HttpCacheEntry root) {
-                if (root != null) {
-                    if (root.isVariantRoot()) {
-                        final List<String> variantNames = CacheKeyGenerator.variantNames(root);
-                        final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
-                        final String variantCacheKey = root.getVariantMap().get(variantKey);
-                        if (variantCacheKey != null) {
-                            complexCancellable.setDependency(storage.getEntry(
-                                    variantCacheKey,
-                                    new FutureCallback<HttpCacheEntry>() {
-
-                                        @Override
-                                        public void completed(final HttpCacheEntry result) {
-                                            callback.completed(result);
-                                        }
-
-                                        @Override
-                                        public void failed(final Exception ex) {
-                                            if (ex instanceof ResourceIOException) {
-                                                if (LOG.isWarnEnabled()) {
-                                                    LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
-                                                }
-                                                callback.completed(null);
-                                            } else {
-                                                callback.failed(ex);
-                                            }
-                                        }
-
-                                        @Override
-                                        public void cancelled() {
-                                            callback.cancelled();
-                                        }
-
-                                    }));
-                            return;
-                        }
-                    }
-                }
-                callback.completed(root);
+            public void completed(final Boolean result) {
+                callback.completed(result);
             }
 
             @Override
             public void failed(final Exception ex) {
                 if (ex instanceof ResourceIOException) {
                     if (LOG.isWarnEnabled()) {
-                        LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
+                        LOG.warn("I/O error removing cache entry with key {}", rootKey);
                     }
-                    callback.completed(null);
+                    callback.completed(Boolean.TRUE);
                 } else {
                     callback.failed(ex);
                 }
@@ -500,84 +487,29 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
                 callback.cancelled();
             }
 
-        }));
-        return complexCancellable;
+        });
     }
 
     @Override
-    public Cancellable getVariantCacheEntriesWithEtags(
-            final HttpHost host, final HttpRequest request, final FutureCallback<Map<String, Variant>> callback) {
+    public Cancellable flushCacheEntriesInvalidatedByRequest(
+            final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request));
+            LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
         }
-        final ComplexCancellable complexCancellable = new ComplexCancellable();
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
-        final Map<String, Variant> variants = new HashMap<>();
-        complexCancellable.setDependency(storage.getEntry(rootKey, new FutureCallback<HttpCacheEntry>() {
-
-            @Override
-            public void completed(final HttpCacheEntry rootEntry) {
-                if (rootEntry != null && rootEntry.isVariantRoot()) {
-                    final Set<String> variantCacheKeys = rootEntry.getVariantMap().keySet();
-                    complexCancellable.setDependency(storage.getEntries(
-                            variantCacheKeys,
-                            new FutureCallback<Map<String, HttpCacheEntry>>() {
-
-                                @Override
-                                public void completed(final Map<String, HttpCacheEntry> resultMap) {
-                                    for (final Map.Entry<String, HttpCacheEntry> resultMapEntry : resultMap.entrySet()) {
-                                        final String cacheKey = resultMapEntry.getKey();
-                                        final HttpCacheEntry cacheEntry = resultMapEntry.getValue();
-                                        final Header etagHeader = cacheEntry.getFirstHeader(HttpHeaders.ETAG);
-                                        if (etagHeader != null) {
-                                            variants.put(etagHeader.getValue(), new Variant(cacheKey, cacheEntry));
-                                        }
-                                    }
-                                    callback.completed(variants);
-                                }
-
-                                @Override
-                                public void failed(final Exception ex) {
-                                    if (ex instanceof ResourceIOException) {
-                                        if (LOG.isWarnEnabled()) {
-                                            LOG.warn("I/O error retrieving cache entry with keys {}", variantCacheKeys);
-                                        }
-                                        callback.completed(variants);
-                                    } else {
-                                        callback.failed(ex);
-                                    }
-                                }
-
-                                @Override
-                                public void cancelled() {
-                                    callback.cancelled();
-                                }
-
-                            }));
-                } else {
-                    callback.completed(variants);
-                }
-            }
-
-            @Override
-            public void failed(final Exception ex) {
-                if (ex instanceof ResourceIOException) {
-                    if (LOG.isWarnEnabled()) {
-                        LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
-                    }
-                    callback.completed(variants);
-                } else {
-                    callback.failed(ex);
-                }
-            }
-
-            @Override
-            public void cancelled() {
-                callback.cancelled();
-            }
+        return cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage, callback);
+    }
 
-        }));
-        return complexCancellable;
+    @Override
+    public Cancellable flushCacheEntriesInvalidatedByExchange(
+            final HttpHost host, final HttpRequest request, final HttpResponse response, final FutureCallback<Boolean> callback) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
+        }
+        if (!Method.isSafe(request.getMethod())) {
+            return cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage, callback);
+        }
+        callback.completed(Boolean.TRUE);
+        return Operations.nonCancellable();
     }
 
 }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
index e2cf0562a..0eecf016e 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
@@ -27,19 +27,21 @@
 package org.apache.hc.client5.http.impl.cache;
 
 import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
 import org.apache.hc.client5.http.cache.HttpCacheInvalidator;
 import org.apache.hc.client5.http.cache.HttpCacheStorage;
 import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
+import org.apache.hc.client5.http.cache.Resource;
 import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.cache.ResourceIOException;
-import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
@@ -92,55 +94,90 @@ class BasicHttpCache implements HttpCache {
         this(CacheConfig.DEFAULT);
     }
 
-    @Override
-    public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
-        final String root = cacheKeyGenerator.generateKey(host, request);
-        if (cacheEntry != null && cacheEntry.isVariantRoot()) {
-            final List<String> variantNames = CacheKeyGenerator.variantNames(cacheEntry);
-            if (!variantNames.isEmpty()) {
-                final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
-                return variantKey + root;
+    void storeInternal(final String cacheKey, final HttpCacheEntry entry) {
+        try {
+            storage.putEntry(cacheKey, entry);
+        } catch (final ResourceIOException ex) {
+            if (LOG.isWarnEnabled()) {
+                LOG.warn("I/O error storing cache entry with key {}", cacheKey);
             }
         }
-        return root;
     }
 
-    @Override
-    public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) {
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request));
+    void updateInternal(final String cacheKey, final HttpCacheCASOperation casOperation) {
+        try {
+            storage.updateEntry(cacheKey, casOperation);
+        } catch (final HttpCacheUpdateException ex) {
+            if (LOG.isWarnEnabled()) {
+                LOG.warn("Cannot update cache entry with key {}", cacheKey);
+            }
+        } catch (final ResourceIOException ex) {
+            if (LOG.isWarnEnabled()) {
+                LOG.warn("I/O error updating cache entry with key {}", cacheKey);
+            }
         }
-        if (!Method.isSafe(request.getMethod())) {
-            final String rootKey = cacheKeyGenerator.generateKey(host, request);
-            try {
-                storage.removeEntry(rootKey);
-            } catch (final ResourceIOException ex) {
-                if (LOG.isWarnEnabled()) {
-                    LOG.warn("I/O error removing cache entry with key {}", rootKey);
-                }
+    }
+
+    HttpCacheEntry getInternal(final String cacheKey) {
+        try {
+            return storage.getEntry(cacheKey);
+        } catch (final ResourceIOException ex) {
+            if (LOG.isWarnEnabled()) {
+                LOG.warn("I/O error retrieving cache entry with key {}", cacheKey);
             }
+            return null;
         }
     }
 
     @Override
-    public void flushCacheEntriesInvalidatedByRequest(final HttpHost host, final HttpRequest request) {
+    public CacheMatch match(final HttpHost host, final HttpRequest request) {
+        final String rootKey = cacheKeyGenerator.generateKey(host, request);
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
+            LOG.debug("Get cache root entry: {}", rootKey);
+        }
+        final HttpCacheEntry root = getInternal(rootKey);
+        if (root == null) {
+            return null;
+        }
+        if (root.isVariantRoot()) {
+            final List<String> variantNames = CacheKeyGenerator.variantNames(root);
+            final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
+            final String cacheKey = root.getVariantMap().get(variantKey);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Get cache variant entry: {}", cacheKey);
+            }
+            if (cacheKey != null) {
+                final HttpCacheEntry entry = getInternal(cacheKey);
+                if (entry != null) {
+                    return new CacheMatch(new CacheHit(rootKey, cacheKey, entry), new CacheHit(rootKey, root));
+                }
+            }
+            return new CacheMatch(null, new CacheHit(rootKey, root));
+        } else {
+            return new CacheMatch(new CacheHit(rootKey, root), null);
         }
-        cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage);
     }
 
     @Override
-    public void flushCacheEntriesInvalidatedByExchange(final HttpHost host, final HttpRequest request, final HttpResponse response) {
+    public List<CacheHit> getVariants(final CacheHit hit) {
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
+            LOG.debug("Get variant cache entries: {}", hit.rootKey);
         }
-        if (!Method.isSafe(request.getMethod())) {
-            cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage);
+        final HttpCacheEntry root = hit.entry;
+        if (root != null && root.isVariantRoot()) {
+            final List<CacheHit> variants = new ArrayList<>();
+            for (final String variantKey : root.getVariantMap().values()) {
+                final HttpCacheEntry variant = getInternal(variantKey);
+                if (variant != null) {
+                    variants.add(new CacheHit(hit.rootKey, variantKey, variant));
+                }
+            }
+            return variants;
         }
+        return Collections.emptyList();
     }
 
-    void storeInCache(
+    CacheHit store(
             final HttpRequest request,
             final HttpResponse originResponse,
             final Instant requestSent,
@@ -148,23 +185,17 @@ class BasicHttpCache implements HttpCache {
             final String rootKey,
             final HttpCacheEntry entry) {
         if (entry.hasVariants()) {
-            storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry);
+            return storeVariant(request, originResponse, requestSent, responseReceived, rootKey, entry);
         } else {
-            storeEntry(rootKey, entry);
-        }
-    }
-
-    void storeEntry(final String cacheKey, final HttpCacheEntry entry) {
-        try {
-            storage.putEntry(cacheKey, entry);
-        } catch (final ResourceIOException ex) {
-            if (LOG.isWarnEnabled()) {
-                LOG.warn("I/O error storing cache entry with key {}", cacheKey);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Put entry in cache: {}", rootKey);
             }
+            storeInternal(rootKey, entry);
+            return new CacheHit(rootKey, entry);
         }
     }
 
-    void storeVariantEntry(
+    CacheHit storeVariant(
             final HttpRequest request,
             final HttpResponse originResponse,
             final Instant requestSent,
@@ -173,185 +204,146 @@ class BasicHttpCache implements HttpCache {
             final HttpCacheEntry entry) {
         final List<String> variantNames = CacheKeyGenerator.variantNames(entry);
         final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
-        final String variantCacheKey = variantKey + request;
-        storeEntry(variantCacheKey, entry);
-        try {
-            storage.updateEntry(rootKey, existing -> {
-                        final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>();
-                        variantMap.put(variantKey, variantCacheKey);
-                        return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap);
-                    });
-        } catch (final HttpCacheUpdateException ex) {
-            if (LOG.isWarnEnabled()) {
-                LOG.warn("Cannot update cache entry with key {}", rootKey);
-            }
-        } catch (final ResourceIOException ex) {
-            if (LOG.isWarnEnabled()) {
-                LOG.warn("I/O error updating cache entry with key {}", rootKey);
-            }
+        final String variantCacheKey = variantKey + rootKey;
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Put variant entry in cache: {}", variantCacheKey);
+        }
+
+        storeInternal(variantCacheKey, entry);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Update root entry: {}", rootKey);
         }
+
+        updateInternal(rootKey, existing -> {
+            final Map<String, String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>();
+            variantMap.put(variantKey, variantCacheKey);
+            return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap);
+        });
+        return new CacheHit(rootKey, variantCacheKey, entry);
     }
 
     @Override
-    public void reuseVariantEntryFor(
+    public CacheHit store(
             final HttpHost host,
             final HttpRequest request,
             final HttpResponse originResponse,
-            final HttpCacheEntry entry,
+            final ByteArrayBuffer content,
             final Instant requestSent,
             final Instant responseReceived) {
+        final String rootKey = cacheKeyGenerator.generateKey(host, request);
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), entry);
+            LOG.debug("Store cache entry: {}", rootKey);
         }
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
-        storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry);
+        final Resource resource;
+        try {
+            resource = content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null;
+        } catch (final ResourceIOException ex) {
+            if (LOG.isWarnEnabled()) {
+                LOG.warn("I/O error creating cache entry with key {}", rootKey);
+            }
+            final HttpCacheEntry backup = cacheEntryFactory.create(
+                    requestSent,
+                    responseReceived,
+                    request,
+                    originResponse,
+                    content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
+            return new CacheHit(rootKey, backup);
+        }
+        final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse, resource);
+        return store(request, originResponse, requestSent, responseReceived, rootKey, entry);
     }
 
     @Override
-    public HttpCacheEntry updateEntry(
-            final HttpHost host,
+    public CacheHit update(
+            final CacheHit stale,
             final HttpRequest request,
-            final HttpCacheEntry stale,
             final HttpResponse originResponse,
             final Instant requestSent,
             final Instant responseReceived) {
+        final String entryKey = stale.getEntryKey();
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
+            LOG.debug("Update cache entry: {}", entryKey);
         }
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
         final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
                 requestSent,
                 responseReceived,
                 originResponse,
-                stale);
-        storeInCache(request, originResponse, requestSent, responseReceived, rootKey, updatedEntry);
-        return updatedEntry;
+                stale.entry);
+        return store(request, originResponse, requestSent, responseReceived, entryKey, updatedEntry);
     }
 
     @Override
-    public HttpCacheEntry updateVariantEntry(
-            final HttpHost host,
-            final HttpRequest request,
+    public CacheHit update(
+            final CacheHit stale,
             final HttpResponse originResponse,
-            final HttpCacheEntry entry,
             final Instant requestSent,
             final Instant responseReceived) {
+        final String entryKey = stale.getEntryKey();
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), entry);
+            LOG.debug("Update cache entry (no root)): {}", entryKey);
         }
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
         final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
                 requestSent,
                 responseReceived,
                 originResponse,
-                entry);
-        storeEntry(rootKey, updatedEntry);
-        return updatedEntry;
+                stale.entry);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Put entry in cache: {}", entryKey);
+        }
+
+        storeInternal(entryKey, updatedEntry);
+        return new CacheHit(stale.rootKey, stale.variantKey, updatedEntry);
     }
 
     @Override
-    public HttpCacheEntry createEntry(
+    public CacheHit storeReusing(
+            final CacheHit hit,
             final HttpHost host,
             final HttpRequest request,
             final HttpResponse originResponse,
-            final ByteArrayBuffer content,
             final Instant requestSent,
             final Instant responseReceived) {
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request));
-        }
         final String rootKey = cacheKeyGenerator.generateKey(host, request);
-        try {
-            final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse,
-                    content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null);
-            storeInCache(request, originResponse, requestSent, responseReceived, rootKey, entry);
-            return entry;
-        } catch (final ResourceIOException ex) {
-            if (LOG.isWarnEnabled()) {
-                LOG.warn("I/O error creating cache entry with key {}", rootKey);
-            }
-            return cacheEntryFactory.create(
-                    requestSent,
-                    responseReceived,
-                    request,
-                    originResponse,
-                    content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Store cache entry using existing entry: {} -> {}", rootKey, hit.rootKey);
         }
+        return store(request, originResponse, requestSent, responseReceived, rootKey, hit.entry);
     }
 
     @Override
-    public HttpCacheEntry getCacheEntry(final HttpHost host, final HttpRequest request) {
+    public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) {
+        final String rootKey = cacheKeyGenerator.generateKey(host, request);
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request));
+            LOG.debug("Flush cache entries: {}", rootKey);
         }
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
-        final HttpCacheEntry root;
         try {
-            root = storage.getEntry(rootKey);
+            storage.removeEntry(rootKey);
         } catch (final ResourceIOException ex) {
             if (LOG.isWarnEnabled()) {
-                LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
-            }
-            return null;
-        }
-        if (root == null) {
-            return null;
-        }
-        if (root.isVariantRoot()) {
-            final List<String> variantNames = CacheKeyGenerator.variantNames(root);
-            final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
-            final String variantCacheKey = root.getVariantMap().get(variantKey);
-            if (variantCacheKey != null) {
-                try {
-                    return storage.getEntry(variantCacheKey);
-                } catch (final ResourceIOException ex) {
-                    if (LOG.isWarnEnabled()) {
-                        LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
-                    }
-                }
+                LOG.warn("I/O error removing cache entry with key {}", rootKey);
             }
-            return null;
-        } else {
-            return root;
         }
     }
 
     @Override
-    public Map<String, Variant> getVariantCacheEntriesWithEtags(final HttpHost host, final HttpRequest request) {
+    public void flushCacheEntriesInvalidatedByRequest(final HttpHost host, final HttpRequest request) {
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request));
+            LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
         }
-        final Map<String,Variant> variants = new HashMap<>();
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
-        final HttpCacheEntry root;
-        try {
-            root = storage.getEntry(rootKey);
-        } catch (final ResourceIOException ex) {
-            if (LOG.isWarnEnabled()) {
-                LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
-            }
-            return variants;
+        cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage);
+    }
+
+    @Override
+    public void flushCacheEntriesInvalidatedByExchange(final HttpHost host, final HttpRequest request, final HttpResponse response) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
         }
-        if (root != null && root.isVariantRoot()) {
-            for(final Map.Entry<String, String> variant : root.getVariantMap().entrySet()) {
-                final String variantCacheKey = variant.getValue();
-                try {
-                    final HttpCacheEntry entry = storage.getEntry(variantCacheKey);
-                    if (entry != null) {
-                        final Header etagHeader = entry.getFirstHeader(HttpHeaders.ETAG);
-                        if (etagHeader != null) {
-                            variants.put(etagHeader.getValue(), new Variant(variantCacheKey, entry));
-                        }
-                    }
-                } catch (final ResourceIOException ex) {
-                    if (LOG.isWarnEnabled()) {
-                        LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
-                    }
-                    return variants;
-                }
-            }
+        if (!Method.isSafe(request.getMethod())) {
+            cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage);
         }
-        return variants;
     }
 
 }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheHit.java
similarity index 68%
copy from httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java
copy to httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheHit.java
index c5fd4c1ca..2c96df0d2 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheHit.java
@@ -28,28 +28,33 @@ package org.apache.hc.client5.http.impl.cache;
 
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 
-/** Records a set of information describing a cached variant. */
-class Variant {
+class CacheHit {
 
-    private final String cacheKey;
-    private final HttpCacheEntry entry;
+    final String rootKey;
+    final String variantKey;
+    final HttpCacheEntry entry;
 
-    public Variant(final String cacheKey, final HttpCacheEntry entry) {
-        this.cacheKey = cacheKey;
+    public CacheHit(final String rootKey, final String variantKey, final HttpCacheEntry entry) {
+        this.rootKey = rootKey;
+        this.variantKey = variantKey;
         this.entry = entry;
     }
 
-    public String getCacheKey() {
-        return cacheKey;
+    public CacheHit(final String rootKey, final HttpCacheEntry entry) {
+        this(rootKey, null, entry);
     }
 
-    public HttpCacheEntry getEntry() {
-        return entry;
+    public String getEntryKey() {
+        return variantKey != null ? variantKey : rootKey;
     }
 
     @Override
     public String toString() {
-        return cacheKey;
+        return "CacheHit{" +
+                "rootKey='" + rootKey + '\'' +
+                ", variantKey='" + variantKey + '\'' +
+                ", entry=" + entry +
+                '}';
     }
 
 }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheMatch.java
similarity index 71%
rename from httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java
rename to httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheMatch.java
index c5fd4c1ca..35e84a4b6 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheMatch.java
@@ -26,30 +26,26 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
-import org.apache.hc.client5.http.cache.HttpCacheEntry;
-
-/** Records a set of information describing a cached variant. */
-class Variant {
-
-    private final String cacheKey;
-    private final HttpCacheEntry entry;
-
-    public Variant(final String cacheKey, final HttpCacheEntry entry) {
-        this.cacheKey = cacheKey;
-        this.entry = entry;
-    }
+/**
+ * Represents a full match or a partial match (variant options)
+ * result of a cache lookup operation.
+ */
+class CacheMatch {
 
-    public String getCacheKey() {
-        return cacheKey;
-    }
+    final CacheHit hit;
+    final CacheHit root;
 
-    public HttpCacheEntry getEntry() {
-        return entry;
+    CacheMatch(final CacheHit hit, final CacheHit root) {
+        this.hit = hit;
+        this.root = root;
     }
 
     @Override
     public String toString() {
-        return cacheKey;
+        return "CacheLookupResult{" +
+                "hit=" + hit +
+                ", root=" + root +
+                '}';
     }
 
 }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
index 360dfd077..293d7e584 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
@@ -64,7 +64,7 @@ class CachedHttpResponseGenerator {
      * @return {@link SimpleHttpResponse} constructed response
      */
     SimpleHttpResponse generateResponse(final HttpRequest request, final HttpCacheEntry entry) throws ResourceIOException {
-        final Instant now =Instant.now();
+        final Instant now = Instant.now();
         final SimpleHttpResponse response = new SimpleHttpResponse(entry.getStatus());
         response.setVersion(HttpVersion.DEFAULT);
 
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 869db843a..f76785db3 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
@@ -29,7 +29,9 @@ package org.apache.hc.client5.http.impl.cache;
 import java.io.IOException;
 import java.io.InputStream;
 import java.time.Instant;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ScheduledExecutorService;
 
@@ -37,7 +39,6 @@ import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.async.methods.SimpleBody;
 import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
 import org.apache.hc.client5.http.cache.CacheResponseStatus;
-import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.HttpCacheStorage;
 import org.apache.hc.client5.http.cache.ResourceIOException;
 import org.apache.hc.client5.http.classic.ExecChain;
@@ -56,6 +57,7 @@ import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
@@ -165,9 +167,11 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
             return new BasicClassicHttpResponse(HttpStatus.SC_NOT_IMPLEMENTED);
         }
-        final HttpCacheEntry entry = responseCache.getCacheEntry(target, request);
+        final CacheMatch result = responseCache.match(target, request);
+        final CacheHit hit = result != null ? result.hit : null;
+        final CacheHit root = result != null ? result.root : null;
 
-        final SimpleHttpResponse fatalErrorResponse = getFatallyNonCompliantResponse(request, context, entry != null);
+        final SimpleHttpResponse fatalErrorResponse = getFatallyNonCompliantResponse(request, context, hit != null);
         if (fatalErrorResponse != null) {
             return convert(fatalErrorResponse, scope);
         }
@@ -183,12 +187,12 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
         }
 
 
-        if (entry == null) {
+        if (hit == null) {
             LOG.debug("Cache miss");
-            return handleCacheMiss(target, request, requestCacheControl, scope, chain);
+            return handleCacheMiss(requestCacheControl, root, target, request, scope, chain);
         } else {
-            final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
-            return handleCacheHit(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
+            final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(hit.entry);
+            return handleCacheHit(requestCacheControl, responseCacheControl, hit, target, request, scope, chain);
         }
     }
 
@@ -238,11 +242,11 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
     private ClassicHttpResponse handleCacheHit(
             final RequestCacheControl requestCacheControl,
             final ResponseCacheControl responseCacheControl,
+            final CacheHit hit,
             final HttpHost target,
             final ClassicHttpRequest request,
             final ExecChain.Scope scope,
-            final ExecChain chain,
-            final HttpCacheEntry entry) throws IOException, HttpException {
+            final ExecChain chain) throws IOException, HttpException {
         final HttpClientContext context  = scope.clientContext;
         context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
         recordCacheHit(target, request);
@@ -250,20 +254,20 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
 
         if (requestCacheControl.isNoCache()) {
             // Revalidate with the server
-            return revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
+            return revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, scope, chain);
         }
 
-        if (suitabilityChecker.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now)) {
-            if (responseCachingPolicy.responseContainsNoCacheDirective(responseCacheControl, entry)) {
+        if (suitabilityChecker.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, hit.entry, now)) {
+            if (responseCachingPolicy.responseContainsNoCacheDirective(responseCacheControl, hit.entry)) {
                 // Revalidate with the server due to no-cache directive in response
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("Revalidating with server due to no-cache directive in response.");
                 }
-                return revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
+                return revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, scope, chain);
             }
             LOG.debug("Cache hit");
             try {
-                return convert(generateCachedResponse(responseCacheControl, request, context, entry, now), scope);
+                return convert(generateCachedResponse(responseCacheControl, hit.entry, request, context, now), scope);
             } catch (final ResourceIOException ex) {
                 recordCacheFailure(target, request);
                 if (!mayCallBackend(requestCacheControl)) {
@@ -275,13 +279,13 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
         } else if (!mayCallBackend(requestCacheControl)) {
             LOG.debug("Cache entry not suitable but only-if-cached requested");
             return convert(generateGatewayTimeout(context), scope);
-        } else if (!(entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
+        } else if (!(hit.entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
             LOG.debug("Revalidating cache entry");
-            final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, entry);
+            final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, hit.entry);
             try {
                 if (cacheRevalidator != null
-                        && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)
-                        && (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now) || staleIfErrorEnabled)) {
+                        && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, now)
+                        && (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, hit.entry, now) || staleIfErrorEnabled)) {
                     LOG.debug("Serving stale with asynchronous revalidation");
                     final String exchangeId = ExecSupport.getNextExchangeId();
                     context.setExchangeId(exchangeId);
@@ -291,21 +295,21 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
                             scope.originalRequest,
                             scope.execRuntime.fork(null),
                             HttpClientContext.create());
-                    final SimpleHttpResponse response = generateCachedResponse(responseCacheControl, request, context, entry, now);
+                    final SimpleHttpResponse response = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
                     cacheRevalidator.revalidateCacheEntry(
-                            responseCache.generateKey(target, request, entry),
-                            () -> revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, fork, chain, entry));
+                            hit.getEntryKey(),
+                            () -> revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, fork, chain));
                     return convert(response, scope);
                 }
-                return revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
+                return revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, scope, chain);
             } catch (final IOException ioex) {
                 if (staleIfErrorEnabled) {
                     if (LOG.isDebugEnabled()) {
                         LOG.debug("Serving stale response due to IOException and stale-if-error enabled");
                     }
-                    return convert(generateCachedResponse(responseCacheControl, request, context, entry, now), scope);
+                    return convert(generateCachedResponse(responseCacheControl, hit.entry, request, context, now), scope);
                 }
-                return convert(handleRevalidationFailure(requestCacheControl, responseCacheControl, request, context, entry, now), scope);
+                return convert(handleRevalidationFailure(requestCacheControl, responseCacheControl, hit.entry, request, context, now), scope);
             }
         } else {
             LOG.debug("Cache entry not usable; calling backend");
@@ -316,20 +320,20 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
     ClassicHttpResponse revalidateCacheEntry(
             final RequestCacheControl requestCacheControl,
             final ResponseCacheControl responseCacheControl,
+            final CacheHit hit,
             final HttpHost target,
             final ClassicHttpRequest request,
             final ExecChain.Scope scope,
-            final ExecChain chain,
-            final HttpCacheEntry cacheEntry) throws IOException, HttpException {
+            final ExecChain chain) throws IOException, HttpException {
         Instant requestDate = getCurrentDate();
         final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(
-                responseCacheControl, scope.originalRequest, cacheEntry);
+                responseCacheControl, scope.originalRequest, hit.entry);
 
         ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
         try {
             Instant responseDate = getCurrentDate();
 
-            if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) {
+            if (revalidationResponseIsTooOld(backendResponse, hit.entry)) {
                 backendResponse.close();
                 final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
                         scope.originalRequest);
@@ -346,20 +350,19 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             }
 
             if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
-                final HttpCacheEntry updatedEntry = responseCache.updateEntry(
-                        target, request, cacheEntry, backendResponse, requestDate, responseDate);
+                final CacheHit updated = responseCache.update(hit, request, backendResponse, requestDate, responseDate);
                 if (suitabilityChecker.isConditional(request)
-                        && suitabilityChecker.allConditionalsMatch(request, updatedEntry, Instant.now())) {
-                    return convert(responseGenerator.generateNotModifiedResponse(updatedEntry), scope);
+                        && suitabilityChecker.allConditionalsMatch(request, updated.entry, Instant.now())) {
+                    return convert(responseGenerator.generateNotModifiedResponse(updated.entry), scope);
                 }
-                return convert(responseGenerator.generateResponse(request, updatedEntry), scope);
+                return convert(responseGenerator.generateResponse(request, updated.entry), scope);
             }
 
             if (staleIfErrorAppliesTo(statusCode)
-                    && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, cacheEntry, getCurrentDate())
-                    && validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, cacheEntry, responseDate)) {
+                    && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())
+                    && validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, hit.entry, responseDate)) {
                 try {
-                    final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, cacheEntry);
+                    final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, hit.entry);
                     cachedResponse.addHeader(HttpHeaders.WARNING, "110 localhost \"Response is stale\"");
                     return convert(cachedResponse, scope);
                 } finally {
@@ -391,7 +394,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             return cacheAndReturnResponse(target, request, backendResponse, scope, requestDate, responseDate);
         }
         LOG.debug("Backend response is not cacheable");
-        responseCache.flushCacheEntriesFor(target, request);
+        if (!Method.isSafe(request.getMethod())) {
+            responseCache.flushCacheEntriesFor(target, request);
+        }
         return backendResponse;
     }
 
@@ -406,16 +411,16 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
 
         // handle 304 Not Modified responses
         if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
-            final HttpCacheEntry existingEntry = responseCache.getCacheEntry(target, request);
-            if (existingEntry != null) {
-                final HttpCacheEntry updatedEntry = responseCache.updateEntry(
-                        target,
+            final CacheMatch result = responseCache.match(target ,request);
+            final CacheHit hit = result != null ? result.hit : null;
+            if (hit != null) {
+                final CacheHit updated = responseCache.update(
+                        hit,
                         request,
-                        existingEntry,
                         backendResponse,
                         requestSent,
                         responseReceived);
-                return convert(responseGenerator.generateResponse(request, updatedEntry), scope);
+                return convert(responseGenerator.generateResponse(request, updated.entry), scope);
             }
         }
 
@@ -441,27 +446,28 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
         }
         backendResponse.close();
 
-        final HttpCacheEntry cacheEntry;
+        CacheHit hit;
         if (cacheConfig.isFreshnessCheckEnabled()) {
-            final HttpCacheEntry existingEntry = responseCache.getCacheEntry(target, request);
-            if (DateSupport.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) {
+            final CacheMatch result = responseCache.match(target ,request);
+            hit = result != null ? result.hit : null;
+            if (DateSupport.isAfter(hit != null ? hit.entry : null, backendResponse, HttpHeaders.DATE)) {
                 LOG.debug("Backend already contains fresher cache entry");
-                cacheEntry = existingEntry;
             } else {
-                cacheEntry = responseCache.createEntry(target, request, backendResponse, buf, requestSent, responseReceived);
+                hit = responseCache.store(target, request, backendResponse, buf, requestSent, responseReceived);
                 LOG.debug("Backend response successfully cached");
             }
         } else {
-            cacheEntry = responseCache.createEntry(target, request, backendResponse, buf, requestSent, responseReceived);
+            hit = responseCache.store(target, request, backendResponse, buf, requestSent, responseReceived);
             LOG.debug("Backend response successfully cached (freshness check skipped)");
         }
-        return convert(responseGenerator.generateResponse(request, cacheEntry), scope);
+        return convert(responseGenerator.generateResponse(request, hit.entry), scope);
     }
 
     private ClassicHttpResponse handleCacheMiss(
+            final RequestCacheControl requestCacheControl,
+            final CacheHit partialMatch,
             final HttpHost target,
             final ClassicHttpRequest request,
-            final RequestCacheControl requestCacheControl,
             final ExecChain.Scope scope,
             final ExecChain chain) throws IOException, HttpException {
         recordCacheMiss(target, request);
@@ -469,10 +475,11 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
         if (!mayCallBackend(requestCacheControl)) {
             return new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
         }
-
-        final Map<String, Variant> variants = responseCache.getVariantCacheEntriesWithEtags(target, request);
-        if (variants != null && !variants.isEmpty()) {
-            return negotiateResponseFromVariants(target, request, scope, chain, variants);
+        if (partialMatch != null && partialMatch.entry.isVariantRoot()) {
+            final List<CacheHit> variants = responseCache.getVariants(partialMatch);
+            if (variants != null && !variants.isEmpty()) {
+                return negotiateResponseFromVariants(target, request, scope, chain, variants);
+            }
         }
 
         return callBackend(target, request, scope, chain);
@@ -483,8 +490,16 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             final ClassicHttpRequest request,
             final ExecChain.Scope scope,
             final ExecChain chain,
-            final Map<String, Variant> variants) throws IOException, HttpException {
-        final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(request, variants);
+            final List<CacheHit> variants) throws IOException, HttpException {
+        final Map<String, CacheHit> variantMap = new HashMap<>();
+        for (final CacheHit variant : variants) {
+            final Header header = variant.entry.getFirstHeader(HttpHeaders.ETAG);
+            if (header != null) {
+                variantMap.put(header.getValue(), variant);
+            }
+        }
+
+        final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(request, variantMap.keySet());
 
         final Instant requestDate = getCurrentDate();
         final ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
@@ -507,15 +522,13 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
             }
 
             final String resultEtag = resultEtagHeader.getValue();
-            final Variant matchingVariant = variants.get(resultEtag);
-            if (matchingVariant == null) {
+            final CacheHit match = variantMap.get(resultEtag);
+            if (match == null) {
                 LOG.debug("304 response did not contain ETag matching one sent in If-None-Match");
                 return callBackend(target, request, scope, chain);
             }
 
-            final HttpCacheEntry variantEntry = matchingVariant.getEntry();
-
-            if (revalidationResponseIsTooOld(backendResponse, variantEntry)
+            if (revalidationResponseIsTooOld(backendResponse, match.entry)
                     && (request.getEntity() == null || request.getEntity().isRepeatable())) {
                 final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);
                 return callBackend(target, unconditional, scope, chain);
@@ -523,13 +536,12 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
 
             recordCacheUpdate(scope.clientContext);
 
-            final HttpCacheEntry responseEntry = responseCache.updateVariantEntry(
-                    target, conditionalRequest, backendResponse, variantEntry, requestDate, responseDate);
-            if (shouldSendNotModifiedResponse(request, responseEntry)) {
-                return convert(responseGenerator.generateNotModifiedResponse(responseEntry), scope);
+            final CacheHit hit = responseCache.update(match, backendResponse, requestDate, responseDate);
+            if (shouldSendNotModifiedResponse(request, hit.entry)) {
+                return convert(responseGenerator.generateNotModifiedResponse(hit.entry), scope);
             }
-            final SimpleHttpResponse response = responseGenerator.generateResponse(request, responseEntry);
-            responseCache.reuseVariantEntryFor(target, request, backendResponse, responseEntry, requestDate, responseDate);
+            final SimpleHttpResponse response = responseGenerator.generateResponse(request, hit.entry);
+            responseCache.storeReusing(hit, target, request, backendResponse, requestDate, responseDate);
             return convert(response, scope);
         } catch (final IOException | RuntimeException ex) {
             backendResponse.close();
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
index 41aaafdbe..af8d4ea10 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
@@ -185,9 +185,9 @@ public class CachingExecBase {
 
     SimpleHttpResponse generateCachedResponse(
             final ResponseCacheControl responseCacheControl,
+            final HttpCacheEntry entry,
             final HttpRequest request,
             final HttpContext context,
-            final HttpCacheEntry entry,
             final Instant now) throws ResourceIOException {
         final SimpleHttpResponse cachedResponse;
         if (request.containsHeader(HttpHeaders.IF_NONE_MATCH)
@@ -223,9 +223,9 @@ public class CachingExecBase {
     SimpleHttpResponse handleRevalidationFailure(
             final RequestCacheControl requestCacheControl,
             final ResponseCacheControl responseCacheControl,
+            final HttpCacheEntry entry,
             final HttpRequest request,
             final HttpContext context,
-            final HttpCacheEntry entry,
             final Instant now) throws IOException {
         if (staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)) {
             return generateGatewayTimeout(context);
@@ -259,8 +259,8 @@ public class CachingExecBase {
             || explicitFreshnessRequest(requestCacheControl, responseCacheControl, entry, now);
     }
 
-    boolean mayCallBackend(final RequestCacheControl cacheControl) {
-        if (cacheControl.isOnlyIfCached()) {
+    boolean mayCallBackend(final RequestCacheControl requestCacheControl) {
+        if (requestCacheControl.isOnlyIfCached()) {
             LOG.debug("Request marked only-if-cached");
             return false;
         }
@@ -268,7 +268,8 @@ public class CachingExecBase {
     }
 
     boolean explicitFreshnessRequest(final RequestCacheControl requestCacheControl,
-                                     final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry,
+                                     final ResponseCacheControl responseCacheControl,
+                                     final HttpCacheEntry entry,
                                      final Instant now) {
         if (requestCacheControl.getMaxStale() >= 0) {
             final TimeValue age = validityPolicy.getCurrentAge(entry, now);
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ConditionalRequestBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ConditionalRequestBuilder.java
index eb2a67f93..22562fbfa 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ConditionalRequestBuilder.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ConditionalRequestBuilder.java
@@ -26,7 +26,7 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
-import java.util.Map;
+import java.util.Set;
 
 import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
@@ -84,9 +84,9 @@ class ConditionalRequestBuilder<T extends HttpRequest> {
      * @param variants
      * @return the wrapped request
      */
-    public T buildConditionalRequestFromVariants(final T request, final Map<String, Variant> variants) {
+    public T buildConditionalRequestFromVariants(final T request, final Set<String> variants) {
         final T newRequest = messageCopier.create(request);
-        newRequest.setHeader(MessageSupport.format(HttpHeaders.IF_NONE_MATCH, variants.keySet()));
+        newRequest.setHeader(MessageSupport.format(HttpHeaders.IF_NONE_MATCH, variants));
         return newRequest;
     }
 
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java
index f4d0bba59..ba59f83d4 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java
@@ -27,7 +27,7 @@
 package org.apache.hc.client5.http.impl.cache;
 
 import java.time.Instant;
-import java.util.Map;
+import java.util.Collection;
 
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.core5.concurrent.Cancellable;
@@ -39,85 +39,81 @@ import org.apache.hc.core5.util.ByteArrayBuffer;
 
 interface HttpAsyncCache {
 
-    String generateKey (HttpHost host, HttpRequest request, HttpCacheEntry cacheEntry);
-
     /**
-     * Clear all matching {@link HttpCacheEntry}s.
+     * Returns a result with either a fully matching {@link HttpCacheEntry}
+     * a partial match with a list of known variants or null if no match could be found.
      */
-    Cancellable flushCacheEntriesFor(
-            HttpHost host, HttpRequest request, FutureCallback<Boolean> callback);
+    Cancellable match(HttpHost host, HttpRequest request, FutureCallback<CacheMatch> callback);
 
     /**
-     * Flush {@link HttpCacheEntry}s invalidated by the given request
+     * Retrieves variant {@link HttpCacheEntry}s for the given hit.
      */
-    Cancellable flushCacheEntriesInvalidatedByRequest(
-            HttpHost host, HttpRequest request, FutureCallback<Boolean> callback);
+    Cancellable getVariants(
+            CacheHit hit, FutureCallback<Collection<CacheHit>> callback);
 
     /**
-     * Flush {@link HttpCacheEntry}s invalidated by the given message exchange.
+     * Stores {@link HttpRequest} / {@link HttpResponse} exchange details in the cache.
      */
-    Cancellable flushCacheEntriesInvalidatedByExchange(
-            HttpHost host, HttpRequest request, HttpResponse response, FutureCallback<Boolean> callback);
-
-    /**
-     * Retrieve matching {@link HttpCacheEntry} from the cache if it exists
-     */
-    Cancellable getCacheEntry(
-            HttpHost host, HttpRequest request, FutureCallback<HttpCacheEntry> callback);
-
-    /**
-     * Retrieve all variants from the cache, if there are no variants then an empty
-     */
-    Cancellable getVariantCacheEntriesWithEtags(
-            HttpHost host, HttpRequest request, FutureCallback<Map<String,Variant>> callback);
-
-    /**
-     * Store a {@link HttpResponse} in the cache if possible, and return
-     */
-    Cancellable createEntry(
+    Cancellable store(
             HttpHost host,
             HttpRequest request,
             HttpResponse originResponse,
             ByteArrayBuffer content,
             Instant requestSent,
             Instant responseReceived,
-            FutureCallback<HttpCacheEntry> callback);
+            FutureCallback<CacheHit> callback);
 
     /**
-     * Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}.
+     * Updates {@link HttpCacheEntry} using details from a 304 {@link HttpResponse} and
+     * updates the root entry if the given cache entry represents a variant.
      */
-    Cancellable updateEntry(
-            HttpHost host,
+    Cancellable update(
+            CacheHit stale,
             HttpRequest request,
-            HttpCacheEntry stale,
             HttpResponse originResponse,
             Instant requestSent,
             Instant responseReceived,
-            FutureCallback<HttpCacheEntry> callback);
+            FutureCallback<CacheHit> callback);
 
     /**
-     * Update a specific {@link HttpCacheEntry} representing a cached variant
-     * using a 304 {@link HttpResponse}.
+     * Updates {@link HttpCacheEntry} using details from a 304 {@link HttpResponse}.
      */
-    Cancellable updateVariantEntry(
-            HttpHost host,
-            HttpRequest request,
+    Cancellable update(
+            CacheHit stale,
             HttpResponse originResponse,
-            HttpCacheEntry entry,
             Instant requestSent,
             Instant responseReceived,
-            FutureCallback<HttpCacheEntry> callback);
+            FutureCallback<CacheHit> callback);
 
     /**
-     * Specifies cache should reuse the given cached variant to satisfy
-     * requests whose varying headers match those of the given client request.
+     * Stores {@link HttpRequest} / {@link HttpResponse} exchange details in the cache
+     * re-using the resource of the existing {@link HttpCacheEntry}.
      */
-    Cancellable reuseVariantEntryFor(
+    Cancellable storeReusing(
+            CacheHit hit,
             HttpHost host,
             HttpRequest request,
             HttpResponse originResponse,
-            HttpCacheEntry entry,
             Instant requestSent,
             Instant responseReceived,
-            FutureCallback<Boolean> callback);
+            FutureCallback<CacheHit> callback);
+
+    /**
+     * Clear all matching {@link HttpCacheEntry}s.
+     */
+    Cancellable flushCacheEntriesFor(
+            HttpHost host, HttpRequest request, FutureCallback<Boolean> callback);
+
+    /**
+     * Flush {@link HttpCacheEntry}s invalidated by the given request
+     */
+    Cancellable flushCacheEntriesInvalidatedByRequest(
+            HttpHost host, HttpRequest request, FutureCallback<Boolean> callback);
+
+    /**
+     * Flush {@link HttpCacheEntry}s invalidated by the given message exchange.
+     */
+    Cancellable flushCacheEntriesInvalidatedByExchange(
+            HttpHost host, HttpRequest request, HttpResponse response, FutureCallback<Boolean> callback);
+
 }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java
index 4747ca4d1..fa73d3e74 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java
@@ -27,7 +27,7 @@
 package org.apache.hc.client5.http.impl.cache;
 
 import java.time.Instant;
-import java.util.Map;
+import java.util.List;
 
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.core5.http.HttpHost;
@@ -37,38 +37,21 @@ import org.apache.hc.core5.util.ByteArrayBuffer;
 
 interface HttpCache {
 
-    String generateKey (HttpHost host, HttpRequest request, HttpCacheEntry cacheEntry);
-
-    /**
-     * Clear all matching {@link HttpCacheEntry}s.
-     */
-    void flushCacheEntriesFor(HttpHost host, HttpRequest request);
-
-    /**
-     * Flush {@link HttpCacheEntry}s invalidated by the given request
-     */
-    void flushCacheEntriesInvalidatedByRequest(HttpHost host, HttpRequest request);
-
     /**
-     * Flush {@link HttpCacheEntry}s invalidated by the given message exchange.
+     * Returns a result with either a fully matching {@link HttpCacheEntry}
+     * a partial match with a list of known variants or null if no match could be found.
      */
-    void flushCacheEntriesInvalidatedByExchange(HttpHost host, HttpRequest request, HttpResponse response);
+    CacheMatch match(HttpHost host, HttpRequest request);
 
     /**
-     * Retrieve matching {@link HttpCacheEntry} from the cache if it exists.
+     * Retrieves variant {@link HttpCacheEntry}s for the given hit.
      */
-    HttpCacheEntry getCacheEntry(HttpHost host, HttpRequest request);
+    List<CacheHit> getVariants(CacheHit hit);
 
     /**
-     * Retrieve all variants from the cache, if there are no variants then an empty
-     * {@link Map} is returned
+     * Stores {@link HttpRequest} / {@link HttpResponse} exchange details in the cache.
      */
-    Map<String,Variant> getVariantCacheEntriesWithEtags(HttpHost host, HttpRequest request);
-
-    /**
-     * Store a {@link HttpResponse} in the cache if possible, and return
-     */
-    HttpCacheEntry createEntry(
+    CacheHit store(
             HttpHost host,
             HttpRequest request,
             HttpResponse originResponse,
@@ -77,37 +60,50 @@ interface HttpCache {
             Instant responseReceived);
 
     /**
-     * Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}.
+     * Updates {@link HttpCacheEntry} using details from a 304 {@link HttpResponse} and
+     * updates the root entry if the given cache entry represents a variant.
      */
-    HttpCacheEntry updateEntry(
-            HttpHost host,
+    CacheHit update(
+            CacheHit stale,
             HttpRequest request,
-            HttpCacheEntry stale,
             HttpResponse originResponse,
             Instant requestSent,
             Instant responseReceived);
 
     /**
-     * Update a specific {@link HttpCacheEntry} representing a cached variant
-     * using a 304 {@link HttpResponse}.
+     * Updates {@link HttpCacheEntry} using details from a 304 {@link HttpResponse}.
      */
-    HttpCacheEntry updateVariantEntry(
-            HttpHost host,
-            HttpRequest request,
+    CacheHit update(
+            CacheHit stale,
             HttpResponse originResponse,
-            HttpCacheEntry entry,
             Instant requestSent,
             Instant responseReceived);
 
     /**
-     * Specifies cache should reuse the given cached variant to satisfy
-     * requests whose varying headers match those of the given client request.
+     * Stores {@link HttpRequest} / {@link HttpResponse} exchange details in the cache
+     * re-using the resource of the existing {@link HttpCacheEntry}.
      */
-    void reuseVariantEntryFor(
+    CacheHit storeReusing(
+            CacheHit hit,
             HttpHost host,
             HttpRequest request,
             HttpResponse originResponse,
-            HttpCacheEntry entry,
             Instant requestSent,
             Instant responseReceived);
+
+    /**
+     * Clear all matching {@link HttpCacheEntry}s.
+     */
+    void flushCacheEntriesFor(HttpHost host, HttpRequest request);
+
+    /**
+     * Flush {@link HttpCacheEntry}s invalidated by the given request
+     */
+    void flushCacheEntriesInvalidatedByRequest(HttpHost host, HttpRequest request);
+
+    /**
+     * Flush {@link HttpCacheEntry}s invalidated by the given message exchange.
+     */
+    void flushCacheEntriesInvalidatedByExchange(HttpHost host, HttpRequest request, HttpResponse response);
+
 }
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/ContainsHeaderMatcher.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/ContainsHeaderMatcher.java
index d7f786ce5..0ec9a53b9 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/ContainsHeaderMatcher.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/ContainsHeaderMatcher.java
@@ -61,7 +61,7 @@ public class ContainsHeaderMatcher extends BaseMatcher<MessageHeaders> {
 
     @Override
     public void describeTo(final Description description) {
-        description.appendText("contains header ").appendValue(headerValue).appendText(": ").appendValue(headerValue);
+        description.appendText("contains header ").appendValue(headerName).appendText(": ").appendValue(headerValue);
     }
 
     public static Matcher<MessageHeaders> contains(final String headerName, final Object headerValue) {
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java
index 2f1ca8494..82be89d68 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java
@@ -318,6 +318,11 @@ public class HttpTestUtils {
         return makeCacheEntry(requestDate, responseDate, Method.GET, "/", null, HttpStatus.SC_OK, headers, variantMap);
     }
 
+    public static HttpCacheEntry makeCacheEntry(final Map<String, String> variantMap) {
+        final Instant now = Instant.now();
+        return makeCacheEntry(now, now, new Header[] {}, variantMap);
+    }
+
     public static HttpCacheEntry makeCacheEntry(final Header[] headers, final byte[] bytes) {
         final Instant now = Instant.now();
         return makeCacheEntry(now, now, headers, bytes);
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java
index f89936bf1..24189bb3a 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java
@@ -34,15 +34,15 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 
 import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.classic.methods.HttpDelete;
 import org.apache.hc.client5.http.classic.methods.HttpGet;
-import org.apache.hc.client5.http.classic.methods.HttpHead;
-import org.apache.hc.client5.http.classic.methods.HttpOptions;
 import org.apache.hc.client5.http.classic.methods.HttpPost;
-import org.apache.hc.client5.http.classic.methods.HttpTrace;
 import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpHost;
@@ -52,6 +52,7 @@ import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.apache.hc.core5.http.message.BasicHttpResponse;
 import org.apache.hc.core5.util.ByteArrayBuffer;
+import org.hamcrest.MatcherAssert;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -67,64 +68,7 @@ public class TestBasicHttpCache {
     }
 
     @Test
-    public void testDoNotFlushCacheEntriesOnGet() throws Exception {
-        final HttpHost host = new HttpHost("foo.example.com");
-        final HttpRequest req = new HttpGet("/bar");
-        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
-        final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
-
-        backing.map.put(key, entry);
-
-        impl.flushCacheEntriesFor(host, req);
-
-        assertEquals(entry, backing.map.get(key));
-    }
-
-    @Test
-    public void testDoNotFlushCacheEntriesOnHead() throws Exception {
-        final HttpHost host = new HttpHost("foo.example.com");
-        final HttpRequest req = new HttpHead("/bar");
-        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
-        final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
-
-        backing.map.put(key, entry);
-
-        impl.flushCacheEntriesFor(host, req);
-
-        assertEquals(entry, backing.map.get(key));
-    }
-
-    @Test
-    public void testDoNotFlushCacheEntriesOnOptions() throws Exception {
-        final HttpHost host = new HttpHost("foo.example.com");
-        final HttpRequest req = new HttpOptions("/bar");
-        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
-        final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
-
-        backing.map.put(key, entry);
-
-        impl.flushCacheEntriesFor(host, req);
-
-        assertEquals(entry, backing.map.get(key));
-    }
-
-    @Test
-    public void testDoNotFlushCacheEntriesOnTrace() throws Exception {
-        final HttpHost host = new HttpHost("foo.example.com");
-        final HttpRequest req = new HttpTrace("/bar");
-        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
-        final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
-
-        backing.map.put(key, entry);
-
-        impl.flushCacheEntriesFor(host, req);
-
-        assertEquals(entry, backing.map.get(key));
-    }
-
-    @Test
-    public void testFlushContentLocationEntryIfUnSafeRequest()
-            throws Exception {
+    public void testFlushContentLocationEntryIfUnSafeRequest() throws Exception {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req = new HttpPost("/foo");
         final HttpResponse resp = HttpTestUtils.make200Response();
@@ -144,8 +88,7 @@ public class TestBasicHttpCache {
     }
 
     @Test
-    public void testDoNotFlushContentLocationEntryIfSafeRequest()
-            throws Exception {
+    public void testDoNotFlushContentLocationEntryIfSafeRequest() throws Exception {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req = new HttpGet("/foo");
         final HttpResponse resp = HttpTestUtils.make200Response();
@@ -187,7 +130,7 @@ public class TestBasicHttpCache {
 
         final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
 
-        impl.storeInCache(req, resp, Instant.now(), Instant.now(), key, entry);
+        impl.store(req, resp, Instant.now(), Instant.now(), key, entry);
         assertSame(entry, backing.map.get(key));
     }
 
@@ -195,7 +138,7 @@ public class TestBasicHttpCache {
     public void testGetCacheEntryReturnsNullOnCacheMiss() throws Exception {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest request = new HttpGet("http://foo.example.com/bar");
-        final HttpCacheEntry result = impl.getCacheEntry(host, request);
+        final CacheMatch result = impl.match(host, request);
         assertNull(result);
     }
 
@@ -210,8 +153,10 @@ public class TestBasicHttpCache {
 
         backing.map.put(key,entry);
 
-        final HttpCacheEntry result = impl.getCacheEntry(host, request);
-        assertSame(entry, result);
+        final CacheMatch result = impl.match(host, request);
+        assertNotNull(result);
+        assertNotNull(result.hit);
+        assertSame(entry, result.hit.entry);
     }
 
     @Test
@@ -229,11 +174,12 @@ public class TestBasicHttpCache {
         origResponse.setHeader("Vary", "Accept-Encoding");
         origResponse.setHeader("Content-Encoding","gzip");
 
-        impl.createEntry(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
+        impl.store(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
 
         final HttpRequest request = new HttpGet("http://foo.example.com/bar");
-        final HttpCacheEntry result = impl.getCacheEntry(host, request);
-        assertNull(result);
+        final CacheMatch result = impl.match(host, request);
+        assertNotNull(result);
+        assertNull(result.hit);
     }
 
     @Test
@@ -251,12 +197,13 @@ public class TestBasicHttpCache {
         origResponse.setHeader("Vary", "Accept-Encoding");
         origResponse.setHeader("Content-Encoding","gzip");
 
-        impl.createEntry(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
+        impl.store(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
 
         final HttpRequest request = new HttpGet("http://foo.example.com/bar");
         request.setHeader("Accept-Encoding","gzip");
-        final HttpCacheEntry result = impl.getCacheEntry(host, request);
+        final CacheMatch result = impl.match(host, request);
         assertNotNull(result);
+        assertNotNull(result.hit);
     }
 
     @Test
@@ -282,28 +229,41 @@ public class TestBasicHttpCache {
         origResponse2.setHeader(HttpHeaders.VARY, "Accept-Encoding");
 
         // Store the two variants in cache
-        impl.createEntry(host, origRequest, origResponse1, buf, Instant.now(), Instant.now());
-        impl.createEntry(host, origRequest, origResponse2, buf, Instant.now(), Instant.now());
+        impl.store(host, origRequest, origResponse1, buf, Instant.now(), Instant.now());
+        impl.store(host, origRequest, origResponse2, buf, Instant.now(), Instant.now());
 
         final HttpRequest request = new HttpGet("http://foo.example.com/bar");
         request.setHeader("Accept-Encoding", "gzip");
-        final HttpCacheEntry result = impl.getCacheEntry(host, request);
+        final CacheMatch result = impl.match(host, request);
         assertNotNull(result);
+        assertNotNull(result.hit);
+        final HttpCacheEntry entry = result.hit.entry;
+        assertNotNull(entry);
 
         // Retrieve the ETag header value from the original response and assert that
         // the returned cache entry has the same ETag value
         final String expectedEtag = origResponse2.getFirstHeader(HttpHeaders.ETAG).getValue();
-        final String actualEtag = result.getFirstHeader(HttpHeaders.ETAG).getValue();
+        final String actualEtag = entry.getFirstHeader(HttpHeaders.ETAG).getValue();
 
         assertEquals(expectedEtag, actualEtag);
     }
 
     @Test
-    public void testGetVariantCacheEntriesReturnsEmptySetOnNoVariants() throws Exception {
-        final HttpHost host = new HttpHost("foo.example.com");
-        final HttpRequest request = new HttpGet("http://foo.example.com/bar");
+    public void testGetVariantsRootNoVariants() throws Exception {
+        final HttpCacheEntry root = HttpTestUtils.makeCacheEntry();
+        final List<CacheHit> variants = impl.getVariants(new CacheHit("root-key", root));
 
-        final Map<String,Variant> variants = impl.getVariantCacheEntriesWithEtags(host, request);
+        assertNotNull(variants);
+        assertEquals(0, variants.size());
+    }
+
+    @Test
+    public void testGetVariantsRootNonExistentVariants() throws Exception {
+        final Map<String, String> variantMap = new HashMap<>();
+        variantMap.put("variant1", "variant-key-1");
+        variantMap.put("variant2", "variant-key-2");
+        final HttpCacheEntry root = HttpTestUtils.makeCacheEntry(variantMap);
+        final List<CacheHit> variants = impl.getVariants(new CacheHit("root-key", root));
 
         assertNotNull(variants);
         assertEquals(0, variants.size());
@@ -334,14 +294,21 @@ public class TestBasicHttpCache {
         resp2.setHeader("Content-Encoding","gzip");
         resp2.setHeader("Vary", "Accept-Encoding");
 
-        impl.createEntry(host, req1, resp1, null, Instant.now(), Instant.now());
-        impl.createEntry(host, req2, resp2, null, Instant.now(), Instant.now());
+        final CacheHit hit1 = impl.store(host, req1, resp1, null, Instant.now(), Instant.now());
+        final CacheHit hit2 = impl.store(host, req2, resp2, null, Instant.now(), Instant.now());
+
+        final Map<String, String> variantMap = new HashMap<>();
+        variantMap.put("variant-1", hit1.variantKey);
+        variantMap.put("variant-2", hit2.variantKey);
 
-        final Map<String,Variant> variants = impl.getVariantCacheEntriesWithEtags(host, req1);
+        final Map<String, HttpCacheEntry> variants = impl.getVariants(new CacheHit(hit1.rootKey,
+                HttpTestUtils.makeCacheEntry(variantMap))).stream()
+                        .collect(Collectors.toMap(CacheHit::getEntryKey, e -> e.entry));
 
         assertNotNull(variants);
         assertEquals(2, variants.size());
-
+        MatcherAssert.assertThat(variants.get(hit1.getEntryKey()), HttpCacheEntryMatcher.equivalent(hit1.entry));
+        MatcherAssert.assertThat(variants.get(hit2.getEntryKey()), HttpCacheEntryMatcher.equivalent(hit2.entry));
     }
 
 }
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
index cbfbca25e..ef39d990c 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
@@ -160,7 +160,7 @@ public class TestCachingExecChain {
         execute(req2);
 
         Mockito.verify(mockExecChain).proceed(Mockito.any(), Mockito.any());
-        Mockito.verify(cache).createEntry(Mockito.eq(host), RequestEquivalent.eq(req1),
+        Mockito.verify(cache).store(Mockito.eq(host), RequestEquivalent.eq(req1),
                 Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
     }
 
@@ -1070,7 +1070,7 @@ public class TestCachingExecChain {
         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, mockExecRuntime, context);
         impl.cacheAndReturnResponse(host, request, originResponse, scope, requestSent, responseReceived);
 
-        Mockito.verify(cache, Mockito.never()).createEntry(
+        Mockito.verify(cache, Mockito.never()).store(
                 Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
     }
 
@@ -1096,18 +1096,18 @@ public class TestCachingExecChain {
         final HttpCacheEntry httpCacheEntry = HttpTestUtils.makeCacheEntry();
         final SimpleHttpResponse response = SimpleHttpResponse.create(HttpStatus.SC_OK);
 
-        Mockito.when(mockCache.createEntry(
+        Mockito.when(mockCache.store(
                 Mockito.eq(host),
                 RequestEquivalent.eq(request),
                 ResponseEquivalent.eq(response),
                 Mockito.any(),
                 Mockito.eq(requestSent),
-                Mockito.eq(responseReceived))).thenReturn(httpCacheEntry);
+                Mockito.eq(responseReceived))).thenReturn(new CacheHit("key", httpCacheEntry));
 
         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, mockExecRuntime, context);
         impl.cacheAndReturnResponse(host, request, originResponse, scope, requestSent, responseReceived);
 
-        Mockito.verify(mockCache).createEntry(
+        Mockito.verify(mockCache).store(
                 Mockito.any(),
                 Mockito.any(),
                 Mockito.any(),
@@ -1426,7 +1426,8 @@ public class TestCachingExecChain {
 
         // Prepare original cache entry
         final HttpCacheEntry originalEntry = HttpTestUtils.makeCacheEntry();
-        Mockito.when(mockCache.getCacheEntry(host, request)).thenReturn(originalEntry);
+        Mockito.when(mockCache.match(host, request)).thenReturn(
+                new CacheMatch(new CacheHit("key", originalEntry), null));
 
         // Prepare 304 Not Modified response
         final Instant now = Instant.now();
@@ -1452,19 +1453,19 @@ public class TestCachingExecChain {
                 headers,
                 new HeapResource(body.getBytes(StandardCharsets.UTF_8)));
 
-        Mockito.when(mockCache.updateEntry(Mockito.eq(host), Mockito.eq(request), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(cacheEntry);
+        Mockito.when(mockCache.update(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
+                .thenReturn(new CacheHit("key", cacheEntry));
 
         // Call cacheAndReturnResponse with 304 Not Modified response
         final ClassicHttpResponse cachedResponse = impl.cacheAndReturnResponse(host, request, backendResponse, scope, requestSent, responseReceived);
 
         // Verify cache entry is updated
-        Mockito.verify(mockCache).updateEntry(
-                host,
-                request,
-                originalEntry,
-                backendResponse,
-                requestSent,
-                responseReceived
+        Mockito.verify(mockCache).update(
+                Mockito.any(),
+                Mockito.same(request),
+                Mockito.same(backendResponse),
+                Mockito.eq(requestSent),
+                Mockito.eq(responseReceived)
         );
 
         // Verify response is generated from the updated cache entry
@@ -1518,7 +1519,8 @@ public class TestCachingExecChain {
         // Execute the first request and assert the response
         final ClassicHttpResponse response1 = execute(req1);
         final HttpCacheEntry httpCacheEntry = HttpTestUtils.makeCacheEntry();
-        Mockito.when(responseCache.getCacheEntry(Mockito.any(), Mockito.any())).thenReturn(httpCacheEntry);
+        Mockito.when(responseCache.match(Mockito.any(), Mockito.any())).thenReturn(
+                new CacheMatch(new CacheHit("key", httpCacheEntry), null));
         Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, response1.getCode());
 
         Mockito.when(mockExecRuntime.fork(Mockito.any())).thenReturn(mockExecRuntime);
@@ -1619,7 +1621,8 @@ public class TestCachingExecChain {
         // Execute the first request and assert the response
         final ClassicHttpResponse response1 = execute(req1);
         final HttpCacheEntry httpCacheEntry = HttpTestUtils.makeCacheEntry();
-        Mockito.when(responseCache.getCacheEntry(Mockito.any(), Mockito.any())).thenReturn(httpCacheEntry);
+        Mockito.when(responseCache.match(Mockito.any(), Mockito.any())).thenReturn(
+                new CacheMatch(new CacheHit("key", httpCacheEntry), null));
         Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, response1.getCode());
 
         Mockito.when(mockExecRuntime.fork(Mockito.any())).thenReturn(mockExecRuntime);
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestConditionalRequestBuilder.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestConditionalRequestBuilder.java
index 3ec083065..beb0f0dd9 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestConditionalRequestBuilder.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestConditionalRequestBuilder.java
@@ -27,9 +27,10 @@
 package org.apache.hc.client5.http.impl.cache;
 
 import java.time.Instant;
-import java.util.HashMap;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Iterator;
-import java.util.Map;
+import java.util.Set;
 
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.utils.DateUtils;
@@ -280,10 +281,7 @@ public class TestConditionalRequestBuilder {
         final String etag2 = "\"456\"";
         final String etag3 = "\"789\"";
 
-        final Map<String,Variant> variantEntries = new HashMap<>();
-        variantEntries.put(etag1, new Variant("A", HttpTestUtils.makeCacheEntry(new BasicHeader("ETag", etag1))));
-        variantEntries.put(etag2, new Variant("B", HttpTestUtils.makeCacheEntry(new BasicHeader("ETag", etag2))));
-        variantEntries.put(etag3, new Variant("C", HttpTestUtils.makeCacheEntry(new BasicHeader("ETag", etag3))));
+        final Set<String> variantEntries = new HashSet<>(Arrays.asList(etag1, etag2, etag3));
 
         final HttpRequest conditional = impl.buildConditionalRequestFromVariants(request, variantEntries);
 
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 f5f43cb20..a727be2a9 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
@@ -1848,24 +1848,23 @@ public class TestProtocolRequirements {
         notModified.setHeader("Date", DateUtils.formatStandardDate(now));
         notModified.setHeader("ETag", "\"etag\"");
 
-        Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
+        Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
+                new CacheMatch(new CacheHit("key", entry), null));
         Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(validate), Mockito.any())).thenReturn(notModified);
-        Mockito.when(mockCache.updateEntry(
-                Mockito.eq(host),
-                RequestEquivalent.eq(request),
-                Mockito.eq(entry),
-                ResponseEquivalent.eq(notModified),
-                Mockito.any(),
-                Mockito.any()))
-                .thenReturn(HttpTestUtils.makeCacheEntry());
+        Mockito.when(mockCache.update(
+                        Mockito.any(),
+                        Mockito.any(),
+                        Mockito.any(),
+                        Mockito.any(),
+                        Mockito.any()))
+                .thenReturn(new CacheHit("key", HttpTestUtils.makeCacheEntry()));
 
         execute(request);
 
-        Mockito.verify(mockCache).updateEntry(
-                Mockito.any(),
-                Mockito.any(),
-                Mockito.any(),
+        Mockito.verify(mockCache).update(
                 Mockito.any(),
+                RequestEquivalent.eq(request),
+                ResponseEquivalent.eq(notModified),
                 Mockito.any(),
                 Mockito.any());
     }
@@ -1892,7 +1891,8 @@ public class TestProtocolRequirements {
         impl = new CachingExec(mockCache, null, config);
         request = new BasicClassicHttpRequest("GET", "/thing");
 
-        Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
+        Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
+                new CacheMatch(new CacheHit("key", entry), null));
 
         final ClassicHttpResponse result = execute(request);
 
@@ -1936,7 +1936,8 @@ public class TestProtocolRequirements {
         impl = new CachingExec(mockCache, null, config);
         request = new BasicClassicHttpRequest("GET", "/thing");
 
-        Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
+        Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
+                new CacheMatch(new CacheHit("key", entry), null));
         Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(
                 new IOException("can't talk to origin!"));
 
@@ -2120,7 +2121,8 @@ public class TestProtocolRequirements {
         impl = new CachingExec(mockCache, null, config);
         request = new BasicClassicHttpRequest("GET", "/thing");
 
-        Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
+        Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
+                new CacheMatch(new CacheHit("key", entry), null));
 
         final ClassicHttpResponse result = execute(request);
 
@@ -2183,15 +2185,16 @@ public class TestProtocolRequirements {
 
         final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry();
 
-        Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
+        Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
+                new CacheMatch(new CacheHit("key", entry), null));
         Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(validated);
-        Mockito.when(mockCache.createEntry(
+        Mockito.when(mockCache.store(
                 Mockito.any(),
                 Mockito.any(),
                 ResponseEquivalent.eq(validated),
                 Mockito.any(),
                 Mockito.any(),
-                Mockito.any())).thenReturn(cacheEntry);
+                Mockito.any())).thenReturn(new CacheHit("key", cacheEntry));
 
         final ClassicHttpResponse result = execute(request);
 
@@ -2214,7 +2217,7 @@ public class TestProtocolRequirements {
             }
             Assertions.assertTrue(found113Warning);
         }
-        Mockito.verify(mockCache).createEntry(
+        Mockito.verify(mockCache).store(
                 Mockito.any(),
                 Mockito.any(),
                 Mockito.any(),


[httpcomponents-client] 01/02: Better request method check (no functional changes)

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 80299b00423bda82e5f98171c2aa3b1af2444c9a
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Tue Jun 27 14:08:26 2023 +0200

    Better request method check (no functional changes)
---
 .../hc/client5/http/cache/HeaderConstants.java     | 28 ++++++++++++++++++++++
 .../http/impl/cache/CacheInvalidatorBase.java      |  8 +++----
 .../http/impl/cache/CacheableRequestPolicy.java    |  4 ++--
 .../impl/cache/CachedHttpResponseGenerator.java    |  4 ++--
 .../cache/CachedResponseSuitabilityChecker.java    | 23 +++++++++++-------
 .../client5/http/impl/cache/CachingExecBase.java   |  4 ++--
 .../http/impl/cache/RequestProtocolCompliance.java |  9 ++++---
 .../http/impl/cache/ResponseCachingPolicy.java     |  5 ++--
 .../impl/cache/ResponseProtocolCompliance.java     |  4 ++--
 .../hc/client5/http/cache/TestHttpCacheEntry.java  |  3 ++-
 .../impl/cache/TestCacheableRequestPolicy.java     |  2 +-
 .../impl/cache/TestRequestProtocolCompliance.java  | 15 ++++++------
 .../http/impl/cache/TestResponseCachingPolicy.java |  2 +-
 13 files changed, 71 insertions(+), 40 deletions(-)

diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HeaderConstants.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HeaderConstants.java
index 78a219439..1dd1e9b08 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HeaderConstants.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HeaderConstants.java
@@ -33,12 +33,40 @@ package org.apache.hc.client5.http.cache;
  */
 public class HeaderConstants {
 
+    /**
+     * @deprecated Use {@link org.apache.hc.core5.http.Method}
+     */
+    @Deprecated
     public static final String GET_METHOD = "GET";
+    /**
+     * @deprecated Use {@link org.apache.hc.core5.http.Method}
+     */
+    @Deprecated
     public static final String HEAD_METHOD = "HEAD";
+    /**
+     * @deprecated Use {@link org.apache.hc.core5.http.Method}
+     */
+    @Deprecated
     public static final String OPTIONS_METHOD = "OPTIONS";
+    /**
+     * @deprecated Use {@link org.apache.hc.core5.http.Method}
+     */
+    @Deprecated
     public static final String PUT_METHOD = "PUT";
+    /**
+     * @deprecated Use {@link org.apache.hc.core5.http.Method}
+     */
+    @Deprecated
     public static final String DELETE_METHOD = "DELETE";
+    /**
+     * @deprecated Use {@link org.apache.hc.core5.http.Method}
+     */
+    @Deprecated
     public static final String TRACE_METHOD = "TRACE";
+    /**
+     * @deprecated Use {@link org.apache.hc.core5.http.Method}
+     */
+    @Deprecated
     public static final String POST_METHOD = "POST";
 
     /**
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
index 860c8434b..186edab14 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
@@ -28,13 +28,13 @@ package org.apache.hc.client5.http.impl.cache;
 
 import java.net.URI;
 
-import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.utils.URIUtils;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.Method;
 
 class CacheInvalidatorBase {
 
@@ -43,11 +43,11 @@ class CacheInvalidatorBase {
     }
 
     static boolean requestIsGet(final HttpRequest req) {
-        return req.getMethod().equals((HeaderConstants.GET_METHOD));
+        return Method.GET.isSame(req.getMethod());
     }
 
     static boolean isAHeadCacheEntry(final HttpCacheEntry parentCacheEntry) {
-        return parentCacheEntry != null && parentCacheEntry.getRequestMethod().equals(HeaderConstants.HEAD_METHOD);
+        return parentCacheEntry != null && Method.HEAD.isSame(parentCacheEntry.getRequestMethod());
     }
 
     static boolean isSameHost(final URI requestURI, final URI targetURI) {
@@ -60,7 +60,7 @@ class CacheInvalidatorBase {
     }
 
     static boolean notGetOrHeadRequest(final String method) {
-        return !(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD.equals(method));
+        return !(Method.GET.isSame(method) || Method.HEAD.isSame(method));
     }
 
     private static URI getLocationURI(final URI requestUri, final HttpResponse response, final String headerName) {
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheableRequestPolicy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheableRequestPolicy.java
index d75da8fda..3e6e18fec 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheableRequestPolicy.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheableRequestPolicy.java
@@ -26,10 +26,10 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
-import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -57,7 +57,7 @@ class CacheableRequestPolicy {
             return false;
         }
 
-        if (!method.equals(HeaderConstants.GET_METHOD) && !method.equals(HeaderConstants.HEAD_METHOD)) {
+        if (!Method.GET.isSame(method) && !Method.HEAD.isSame(method)) {
             if (LOG.isDebugEnabled()) {
                 LOG.debug("{} request is not serveable from cache", method);
             }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
index d00f83148..360dfd077 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
@@ -29,7 +29,6 @@ package org.apache.hc.client5.http.impl.cache;
 import java.time.Instant;
 
 import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
-import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.Resource;
 import org.apache.hc.client5.http.cache.ResourceIOException;
@@ -41,6 +40,7 @@ import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.apache.hc.core5.util.TimeValue;
 
@@ -160,7 +160,7 @@ class CachedHttpResponseGenerator {
     }
 
     private boolean responseShouldContainEntity(final HttpRequest request, final HttpCacheEntry cacheEntry) {
-        return request.getMethod().equals(HeaderConstants.GET_METHOD) && cacheEntry.getResource() != null;
+        return Method.GET.isSame(request.getMethod()) && cacheEntry.getResource() != null;
     }
 
     /**
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java
index 60b2f6127..7581688e6 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java
@@ -29,7 +29,6 @@ package org.apache.hc.client5.http.impl.cache;
 import java.time.Instant;
 import java.util.Iterator;
 
-import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.Header;
@@ -37,6 +36,7 @@ import org.apache.hc.core5.http.HeaderElement;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.message.MessageSupport;
 import org.apache.hc.core5.util.TimeValue;
 import org.slf4j.Logger;
@@ -176,7 +176,11 @@ class CachedResponseSuitabilityChecker {
     }
 
     private boolean isGet(final HttpRequest request) {
-        return request.getMethod().equals(HeaderConstants.GET_METHOD);
+        return Method.GET.isSame(request.getMethod());
+    }
+
+    private boolean isHead(final HttpRequest request) {
+        return Method.HEAD.isSame(request.getMethod());
     }
 
     private boolean entryIsNotA204Response(final HttpCacheEntry entry) {
@@ -201,17 +205,18 @@ class CachedResponseSuitabilityChecker {
     }
 
     /**
-     * Determines whether the given request is a {@link HeaderConstants#GET_METHOD} request and the associated cache entry was created by a
-     * {@link HeaderConstants#HEAD_METHOD} request.
+     * Determines whether the given request is a {@link org.apache.hc.core5.http.Method#GET} request and the
+     * associated cache entry was created by a {@link org.apache.hc.core5.http.Method#HEAD} request.
      *
-     * @param request The {@link HttpRequest} to check if it is a {@link HeaderConstants#GET_METHOD} request.
-     * @param entry   The {@link HttpCacheEntry} to check if it was created by a {@link HeaderConstants#HEAD_METHOD} request.
-     * @return true if the request is a {@link HeaderConstants#GET_METHOD} request and the cache entry was created by a
-     * {@link HeaderConstants#HEAD_METHOD} request, otherwise {@code false}.
+     * @param request The {@link HttpRequest} to check if it is a {@link org.apache.hc.core5.http.Method#GET} request.
+     * @param entry   The {@link HttpCacheEntry} to check if it was created by
+     *                a {@link org.apache.hc.core5.http.Method#HEAD} request.
+     * @return true if the request is a {@link org.apache.hc.core5.http.Method#GET} request and the cache entry was
+     * created by a {@link org.apache.hc.core5.http.Method#HEAD} request, otherwise {@code false}.
      * @since 5.3
      */
     public boolean isGetRequestWithHeadCacheEntry(final HttpRequest request, final HttpCacheEntry entry) {
-        return isGet(request) && HeaderConstants.HEAD_METHOD.equalsIgnoreCase(entry.getRequestMethod());
+        return isGet(request) && Method.HEAD.isSame(entry.getRequestMethod());
     }
 
 
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
index e81e0eba5..41aaafdbe 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
@@ -35,7 +35,6 @@ import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
 import org.apache.hc.client5.http.cache.CacheResponseStatus;
-import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.client5.http.cache.HttpCacheContext;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.ResourceIOException;
@@ -48,6 +47,7 @@ import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.protocol.HttpContext;
@@ -332,7 +332,7 @@ public class CachingExecBase {
     }
 
     boolean clientRequestsOurOptions(final HttpRequest request) {
-        if (!HeaderConstants.OPTIONS_METHOD.equals(request.getMethod())) {
+        if (!Method.OPTIONS.isSame(request.getMethod())) {
             return false;
         }
 
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/RequestProtocolCompliance.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/RequestProtocolCompliance.java
index 0fa43c893..7a60d5c42 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/RequestProtocolCompliance.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/RequestProtocolCompliance.java
@@ -33,11 +33,11 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
-import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -97,7 +97,7 @@ class RequestProtocolCompliance {
     }
 
     private void decrementOPTIONSMaxForwardsIfGreaterThen0(final HttpRequest request) {
-        if (!HeaderConstants.OPTIONS_METHOD.equals(request.getMethod())) {
+        if (!Method.OPTIONS.isSame(request.getMethod())) {
             return;
         }
 
@@ -131,7 +131,7 @@ class RequestProtocolCompliance {
 
     private RequestProtocolError requestHasWeakETagAndRange(final HttpRequest request) {
         final String method = request.getMethod();
-        if (!(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD.equals(method))) {
+        if (!(Method.GET.isSame(method) || Method.HEAD.isSame(method))) {
             return null;
         }
 
@@ -165,8 +165,7 @@ class RequestProtocolCompliance {
 
     private RequestProtocolError requestHasWeekETagForPUTOrDELETEIfMatch(final HttpRequest request, final boolean resourceExists) {
         final String method = request.getMethod();
-        if (!(HeaderConstants.PUT_METHOD.equals(method) || HeaderConstants.DELETE_METHOD.equals(method)
-                || HeaderConstants.POST_METHOD.equals(method))
+        if (!(Method.PUT.isSame(method) || Method.DELETE.isSame(method) || Method.POST.isSame(method))
         ) {
             return null;
         }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java
index 179bb7ace..a055bbd3a 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java
@@ -35,7 +35,6 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
-import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.Header;
@@ -45,6 +44,7 @@ import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.http.message.MessageSupport;
 import org.slf4j.Logger;
@@ -164,8 +164,7 @@ class ResponseCachingPolicy {
     public boolean isResponseCacheable(final ResponseCacheControl cacheControl, final String httpMethod, final HttpResponse response) {
         boolean cacheable = false;
 
-        if (!HeaderConstants.GET_METHOD.equals(httpMethod) && !HeaderConstants.HEAD_METHOD.equals(httpMethod)
-                && !HeaderConstants.POST_METHOD.equals(httpMethod)) {
+        if (!Method.GET.isSame(httpMethod) && !Method.HEAD.isSame(httpMethod) && !Method.POST.isSame((httpMethod))) {
             if (LOG.isDebugEnabled()) {
                 LOG.debug("{} method response is not cacheable", httpMethod);
             }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseProtocolCompliance.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseProtocolCompliance.java
index 19adf6d93..9454b3a7a 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseProtocolCompliance.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseProtocolCompliance.java
@@ -32,7 +32,6 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.hc.client5.http.ClientProtocolException;
-import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HeaderElement;
@@ -42,6 +41,7 @@ import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.apache.hc.core5.http.message.MessageSupport;
@@ -169,7 +169,7 @@ class ResponseProtocolCompliance {
 
     private void ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(final HttpRequest request,
             final HttpResponse response) {
-        if (!request.getMethod().equalsIgnoreCase(HeaderConstants.OPTIONS_METHOD)) {
+        if (!Method.OPTIONS.isSame(request.getMethod())) {
             return;
         }
 
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntry.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntry.java
index 1b4745944..8257ffe91 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntry.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntry.java
@@ -45,6 +45,7 @@ import org.apache.hc.client5.http.impl.cache.HttpTestUtils;
 import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -151,7 +152,7 @@ public class TestHttpCacheEntry {
                 new BasicHeader("bar", "barValue2")
         };
         entry = makeEntry(headers);
-        assertEquals(HeaderConstants.GET_METHOD, entry.getRequestMethod());
+        assertEquals(Method.GET.name(), entry.getRequestMethod());
     }
 
     @Test
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheableRequestPolicy.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheableRequestPolicy.java
index 8a2d088b3..bf15db608 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheableRequestPolicy.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheableRequestPolicy.java
@@ -140,7 +140,7 @@ public class TestCacheableRequestPolicy {
 
         Assertions.assertFalse(policy.isServableFromCache(cacheControl, request));
 
-        final BasicHttpRequest request2 = new BasicHttpRequest("get", "someUri");
+        final BasicHttpRequest request2 = new BasicHttpRequest("huh", "someUri");
 
         Assertions.assertFalse(policy.isServableFromCache(cacheControl, request2));
 
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRequestProtocolCompliance.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRequestProtocolCompliance.java
index f1693d964..fd36b3500 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRequestProtocolCompliance.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRequestProtocolCompliance.java
@@ -33,7 +33,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpVersion;
@@ -103,7 +102,7 @@ public class TestRequestProtocolCompliance {
 
     @Test
     public void testRequestWithMultipleIfMatchHeaders() {
-        final HttpRequest req = new BasicHttpRequest(HeaderConstants.PUT_METHOD, "http://example.com/");
+        final HttpRequest req = new BasicHttpRequest("PUT", "http://example.com/");
         req.addHeader(HttpHeaders.IF_MATCH, "W/\"weak1\"");
         req.addHeader(HttpHeaders.IF_MATCH, "W/\"weak2\"");
         assertEquals(1, impl.requestIsFatallyNonCompliant(req, false).size());
@@ -111,7 +110,7 @@ public class TestRequestProtocolCompliance {
 
     @Test
     public void testRequestWithMultipleIfNoneMatchHeaders() {
-        final HttpRequest req = new BasicHttpRequest(HeaderConstants.PUT_METHOD, "http://example.com/");
+        final HttpRequest req = new BasicHttpRequest("PUT", "http://example.com/");
         req.addHeader(HttpHeaders.IF_NONE_MATCH, "W/\"weak1\"");
         req.addHeader(HttpHeaders.IF_NONE_MATCH, "W/\"weak2\"");
         assertEquals(1, impl.requestIsFatallyNonCompliant(req, false).size());
@@ -119,7 +118,7 @@ public class TestRequestProtocolCompliance {
 
     @Test
     public void testRequestWithPreconditionFailed() {
-        final HttpRequest req = new BasicHttpRequest(HeaderConstants.GET_METHOD, "http://example.com/");
+        final HttpRequest req = new BasicHttpRequest("GET", "http://example.com/");
         req.addHeader(HttpHeaders.IF_MATCH, "W/\"weak1\"");
         req.addHeader(HttpHeaders.RANGE, "1");
         req.addHeader(HttpHeaders.IF_RANGE, "W/\"weak2\""); // ETag doesn't match with If-Match ETag
@@ -130,7 +129,7 @@ public class TestRequestProtocolCompliance {
 
     @Test
     public void testRequestWithValidIfRangeDate() {
-        final HttpRequest req = new BasicHttpRequest(HeaderConstants.GET_METHOD, "http://example.com/");
+        final HttpRequest req = new BasicHttpRequest("GET", "http://example.com/");
         req.addHeader(HttpHeaders.RANGE, "bytes=0-499");
         req.addHeader(HttpHeaders.LAST_MODIFIED, "Wed, 21 Oct 2023 07:28:00 GMT");
         req.addHeader(HttpHeaders.IF_RANGE, "Wed, 21 Oct 2023 07:28:00 GMT");
@@ -139,7 +138,7 @@ public class TestRequestProtocolCompliance {
 
     @Test
     public void testRequestWithInvalidDateFormat() {
-        final HttpRequest req = new BasicHttpRequest(HeaderConstants.GET_METHOD, "http://example.com/");
+        final HttpRequest req = new BasicHttpRequest("GET", "http://example.com/");
         req.addHeader(HttpHeaders.RANGE, "bytes=0-499");
         req.addHeader(HttpHeaders.LAST_MODIFIED, "Wed, 21 Oct 2023 07:28:00 GMT");
         req.addHeader(HttpHeaders.IF_RANGE, "20/10/2023");
@@ -148,7 +147,7 @@ public class TestRequestProtocolCompliance {
 
     @Test
     public void testRequestWithMissingIfRangeDate() {
-        final HttpRequest req = new BasicHttpRequest(HeaderConstants.GET_METHOD, "http://example.com/");
+        final HttpRequest req = new BasicHttpRequest("GET", "http://example.com/");
         req.addHeader(HttpHeaders.RANGE, "bytes=0-499");
         req.addHeader(HttpHeaders.LAST_MODIFIED, "Wed, 21 Oct 2023 07:28:00 GMT");
         assertTrue(impl.requestIsFatallyNonCompliant(req, false).isEmpty());
@@ -158,7 +157,7 @@ public class TestRequestProtocolCompliance {
     public void testRequestWithWeakETagAndRangeAndDAte() {
         // Setup request with GET method, Range header, If-Range header starting with "W/",
         // and a Last-Modified date that doesn't match the If-Range date
-        final HttpRequest req = new BasicHttpRequest(HeaderConstants.GET_METHOD, "http://example.com/");
+        final HttpRequest req = new BasicHttpRequest("GET", "http://example.com/");
         req.addHeader(HttpHeaders.RANGE, "bytes=0-499");
         req.addHeader(HttpHeaders.LAST_MODIFIED, "Fri, 20 Oct 2023 07:28:00 GMT");
         req.addHeader(HttpHeaders.IF_RANGE, "Wed, 18 Oct 2023 07:28:00 GMT");
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java
index 03851b015..57344e31c 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java
@@ -512,7 +512,7 @@ public class TestResponseCachingPolicy {
 
         Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, "PUT", response));
 
-        Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, "get", response));
+        Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, "huh", response));
     }
 
     @Test