Conditional GET (ETag / Last-Modified) for Maven metadata downloads - #8081
Open
knutwannheden wants to merge 2 commits into
Open
Conditional GET (ETag / Last-Modified) for Maven metadata downloads#8081knutwannheden wants to merge 2 commits into
knutwannheden wants to merge 2 commits into
Conversation
Re-validating an expired `maven-metadata.xml` entry previously meant a full re-download and re-parse for every GAV across every configured repository. This adds support for HTTP conditional requests so an unchanged version list can be confirmed by a cheap `304 Not Modified` instead. `MavenPomCache` gains two non-breaking `default` methods: one to hand back a possibly-stale entry together with its `ETag` / `Last-Modified` validators, and a validator-aware `putMavenMetadata` overload. Existing cache implementations compile and behave exactly as before (no validator -> unconditional download). `MavenPomDownloader.downloadMetadata` now issues an `If-None-Match` / `If-Modified-Since` request when a validator is available, reuses the cached value (skipping the parse) on a 304, and stores the response's validators on a 200.
knutwannheden
force-pushed
the
conditional-gets-for-maven-metadata
branch
from
June 19, 2026 12:47
b5db869 to
937a7e0
Compare
jkschneider
reviewed
Jun 19, 2026
| * @param etag the {@code ETag} response header, or {@code null} | ||
| * @param lastModified the {@code Last-Modified} response header, or {@code null} | ||
| */ | ||
| default void putMavenMetadata(URI repo, GroupArtifactVersion gav, @Nullable MavenMetadata metadata, |
Member
There was a problem hiding this comment.
Like to see if we can do this without adding more overloads.
Contributor
Author
There was a problem hiding this comment.
I've simplified the APIs now. Please check if that works for you.
PR #8081 added conditional-GET support by bolting a parallel pair of methods onto MavenPomCache: getMavenMetadataForRevalidation (consulted after a miss to recover a stale value + its validators) and a 4-arg putMavenMetadata overload carrying ETag/Last-Modified. That left two ways to get and two ways to put, and — because the validators rode on separate methods — every cache decorator had to remember to forward them or silently drop the feature (CompositeMavenPomCache did exactly that). Collapse the four methods into the existing two. getMavenMetadata now returns a MavenMetadataCacheEntry (renamed from MavenMetadataValidation) carrying the value, its validators, and an `expired` verdict; putMavenMetadata accepts the same entry. A single get conveys all three outcomes the downloader needs: null -> miss, unconditional download entry !expired -> fresh hit (value or negative), served directly, no network entry expired -> revalidate via conditional GET using the entry's validators The `expired` flag is a read-time verdict only (always false on the write path, via MavenMetadataCacheEntry.fresh); caches that expire entries recompute it on read. CompositeMavenPomCache no longer needs any conditional-GET-specific override — plain forwarding of get/put carries the validators through both layers, so the decorator-drops-the-feature hazard is structurally gone. Breaking change to MavenPomCache (no default methods); InMemory, Rocksdb, Composite, the downloader, and the test fakes are updated accordingly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Caches that store
maven-metadata.xmlversion lists with a freshness window (for example a persistent cross-JVM cache with a 1-hour TTL) pay a recurring "cold metadata" tax: once the window elapses, the next resolution re-downloads and re-parses the metadata for every GAV across every configured repository, even when nothing has changed. Released POMs are immutable and cache cleanly, so the cost is specifically the version-list metadata that driveslatest.release, version ranges,RELEASE/LATEST, BOM / plugin version discovery, and snapshot timestamps. The TTL can't simply be lengthened because metadata is genuinely mutable — new versions get published, andlatest.release-style recipes would silently miss them. The correct fix is an HTTP conditional request: sendIf-None-Match(ETag) /If-Modified-Since(Last-Modified) and handle304 Not Modified, whose empty body lets us skip both the download and the parse while refreshing the entry. Maven Central, Artifactory, and Nexus all support these onmaven-metadata.xml.Examples
MavenPomCacheexchanges a singleMavenMetadataCacheEntryformaven-metadata.xml— it carries the value, itsETag/Last-Modifiedvalidators, and anexpiredverdict. A cache that wants conditional GETs persists the validators and, once an entry's freshness window has elapsed, hands it back markedexpiredso the downloader revalidates it instead of re-downloading:On the next resolution after the entry expires, the downloader sends
If-None-Match/If-Modified-Since; a304reuses the cached value (no parse) and re-stores it (refreshing freshness), while a200replaces the value and captures the new validators. A cache that never marks an entryexpired(the in-memory default) keeps serving it directly with no network call.Summary
MavenMetadataCacheEntry— a value type carrying a cachedMavenMetadata, itsETag/Last-Modifiedvalidators, and anexpiredread-time verdict. Write-side entries are built withMavenMetadataCacheEntry.fresh(...)(neverexpired); caches that expire entries recompute the verdict on read (e.g. viawithExpired(...)).MavenPomCache's metadata methods into a single get/put pair carrying that entry:getMavenMetadatareturns a@Nullable MavenMetadataCacheEntry(anullreturn is a cache miss; a non-null entry carries the value — or a negative result when itsmetadataisnull— together with any validators, and is markedexpiredwhen the caller should revalidate), andputMavenMetadataaccepts the same entry. This replaces the previousOptional<MavenMetadata>-returning get and the bare put.defaultand their signatures changed, so everyMavenPomCacheimplementation must adopt the new entry type. Updated in this PR:InMemoryMavenPomCache,RocksdbMavenPomCache(metadata remains a no-op there), andCompositeMavenPomCache.MavenPomDownloader.downloadMetadataconsults the single get, serves a fresh hit (value or negative) directly with no network call, and on a miss or expired entry issues a conditional GET (If-None-Match/If-Modified-Since) using the entry's validators; a304reuses the cached value (skipping the parse), a200captures the newETag/Last-Modified. The 304 status is surfaced through theHttpSenderresponse rather than being treated as an error.CompositeMavenPomCacheneeds no conditional-GET-specific code: forwarding the unified get/put through both layers carries the validators automatically. It promotes only a still-freshl2hit intol1, leaving an expiredl2entry (validators only) for the downloader to revalidate rather than seedingl1with a stale value.Test plan
MavenPomDownloaderTest.ConditionalMetadataRequests.reusesCachedValueOn304— the second request sendsIf-None-Match, the server replies304, and the cached value is reused without re-parsing a body.downloadsNewMetadataWhenChanged— a conditional GET whose validator no longer matches receives a fresh200(new version surfaces), the new ETag is stored, and the following revalidation is a304against the new ETag.sendsNoConditionalHeadersWhenCacheRetainsNoValidator— a cache that always misses performs only plain, unconditional downloads.CompositeMavenPomCacheTest— the unified put forwards a value and its validators to the persistent layer, and anl2entry (with validators) surfaces through the composite on anl1miss.:rewrite-maven:testsuite plus:rewrite-gradleconsumers, and license checks, pass.