Share artifact cache across JavaSourceSetUpdater visitors#7454
Merged
steve-aom-elliott merged 2 commits intomainfrom Apr 22, 2026
Merged
Share artifact cache across JavaSourceSetUpdater visitors#7454steve-aom-elliott merged 2 commits intomainfrom
steve-aom-elliott merged 2 commits intomainfrom
Conversation
JavaSourceSetUpdater previously allocated a fresh temp directory and a fresh LocalMavenArtifactCache in its constructor. Every recipe visitor that mutates a dependency (AddDependency, UpgradeDependencyVersion, ChangeDependency, ChangeDependencyGroupIdAndArtifactId in both rewrite-maven and rewrite-gradle) constructs its own updater, so the cache was never reused across visitors — each downloaded the same JARs from scratch. Add a getArtifactCache()/setArtifactCache() pair on MavenExecutionContextView mirroring the existing getPomCache()/setPomCache() pattern, lazily defaulting to a LocalMavenArtifactCache at ~/.rewrite/cache/artifacts (the same path JavaRewriteRpc already uses) and falling back to a single JVM-lifetime temp directory if the user home is not writable. JavaSourceSetUpdater now resolves its downloader cache from this shared cache, so all updaters built from the same ExecutionContext share artifact bytes. Embedders that want to point the cache at a persistent or shared volume can override it via setArtifactCache before any visitor runs.
Even with the artifact cache shared, every visitor that calls JavaSourceSet.typesFromPath re-opens the JAR and re-walks its entries to build the FullyQualified type list. The result is a pure function of the GAV's bytes, so it can be cached by GAV for the lifetime of the ExecutionContext. Add a Map<String, List<JavaType.FullyQualified>> on the ctx (keyed by "org.openrewrite.maven.jarTypeCache") that JavaSourceSetUpdater consults before scanning. Only positive results are cached: addDependency tries each repository in order until one succeeds, so caching empty results would short-circuit later repos.
This was referenced Apr 22, 2026
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.
What
JavaSourceSetUpdater(introduced in #7202, refined by #7358 / #7373 / #7376) downloads each newly-added or changed dependency's JAR so it can refresh the in-memoryJavaSourceSetmarker with the new types. Two pieces of state were not shared across visitors:Files.createTempDirectory("rewrite-artifact-cache")and wrapped it in aLocalMavenArtifactCacheper visitor instance, so the same JARs were re-downloaded across recipes in a run.JavaSourceSet.typesFromPath(jarPath, null)re-opens the JAR and re-walks its entries on every call, even though the result is a pure function of the GAV's bytes.Every dependency-mutating recipe (
AddDependency,UpgradeDependencyVersion,ChangeDependency,ChangeDependencyGroupIdAndArtifactIdin bothrewrite-mavenandrewrite-gradle) constructs its own updater, so both costs were paid per recipe per source set.This PR shares both, scoped to the
ExecutionContext.Why
Migration recipe chains (e.g. Spring Boot 3 → 4) trigger dozens of dependency-mutating recipes per repository. Without sharing, the same JARs are re-downloaded and re-scanned across the run — multiplying outbound traffic to Maven Central / artifactory and burning CPU on redundant JAR walks.
How
1. Shared
MavenArtifactCacheonMavenExecutionContextViewMirrors the existing
MavenPomCachepattern:MAVEN_ARTIFACT_CACHEmessage key.setArtifactCache(MavenArtifactCache)setter for embedders that want to override (e.g. point at a persistent volume).getArtifactCache()that lazily defaults to aLocalMavenArtifactCacheat~/.rewrite/cache/artifacts(the same directoryJavaRewriteRpcalready uses), falling back to a single JVM-lifetime temp directory if the user home isn't writable.JavaSourceSetUpdaterconstructor now resolves its downloader's cache frommctx.getArtifactCache()instead of allocating a per-visitor temp dir.2. Memoized
typesFromPathresults on theExecutionContextJavaSourceSetUpdaterreads/writes aMap<String, List<JavaType.FullyQualified>>stored on the ctx under"org.openrewrite.maven.jarTypeCache", keyed bygroupId:artifactId:version.addDependencytries each repository in order until one succeeds, so caching empty results would short-circuit later repos.Tests
New
MavenExecutionContextViewTest:artifactCacheIsMemoizedAcrossCalls— repeatedgetArtifactCache()calls return the same instance.setArtifactCacheOverridesDefault—setArtifactCache(...)overrides the lazy default.artifactCacheIsSharedBetweenViewsOverSameDelegate— load-bearing: eachJavaSourceSetUpdatercallsMavenExecutionContextView.view(ctx)freshly, so the cache must live on the underlying delegate's message map, not on the view.New
JavaSourceSetUpdaterTest:typeCacheIsSharedAcrossUpdaters— two updaters built from the same ctx see the same type-cache map.Existing tests still pass:
ChangeDependencyGroupIdAndArtifactIdTest(full class, includingupdatesJavaSourceSetMarkerOnJavaFiles),AddDependencyTestandUpgradeDependencyVersionTestin bothrewrite-mavenandrewrite-gradle, andChangeDependencyTestinrewrite-gradle.