From 6e336a79fbf59d19d03bd3f1eafcc12e2bfa310d Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 22 Jul 2026 23:07:18 +0200 Subject: [PATCH 1/9] Ignore spurious transitive exclusions when detecting redundant dependencies A dependency's effective exclusions are resolved within the matched parent's whole tree, so they can be polluted by exclusions other branches declare against artifacts the coordinate could never bring on its own (e.g. an optional dependency pruned elsewhere). Comparing these raw sets against a clean direct declaration made the recipe keep genuinely redundant dependencies. Filter each transitive's effective exclusions down to artifacts in the coordinate's own dependency closure, dropping no-op exclusions before the comparison. This only ever drops exclusions that change nothing, so it never causes an unsafe removal. --- .../RemoveRedundantDependencies.java | 51 +++++- .../RemoveRedundantDependenciesTest.java | 162 ++++++++++++++++++ 2 files changed, 209 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java index ef8f53ad..34835a9a 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java @@ -74,6 +74,8 @@ public Accumulator getInitialValue(ExecutionContext ctx) { @Override public TreeVisitor getScanner(Accumulator acc) { return new TreeVisitor() { + private final Map> closureCache = new HashMap<>(); + @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree == null) { @@ -162,7 +164,7 @@ private void resolveTransitivesFromPom( // Collect all dependencies (both direct and transitive of the parent) Set visited = new HashSet<>(); for (ResolvedDependency dep : resolved) { - collectAllDependencies(dep, transitives, visited); + collectAllDependencies(dep, transitives, visited, effectiveRepos, downloader, ctx); } } catch (MavenDownloadingException | MavenDownloadingExceptions e) { // If we can't download/resolve the POM, fall back to not detecting redundancies @@ -180,11 +182,52 @@ private ResolvedPom applyExclusions(ResolvedPom resolvedPom, List } private void collectAllDependencies(ResolvedDependency dep, Set transitives, - Set visited) { + Set visited, List repositories, + MavenPomDownloader downloader, ExecutionContext ctx) { if (visited.add(dep.getGav())) { - transitives.add(new TransitiveDependency(dep.getGav(), new HashSet<>(dep.getEffectiveExclusions()))); + transitives.add(new TransitiveDependency(dep.getGav(), + relevantExclusions(dep, repositories, downloader, ctx))); + for (ResolvedDependency transitive : dep.getDependencies()) { + collectAllDependencies(transitive, transitives, visited, repositories, downloader, ctx); + } + } + } + + // A dependency's effective exclusions are resolved within the parent's whole tree, so they can + // be polluted by exclusions that other branches declare against artifacts this coordinate could + // never bring on its own (e.g. an optional dependency pruned elsewhere). Keep only the exclusions + // that target something in the coordinate's own dependency closure, so the comparison against a + // clean direct declaration is not thrown off by no-op exclusions. + private Set relevantExclusions(ResolvedDependency dep, List repositories, + MavenPomDownloader downloader, ExecutionContext ctx) { + Set exclusions = new HashSet<>(dep.getEffectiveExclusions()); + if (!exclusions.isEmpty()) { + exclusions.retainAll(dependencyClosure(dep.getGav(), repositories, downloader, ctx)); + } + return exclusions; + } + + private Set dependencyClosure(ResolvedGroupArtifactVersion gav, List repositories, + MavenPomDownloader downloader, ExecutionContext ctx) { + return closureCache.computeIfAbsent(gav, g -> { + Set closure = new HashSet<>(); + try { + Pom pom = downloader.download(g.asGroupArtifactVersion(), null, null, repositories); + ResolvedPom resolvedPom = pom.resolve(emptyList(), downloader, repositories, ctx); + for (ResolvedDependency d : resolvedPom.resolveDependencies(Scope.Compile, downloader, ctx)) { + collectClosure(d, closure); + } + } catch (MavenDownloadingException | MavenDownloadingExceptions e) { + // Best-effort: an unresolvable closure leaves the exclusions unfiltered + } + return closure; + }); + } + + private void collectClosure(ResolvedDependency dep, Set closure) { + if (closure.add(dep.getGav().asGroupArtifact())) { for (ResolvedDependency transitive : dep.getDependencies()) { - collectAllDependencies(transitive, transitives, visited); + collectClosure(transitive, closure); } } } diff --git a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java index f163f7da..307a27e6 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java @@ -495,6 +495,168 @@ void keepsDirectCompileTomcatEmbedCoreWhenProviderIsProvidedScoped() { ); } + @Test + void removesJakartaClientWhenTransitiveExclusionsAreEquivalent() { + // Resolved within the starter's whole tree, the transitive jakarta.ws.rs-api gains a spurious + // effective exclusion of jakarta.activation-api (which it can never bring on its own), while the + // direct declaration has none; the two are effectively equivalent so the direct one is redundant. + rewriteRun( + spec -> spec.recipe(new RemoveRedundantDependencies( + "org.springframework.boot", "spring-boot-starter-*")), + //language=xml + pomXml( + """ + + 4.0.0 + com.sample + sample + 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + + org.springframework.boot + spring-boot-starter-jersey + + + jakarta.ws.rs + jakarta.ws.rs-api + 3.1.0 + + + + """, + """ + + 4.0.0 + com.sample + sample + 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + + org.springframework.boot + spring-boot-starter-jersey + + + + """ + ) + ); + } + + @Test + void keepsJerseyClientWhenDirectExclusionsDifferFromTransitive() { + rewriteRun( + spec -> spec.recipe(new RemoveRedundantDependencies( + "org.springframework.boot", "spring-boot-starter-*")), + //language=xml + pomXml( + """ + + 4.0.0 + com.sample + sample + 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + + org.springframework.boot + spring-boot-starter-jersey + + + org.glassfish.jersey.core + jersey-client + 3.1.5 + + + jakarta.inject + jakarta.inject-api + + + + + + """ + ) + ); + } + + @Test + void removesTomcatEmbedCoreWhenExclusionsMatchTransitive() { + rewriteRun( + spec -> spec.recipe(new RemoveRedundantDependencies( + "org.springframework.boot", "spring-boot-starter-*")), + //language=xml + pomXml( + """ + + 4.0.0 + com.sample + sample + 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.apache.tomcat.embed + tomcat-embed-core + + + org.apache.tomcat + tomcat-annotations-api + + + + + + """, + """ + + 4.0.0 + com.sample + sample + 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + + org.springframework.boot + spring-boot-starter-web + + + + """ + ) + ); + } + @Test void removeRedundantGradleDependency() { rewriteRun( From e5cc17aaab4e4aa2958e026ce06d5b358b5305dc Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 22 Jul 2026 23:26:22 +0200 Subject: [PATCH 2/9] Simplify redundant-dependency exclusion filtering Share the POM download/resolve path between resolveTransitivesFromPom and dependencyClosure via resolvePom/withMavenCentral helpers, and hoist the closure cache onto the Accumulator so it survives across source files in a multi-module build. --- .../RemoveRedundantDependencies.java | 48 +++++++++++-------- .../RemoveRedundantDependenciesTest.java | 6 +-- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java index 34835a9a..598cad70 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java @@ -58,6 +58,8 @@ public class RemoveRedundantDependencies extends ScanningRecipe scope/configuration -> Set of transitive dependencies Map>> transitivesByProjectAndScope; + // Cache of each coordinate's own clean dependency closure, shared across all source files in the run + Map> closureCache; } @Value @@ -68,14 +70,12 @@ public static class TransitiveDependency { @Override public Accumulator getInitialValue(ExecutionContext ctx) { - return new Accumulator(new HashMap<>()); + return new Accumulator(new HashMap<>(), new HashMap<>()); } @Override public TreeVisitor getScanner(Accumulator acc) { return new TreeVisitor() { - private final Map> closureCache = new HashMap<>(); - @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree == null) { @@ -147,17 +147,10 @@ private void resolveTransitivesFromPom( MavenPomDownloader downloader, ExecutionContext ctx, Set transitives) { + List effectiveRepos = withMavenCentral(repositories); try { - // Ensure we have Maven Central in the repositories - List effectiveRepos = new ArrayList<>(repositories); - if (effectiveRepos.stream().noneMatch(r -> r.getUri().contains("repo.maven.apache.org") || - r.getUri().contains("repo1.maven.org"))) { - effectiveRepos.add(MavenRepository.MAVEN_CENTRAL); - } - // Get the resolved dependencies for compile scope (which includes most transitives) - Pom pom = downloader.download(gav.asGroupArtifactVersion(), null, null, effectiveRepos); - ResolvedPom resolvedPom = pom.resolve(emptyList(), downloader, effectiveRepos, ctx); + ResolvedPom resolvedPom = resolvePom(gav, effectiveRepos, downloader, ctx); ResolvedPom patchedPom = applyExclusions(resolvedPom, effectiveExclusions); List resolved = patchedPom.resolveDependencies(Scope.Compile, downloader, ctx); @@ -193,11 +186,8 @@ private void collectAllDependencies(ResolvedDependency dep, Set relevantExclusions(ResolvedDependency dep, List repositories, MavenPomDownloader downloader, ExecutionContext ctx) { Set exclusions = new HashSet<>(dep.getEffectiveExclusions()); @@ -209,12 +199,11 @@ private Set relevantExclusions(ResolvedDependency dep, List dependencyClosure(ResolvedGroupArtifactVersion gav, List repositories, MavenPomDownloader downloader, ExecutionContext ctx) { - return closureCache.computeIfAbsent(gav, g -> { + return acc.closureCache.computeIfAbsent(gav, g -> { Set closure = new HashSet<>(); try { - Pom pom = downloader.download(g.asGroupArtifactVersion(), null, null, repositories); - ResolvedPom resolvedPom = pom.resolve(emptyList(), downloader, repositories, ctx); - for (ResolvedDependency d : resolvedPom.resolveDependencies(Scope.Compile, downloader, ctx)) { + for (ResolvedDependency d : resolvePom(g, repositories, downloader, ctx) + .resolveDependencies(Scope.Compile, downloader, ctx)) { collectClosure(d, closure); } } catch (MavenDownloadingException | MavenDownloadingExceptions e) { @@ -224,6 +213,23 @@ private Set dependencyClosure(ResolvedGroupArtifactVersion gav, L }); } + private ResolvedPom resolvePom(ResolvedGroupArtifactVersion gav, List repositories, + MavenPomDownloader downloader, ExecutionContext ctx) + throws MavenDownloadingException, MavenDownloadingExceptions { + List repos = withMavenCentral(repositories); + Pom pom = downloader.download(gav.asGroupArtifactVersion(), null, null, repos); + return pom.resolve(emptyList(), downloader, repos, ctx); + } + + private List withMavenCentral(List repositories) { + List effectiveRepos = new ArrayList<>(repositories); + if (effectiveRepos.stream().noneMatch(r -> r.getUri().contains("repo.maven.apache.org") || + r.getUri().contains("repo1.maven.org"))) { + effectiveRepos.add(MavenRepository.MAVEN_CENTRAL); + } + return effectiveRepos; + } + private void collectClosure(ResolvedDependency dep, Set closure) { if (closure.add(dep.getGav().asGroupArtifact())) { for (ResolvedDependency transitive : dep.getDependencies()) { diff --git a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java index 307a27e6..084e035f 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java @@ -497,9 +497,9 @@ void keepsDirectCompileTomcatEmbedCoreWhenProviderIsProvidedScoped() { @Test void removesJakartaClientWhenTransitiveExclusionsAreEquivalent() { - // Resolved within the starter's whole tree, the transitive jakarta.ws.rs-api gains a spurious - // effective exclusion of jakarta.activation-api (which it can never bring on its own), while the - // direct declaration has none; the two are effectively equivalent so the direct one is redundant. + // In the starter's tree the transitive jakarta.ws.rs-api gains a spurious exclusion of + // jakarta.activation-api (which it can never bring on its own); the direct declaration has none, + // so the two are effectively equivalent and the direct one is redundant. rewriteRun( spec -> spec.recipe(new RemoveRedundantDependencies( "org.springframework.boot", "spring-boot-starter-*")), From 2d49d4a6152b8ba1387b80cb1f0047bb9f2cfbac Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 27 Jul 2026 23:42:32 +0200 Subject: [PATCH 3/9] Compare declared exclusions, not effective ones, for redundant dependencies Within a large dependency tree a coordinate's effective exclusions are corrupted by dependency mediation: an artifact excluded on one path may already have been pruned at a shared node, so it silently drops out of getEffectiveExclusions(). That inverted RemoveRedundantDependencies for e.g. spring-boot-starter-web -> spring-boot-starter-tomcat -> tomcat-embed-websocket (openrewrite/rewrite#8336). Compare each coordinate's declared (requested) exclusions instead, intersected with its own clean closure to drop no-op exclusions, and apply the same relevant -exclusion logic to the direct declaration being evaluated for removal. Fixes openrewrite/rewrite#8336 --- .../RemoveRedundantDependencies.java | 118 ++++++++++-------- .../RemoveRedundantDependenciesTest.java | 104 +++++++++++++++ 2 files changed, 169 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java index 598cad70..4b1dc63b 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java @@ -52,7 +52,7 @@ public class RemoveRedundantDependencies extends ScanningRecipe effectiveRepos = withMavenCentral(repositories); try { // Get the resolved dependencies for compile scope (which includes most transitives) - ResolvedPom resolvedPom = resolvePom(gav, effectiveRepos, downloader, ctx); + Pom pom = downloader.download(gav.asGroupArtifactVersion(), null, null, effectiveRepos); + ResolvedPom resolvedPom = pom.resolve(emptyList(), downloader, effectiveRepos, ctx); ResolvedPom patchedPom = applyExclusions(resolvedPom, effectiveExclusions); List resolved = patchedPom.resolveDependencies(Scope.Compile, downloader, ctx); @@ -179,65 +180,73 @@ private void collectAllDependencies(ResolvedDependency dep, Set relevantExclusions(ResolvedDependency dep, List repositories, - MavenPomDownloader downloader, ExecutionContext ctx) { - Set exclusions = new HashSet<>(dep.getEffectiveExclusions()); - if (!exclusions.isEmpty()) { - exclusions.retainAll(dependencyClosure(dep.getGav(), repositories, downloader, ctx)); - } - return exclusions; - } + // Compares a coordinate's declared (requested) exclusions rather than its effective ones: within a large + // tree the effective set is corrupted by dependency mediation (an artifact excluded here may already have + // been pruned by a shared node elsewhere, so it silently drops out of getEffectiveExclusions). The declared + // exclusions are then intersected with the coordinate's own clean closure, dropping no-op exclusions that + // target artifacts the coordinate could never bring on its own. Comparing this set on the direct and the + // transitively-provided declaration tells us whether removing the direct one changes the effective classpath. + private static Set relevantExclusions(ResolvedDependency dep, List repositories, + MavenPomDownloader downloader, ExecutionContext ctx, + Map> closureCache) { + List requested = dep.getRequested() == null ? null : dep.getRequested().getExclusions(); + if (requested == null || requested.isEmpty()) { + return emptySet(); + } + Set exclusions = new HashSet<>(requested); + exclusions.retainAll(dependencyClosure(dep.getGav(), repositories, downloader, ctx, closureCache)); + return exclusions; + } - private Set dependencyClosure(ResolvedGroupArtifactVersion gav, List repositories, - MavenPomDownloader downloader, ExecutionContext ctx) { - return acc.closureCache.computeIfAbsent(gav, g -> { - Set closure = new HashSet<>(); - try { - for (ResolvedDependency d : resolvePom(g, repositories, downloader, ctx) - .resolveDependencies(Scope.Compile, downloader, ctx)) { - collectClosure(d, closure); - } - } catch (MavenDownloadingException | MavenDownloadingExceptions e) { - // Best-effort: an unresolvable closure leaves the exclusions unfiltered - } - return closure; - }); + private static Set dependencyClosure(ResolvedGroupArtifactVersion gav, List repositories, + MavenPomDownloader downloader, ExecutionContext ctx, + Map> closureCache) { + return closureCache.computeIfAbsent(gav, g -> { + Set closure = new HashSet<>(); + try { + for (ResolvedDependency d : resolvePom(g, repositories, downloader, ctx) + .resolveDependencies(Scope.Compile, downloader, ctx)) { + collectClosure(d, closure); + } + } catch (MavenDownloadingException | MavenDownloadingExceptions e) { + // Best-effort: an unresolvable closure leaves the exclusions unfiltered } + return closure; + }); + } - private ResolvedPom resolvePom(ResolvedGroupArtifactVersion gav, List repositories, - MavenPomDownloader downloader, ExecutionContext ctx) - throws MavenDownloadingException, MavenDownloadingExceptions { - List repos = withMavenCentral(repositories); - Pom pom = downloader.download(gav.asGroupArtifactVersion(), null, null, repos); - return pom.resolve(emptyList(), downloader, repos, ctx); - } + private static ResolvedPom resolvePom(ResolvedGroupArtifactVersion gav, List repositories, + MavenPomDownloader downloader, ExecutionContext ctx) + throws MavenDownloadingException, MavenDownloadingExceptions { + List repos = withMavenCentral(repositories); + Pom pom = downloader.download(gav.asGroupArtifactVersion(), null, null, repos); + return pom.resolve(emptyList(), downloader, repos, ctx); + } - private List withMavenCentral(List repositories) { - List effectiveRepos = new ArrayList<>(repositories); - if (effectiveRepos.stream().noneMatch(r -> r.getUri().contains("repo.maven.apache.org") || - r.getUri().contains("repo1.maven.org"))) { - effectiveRepos.add(MavenRepository.MAVEN_CENTRAL); - } - return effectiveRepos; - } + private static List withMavenCentral(List repositories) { + List effectiveRepos = new ArrayList<>(repositories); + if (effectiveRepos.stream().noneMatch(r -> r.getUri().contains("repo.maven.apache.org") || + r.getUri().contains("repo1.maven.org"))) { + effectiveRepos.add(MavenRepository.MAVEN_CENTRAL); + } + return effectiveRepos; + } - private void collectClosure(ResolvedDependency dep, Set closure) { - if (closure.add(dep.getGav().asGroupArtifact())) { - for (ResolvedDependency transitive : dep.getDependencies()) { - collectClosure(transitive, closure); - } - } + private static void collectClosure(ResolvedDependency dep, Set closure) { + if (closure.add(dep.getGav().asGroupArtifact())) { + for (ResolvedDependency transitive : dep.getDependencies()) { + collectClosure(transitive, closure); } - }; + } } @Override @@ -268,6 +277,7 @@ public TreeVisitor getVisitor(Accumulator acc) { String projectId = gradle.getGroup() + ":" + gradle.getName(); Map> scopeToTransitives = acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); + MavenPomDownloader downloader = new MavenPomDownloader(ctx); for (GradleDependencyConfiguration conf : gradle.getConfigurations()) { Set transitives = getCompatibleGradleTransitives( @@ -279,7 +289,7 @@ public TreeVisitor getVisitor(Accumulator acc) { for (ResolvedDependency dep : conf.getResolved()) { if (dep.isDirect() && doesNotMatchArguments(dep) && - isRedundant(dep, transitives)) { + isRedundant(dep, transitives, gradle.getMavenRepositories(), downloader, ctx)) { // This direct dependency is transitively provided, remove it // Don't specify configuration - Gradle's resolved config names differ from declaration names result = new RemoveDependency( @@ -295,6 +305,7 @@ public TreeVisitor getVisitor(Accumulator acc) { String projectId = maven.getPom().getGroupId() + ":" + maven.getPom().getArtifactId(); Map> scopeToTransitives = acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); + MavenPomDownloader downloader = new MavenPomDownloader(ctx); // A direct dependency appears under every scope bucket it is visible in; evaluate each // one once using its own effective scope so a wider transitive scope does not falsely @@ -308,7 +319,7 @@ public TreeVisitor getVisitor(Accumulator acc) { Scope depScope = Scope.fromName(dep.getRequested().getScope()); Set transitives = scopeToTransitives.getOrDefault( depScope.name().toLowerCase(), emptySet()); - if (isRedundant(dep, transitives)) { + if (isRedundant(dep, transitives, maven.getPom().getRepositories(), downloader, ctx)) { // This direct dependency is transitively provided at the same scope and // with the same exclusions, remove it. result = new RemoveDependency( @@ -326,8 +337,9 @@ private boolean doesNotMatchArguments(ResolvedDependency dep) { !StringUtils.matchesGlob(dep.getArtifactId(), artifactId); } - private boolean isRedundant(ResolvedDependency dep, Set transitives) { - Set depExclusions = new HashSet<>(dep.getEffectiveExclusions()); + private boolean isRedundant(ResolvedDependency dep, Set transitives, + List repositories, MavenPomDownloader downloader, ExecutionContext ctx) { + Set depExclusions = relevantExclusions(dep, repositories, downloader, ctx, acc.closureCache); for (TransitiveDependency transitive : transitives) { ResolvedGroupArtifactVersion gav = transitive.getGav(); if (dep.getGroupId().equals(gav.getGroupId()) && diff --git a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java index 084e035f..47d2fb84 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java @@ -657,6 +657,110 @@ void removesTomcatEmbedCoreWhenExclusionsMatchTransitive() { ); } + @Test + void keepsWebsocketWhenDirectHasNoExclusionButTransitiveDoes() { + // https://github.com/openrewrite/rewrite/issues/8336 + // spring-boot-starter-tomcat excludes tomcat-annotations-api from tomcat-embed-websocket, which still + // brings its own tomcat-embed-core. A direct websocket with no exclusion is therefore NOT equivalent to + // the transitively-provided one and must be kept, even though websocket's effective-exclusion set is + // emptied by mediation (annotations-api is pruned at the shared embed-core node first). + rewriteRun( + spec -> spec.recipe(new RemoveRedundantDependencies( + "org.springframework.boot", "spring-boot-starter-web")), + //language=xml + pomXml( + """ + + 4.0.0 + com.sample + sample + 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.apache.tomcat.embed + tomcat-embed-websocket + + + + """ + ) + ); + } + + @Test + void removesWebsocketWhenDirectRepeatsTheTransitiveExclusion() { + // https://github.com/openrewrite/rewrite/issues/8336 + // The mirror image: a direct websocket that repeats the same tomcat-annotations-api exclusion the + // starter applies is truly equivalent to the transitively-provided one and should be removed. + rewriteRun( + spec -> spec.recipe(new RemoveRedundantDependencies( + "org.springframework.boot", "spring-boot-starter-web")), + //language=xml + pomXml( + """ + + 4.0.0 + com.sample + sample + 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.apache.tomcat.embed + tomcat-embed-websocket + + + org.apache.tomcat + tomcat-annotations-api + + + + + + """, + """ + + 4.0.0 + com.sample + sample + 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + + org.springframework.boot + spring-boot-starter-web + + + + """ + ) + ); + } + @Test void removeRedundantGradleDependency() { rewriteRun( From fe1db7321e2abf5a6a30331ef3441f464521ce99 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 28 Jul 2026 13:08:49 +0200 Subject: [PATCH 4/9] Resolve dependency closures lazily when comparing exclusions Closure resolution was eager: every visited node carrying an inherited exclusion triggered a POM download, resolve and compile-scope graph resolution. Since ancestor exclusions are propagated into each child's requested dependency, the "no exclusions" early-out rarely fired, so a spring-boot-starter tree performed ~38 extra resolutions of which one was ever consulted. TransitiveDependency now carries declared exclusions, and the closure intersection is deferred into isRedundant, where it runs only for a coordinate that actually matched and only when the two declared sets differ. Both sides share a single closure for that coordinate. Also collapse the (repositories, downloader, ctx, cache) tuple into a ClosureResolver, dedupe the two recursive dependency walks, key the cache on GroupArtifactVersion so it is not repository-sensitive, and correct the description and comments that described the abandoned mechanism. --- .../RemoveRedundantDependencies.java | 151 ++++++++++-------- .../RemoveRedundantDependenciesTest.java | 5 +- 2 files changed, 85 insertions(+), 71 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java index 4b1dc63b..784fa0bc 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java @@ -29,6 +29,7 @@ import org.openrewrite.maven.tree.*; import java.util.*; +import java.util.function.Consumer; import static java.util.Collections.*; @@ -52,14 +53,14 @@ public class RemoveRedundantDependencies extends ScanningRecipe scope/configuration -> Set of transitive dependencies Map>> transitivesByProjectAndScope; - // Cache of each coordinate's own clean dependency closure, shared across all source files in the run - Map> closureCache; + // Run-scoped: every closure miss costs a POM download and resolve + Map> closureCache; } @Value @@ -147,18 +148,17 @@ private void resolveTransitivesFromPom( MavenPomDownloader downloader, ExecutionContext ctx, Set transitives) { - List effectiveRepos = withMavenCentral(repositories); try { // Get the resolved dependencies for compile scope (which includes most transitives) - Pom pom = downloader.download(gav.asGroupArtifactVersion(), null, null, effectiveRepos); - ResolvedPom resolvedPom = pom.resolve(emptyList(), downloader, effectiveRepos, ctx); + ResolvedPom resolvedPom = resolvePom( + gav.asGroupArtifactVersion(), withMavenCentral(repositories), downloader, ctx); ResolvedPom patchedPom = applyExclusions(resolvedPom, effectiveExclusions); - List resolved = patchedPom.resolveDependencies(Scope.Compile, downloader, ctx); // Collect all dependencies (both direct and transitive of the parent) Set visited = new HashSet<>(); - for (ResolvedDependency dep : resolved) { - collectAllDependencies(dep, transitives, visited, effectiveRepos, downloader, ctx); + for (ResolvedDependency dep : patchedPom.resolveDependencies(Scope.Compile, downloader, ctx)) { + walkDependencies(dep, visited, d -> + transitives.add(new TransitiveDependency(d.getGav(), declaredExclusions(d)))); } } catch (MavenDownloadingException | MavenDownloadingExceptions e) { // If we can't download/resolve the POM, fall back to not detecting redundancies @@ -174,62 +174,32 @@ private ResolvedPom applyExclusions(ResolvedPom resolvedPom, List .anyMatch(e -> e.getGroupId().equals(d.getGroupId()) && e.getArtifactId().equals(d.getArtifactId()))); return patchedPom; } - - private void collectAllDependencies(ResolvedDependency dep, Set transitives, - Set visited, List repositories, - MavenPomDownloader downloader, ExecutionContext ctx) { - if (visited.add(dep.getGav())) { - transitives.add(new TransitiveDependency(dep.getGav(), - relevantExclusions(dep, repositories, downloader, ctx, acc.closureCache))); - for (ResolvedDependency transitive : dep.getDependencies()) { - collectAllDependencies(transitive, transitives, visited, repositories, downloader, ctx); - } - } - } }; } - // Compares a coordinate's declared (requested) exclusions rather than its effective ones: within a large - // tree the effective set is corrupted by dependency mediation (an artifact excluded here may already have - // been pruned by a shared node elsewhere, so it silently drops out of getEffectiveExclusions). The declared - // exclusions are then intersected with the coordinate's own clean closure, dropping no-op exclusions that - // target artifacts the coordinate could never bring on its own. Comparing this set on the direct and the - // transitively-provided declaration tells us whether removing the direct one changes the effective classpath. - private static Set relevantExclusions(ResolvedDependency dep, List repositories, - MavenPomDownloader downloader, ExecutionContext ctx, - Map> closureCache) { - List requested = dep.getRequested() == null ? null : dep.getRequested().getExclusions(); - if (requested == null || requested.isEmpty()) { - return emptySet(); - } - Set exclusions = new HashSet<>(requested); - exclusions.retainAll(dependencyClosure(dep.getGav(), repositories, downloader, ctx, closureCache)); - return exclusions; + // Declared (requested) exclusions rather than effective ones: within a large tree the effective set is + // corrupted by dependency mediation, as an artifact excluded here may already have been pruned by a shared + // node elsewhere, in which case the exclusion never fires and drops out of getEffectiveExclusions(). + private static Set declaredExclusions(ResolvedDependency dep) { + Dependency requested = dep.getRequested(); + List exclusions = requested == null ? null : requested.getExclusions(); + return exclusions == null || exclusions.isEmpty() ? emptySet() : new HashSet<>(exclusions); } - private static Set dependencyClosure(ResolvedGroupArtifactVersion gav, List repositories, - MavenPomDownloader downloader, ExecutionContext ctx, - Map> closureCache) { - return closureCache.computeIfAbsent(gav, g -> { - Set closure = new HashSet<>(); - try { - for (ResolvedDependency d : resolvePom(g, repositories, downloader, ctx) - .resolveDependencies(Scope.Compile, downloader, ctx)) { - collectClosure(d, closure); - } - } catch (MavenDownloadingException | MavenDownloadingExceptions e) { - // Best-effort: an unresolvable closure leaves the exclusions unfiltered + private static void walkDependencies(ResolvedDependency dep, Set visited, + Consumer action) { + if (visited.add(dep.getGav())) { + action.accept(dep); + for (ResolvedDependency transitive : dep.getDependencies()) { + walkDependencies(transitive, visited, action); } - return closure; - }); + } } - private static ResolvedPom resolvePom(ResolvedGroupArtifactVersion gav, List repositories, + private static ResolvedPom resolvePom(GroupArtifactVersion gav, List repos, MavenPomDownloader downloader, ExecutionContext ctx) throws MavenDownloadingException, MavenDownloadingExceptions { - List repos = withMavenCentral(repositories); - Pom pom = downloader.download(gav.asGroupArtifactVersion(), null, null, repos); - return pom.resolve(emptyList(), downloader, repos, ctx); + return downloader.download(gav, null, null, repos).resolve(emptyList(), downloader, repos, ctx); } private static List withMavenCentral(List repositories) { @@ -241,11 +211,36 @@ private static List withMavenCentral(List repo return effectiveRepos; } - private static void collectClosure(ResolvedDependency dep, Set closure) { - if (closure.add(dep.getGav().asGroupArtifact())) { - for (ResolvedDependency transitive : dep.getDependencies()) { - collectClosure(transitive, closure); - } + // Resolves what a coordinate can bring in on its own, so that exclusions targeting artifacts it could never + // have brought can be discarded before comparing two declarations. + private static class ClosureResolver { + private final List repositories; + private final MavenPomDownloader downloader; + private final ExecutionContext ctx; + private final Map> cache; + + private ClosureResolver(List repositories, ExecutionContext ctx, + Map> cache) { + this.repositories = withMavenCentral(repositories); + this.downloader = new MavenPomDownloader(ctx); + this.ctx = ctx; + this.cache = cache; + } + + private Set closureOf(GroupArtifactVersion gav) { + return cache.computeIfAbsent(gav, g -> { + Set closure = new HashSet<>(); + Set visited = new HashSet<>(); + try { + for (ResolvedDependency d : resolvePom(g, repositories, downloader, ctx) + .resolveDependencies(Scope.Compile, downloader, ctx)) { + walkDependencies(d, visited, r -> closure.add(r.getGav().asGroupArtifact())); + } + } catch (MavenDownloadingException | MavenDownloadingExceptions e) { + // Best-effort: an empty closure makes every exclusion look like a no-op + } + return closure; + }); } } @@ -277,7 +272,7 @@ public TreeVisitor getVisitor(Accumulator acc) { String projectId = gradle.getGroup() + ":" + gradle.getName(); Map> scopeToTransitives = acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); - MavenPomDownloader downloader = new MavenPomDownloader(ctx); + ClosureResolver resolver = new ClosureResolver(gradle.getMavenRepositories(), ctx, acc.closureCache); for (GradleDependencyConfiguration conf : gradle.getConfigurations()) { Set transitives = getCompatibleGradleTransitives( @@ -289,7 +284,7 @@ public TreeVisitor getVisitor(Accumulator acc) { for (ResolvedDependency dep : conf.getResolved()) { if (dep.isDirect() && doesNotMatchArguments(dep) && - isRedundant(dep, transitives, gradle.getMavenRepositories(), downloader, ctx)) { + isRedundant(dep, transitives, resolver)) { // This direct dependency is transitively provided, remove it // Don't specify configuration - Gradle's resolved config names differ from declaration names result = new RemoveDependency( @@ -305,7 +300,7 @@ public TreeVisitor getVisitor(Accumulator acc) { String projectId = maven.getPom().getGroupId() + ":" + maven.getPom().getArtifactId(); Map> scopeToTransitives = acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); - MavenPomDownloader downloader = new MavenPomDownloader(ctx); + ClosureResolver resolver = new ClosureResolver(maven.getPom().getRepositories(), ctx, acc.closureCache); // A direct dependency appears under every scope bucket it is visible in; evaluate each // one once using its own effective scope so a wider transitive scope does not falsely @@ -319,7 +314,7 @@ public TreeVisitor getVisitor(Accumulator acc) { Scope depScope = Scope.fromName(dep.getRequested().getScope()); Set transitives = scopeToTransitives.getOrDefault( depScope.name().toLowerCase(), emptySet()); - if (isRedundant(dep, transitives, maven.getPom().getRepositories(), downloader, ctx)) { + if (isRedundant(dep, transitives, resolver)) { // This direct dependency is transitively provided at the same scope and // with the same exclusions, remove it. result = new RemoveDependency( @@ -338,20 +333,38 @@ private boolean doesNotMatchArguments(ResolvedDependency dep) { } private boolean isRedundant(ResolvedDependency dep, Set transitives, - List repositories, MavenPomDownloader downloader, ExecutionContext ctx) { - Set depExclusions = relevantExclusions(dep, repositories, downloader, ctx, acc.closureCache); + ClosureResolver resolver) { + Set depExclusions = declaredExclusions(dep); + Set closure = null; for (TransitiveDependency transitive : transitives) { ResolvedGroupArtifactVersion gav = transitive.getGav(); if (dep.getGroupId().equals(gav.getGroupId()) && dep.getArtifactId().equals(gav.getArtifactId()) && - dep.getVersion().equals(gav.getVersion()) && - depExclusions.equals(transitive.getExclusions())) { - return true; + dep.getVersion().equals(gav.getVersion())) { + if (depExclusions.equals(transitive.getExclusions())) { + return true; + } + // Differing declarations can still be equivalent once exclusions that the coordinate + // could never have honoured anyway are discarded. Both sides share one closure, and + // every candidate here has the same coordinate, so this resolves at most once. + if (closure == null) { + closure = resolver.closureOf(gav.asGroupArtifactVersion()); + } + if (retainClosure(depExclusions, closure).equals( + retainClosure(transitive.getExclusions(), closure))) { + return true; + } } } return false; } + private Set retainClosure(Set exclusions, Set closure) { + Set relevant = new HashSet<>(exclusions); + relevant.retainAll(closure); + return relevant; + } + private Set getCompatibleGradleTransitives( Map> scopeToTransitives, String targetScope) { diff --git a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java index 47d2fb84..00162261 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; import static org.openrewrite.gradle.Assertions.buildGradle; @@ -657,9 +658,9 @@ void removesTomcatEmbedCoreWhenExclusionsMatchTransitive() { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/8336") @Test void keepsWebsocketWhenDirectHasNoExclusionButTransitiveDoes() { - // https://github.com/openrewrite/rewrite/issues/8336 // spring-boot-starter-tomcat excludes tomcat-annotations-api from tomcat-embed-websocket, which still // brings its own tomcat-embed-core. A direct websocket with no exclusion is therefore NOT equivalent to // the transitively-provided one and must be kept, even though websocket's effective-exclusion set is @@ -697,9 +698,9 @@ void keepsWebsocketWhenDirectHasNoExclusionButTransitiveDoes() { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/8336") @Test void removesWebsocketWhenDirectRepeatsTheTransitiveExclusion() { - // https://github.com/openrewrite/rewrite/issues/8336 // The mirror image: a direct websocket that repeats the same tomcat-annotations-api exclusion the // starter applies is truly equivalent to the transitively-provided one and should be removed. rewriteRun( From fbd2268f7bd4ef7138d80c4ce70f33954f231725 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 28 Jul 2026 16:37:48 +0200 Subject: [PATCH 5/9] Drop redundant null check on non-null getRequested() --- .../java/dependencies/RemoveRedundantDependencies.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java index 784fa0bc..88407ae9 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java @@ -181,8 +181,7 @@ private ResolvedPom applyExclusions(ResolvedPom resolvedPom, List // corrupted by dependency mediation, as an artifact excluded here may already have been pruned by a shared // node elsewhere, in which case the exclusion never fires and drops out of getEffectiveExclusions(). private static Set declaredExclusions(ResolvedDependency dep) { - Dependency requested = dep.getRequested(); - List exclusions = requested == null ? null : requested.getExclusions(); + List exclusions = dep.getRequested().getExclusions(); return exclusions == null || exclusions.isEmpty() ? emptySet() : new HashSet<>(exclusions); } From ab6b8fef34ed1df32f975d69d67a50e5c57bf90b Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 28 Jul 2026 17:08:06 +0200 Subject: [PATCH 6/9] Name the closure cache for what it holds --- .../java/dependencies/RemoveRedundantDependencies.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java index 88407ae9..0482a16c 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java @@ -59,8 +59,9 @@ public class RemoveRedundantDependencies extends ScanningRecipe scope/configuration -> Set of transitive dependencies Map>> transitivesByProjectAndScope; - // Run-scoped: every closure miss costs a POM download and resolve - Map> closureCache; + // Map from coordinate -> group:artifacts it pulls in on its own, at compile scope and versionless + // because exclusions match on group:artifact. Run-scoped, as every miss costs a POM download. + Map> compileClosureByGav; } @Value @@ -271,7 +272,7 @@ public TreeVisitor getVisitor(Accumulator acc) { String projectId = gradle.getGroup() + ":" + gradle.getName(); Map> scopeToTransitives = acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); - ClosureResolver resolver = new ClosureResolver(gradle.getMavenRepositories(), ctx, acc.closureCache); + ClosureResolver resolver = new ClosureResolver(gradle.getMavenRepositories(), ctx, acc.compileClosureByGav); for (GradleDependencyConfiguration conf : gradle.getConfigurations()) { Set transitives = getCompatibleGradleTransitives( @@ -299,7 +300,7 @@ public TreeVisitor getVisitor(Accumulator acc) { String projectId = maven.getPom().getGroupId() + ":" + maven.getPom().getArtifactId(); Map> scopeToTransitives = acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); - ClosureResolver resolver = new ClosureResolver(maven.getPom().getRepositories(), ctx, acc.closureCache); + ClosureResolver resolver = new ClosureResolver(maven.getPom().getRepositories(), ctx, acc.compileClosureByGav); // A direct dependency appears under every scope bucket it is visible in; evaluate each // one once using its own effective scope so a wider transitive scope does not falsely From 0065d629a368388aa2c2961c172854d76bea79c1 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 28 Jul 2026 17:08:06 +0200 Subject: [PATCH 7/9] Regenerate recipes.csv --- .../resources/META-INF/rewrite/recipes.csv | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index 82d291e6..d3eae2aa 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -1,30 +1,29 @@ ecosystem,packageName,name,displayName,description,recipeCount,category1,category2,category3,category1Description,category2Description,category3Description,options,dataTables -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.UpgradeTransitiveDependencyVersion,Upgrade transitive Gradle or Maven dependencies,"Upgrades the version of a transitive dependency in a Maven pom.xml or Gradle build.gradle. Leaves direct dependencies unmodified. Can be paired with the regular Upgrade Dependency Version recipe to upgrade a dependency everywhere, regardless of whether it is direct or transitive.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.apache.logging.log4j:ARTIFACT_ID:VERSION'."",""example"":""org.apache.logging.log4j"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.apache.logging.log4j:log4j-bom:VERSION'."",""example"":""log4j-bom"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""latest.release"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""An optional scope to use for the dependency management tag. Relevant only to Maven."",""example"":""import"",""valid"":[""import"",""runtime"",""provided"",""test""]},{""name"":""type"",""type"":""String"",""displayName"":""Type"",""description"":""An optional type to use for the dependency management tag. Relevant only to Maven builds."",""example"":""pom"",""valid"":[""jar"",""pom"",""war""]},{""name"":""classifier"",""type"":""String"",""displayName"":""Classifier"",""description"":""An optional classifier to use for the dependency management tag. Relevant only to Maven."",""example"":""test""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select 29.0-jre"",""example"":""-jre""},{""name"":""because"",""type"":""String"",""displayName"":""Because"",""description"":""The reason for upgrading the transitive dependency. For example, we could be responding to a vulnerability."",""example"":""CVE-2021-1234""},{""name"":""releasesOnly"",""type"":""Boolean"",""displayName"":""Releases only"",""description"":""Whether to exclude snapshots from consideration when using a semver selector""},{""name"":""onlyIfUsing"",""type"":""String"",""displayName"":""Only if using glob expression for group:artifact"",""description"":""Only add managed dependencies to projects having a dependency matching the expression."",""example"":""org.apache.logging.log4j:log4j*""},{""name"":""addToRootPom"",""type"":""Boolean"",""displayName"":""Add to the root pom"",""description"":""Add to the root pom where root is the eldest parent of the pom within the source set.""}]", +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.AddDependency,Add Gradle or Maven dependency,"For a Gradle project, add a gradle dependency to a `build.gradle` file in the correct configuration based on where it is used. Or For a maven project, Add a Maven dependency to a `pom.xml` file in the correct scope based on where it is used.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`"",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""29.X""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example, Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""onlyIfUsing"",""type"":""String"",""displayName"":""Only if using"",""description"":""Used to determine if the dependency will be added and in which scope it should be placed."",""example"":""org.junit.jupiter.api.*""},{""name"":""classifier"",""type"":""String"",""displayName"":""Classifier"",""description"":""A classifier to add. Commonly used to select variants of a library."",""example"":""test""},{""name"":""familyPattern"",""type"":""String"",""displayName"":""Family pattern"",""description"":""A pattern, applied to groupIds, used to determine which other dependencies should have aligned version numbers. Accepts '*' as a wildcard character."",""example"":""com.fasterxml.jackson*""},{""name"":""extension"",""type"":""String"",""displayName"":""Extension"",""description"":""For Gradle only, The extension of the dependency to add. If omitted Gradle defaults to assuming the type is \""jar\""."",""example"":""jar""},{""name"":""configuration"",""type"":""String"",""displayName"":""Gradle configuration"",""description"":""The Gradle dependency configuration name within which to place the dependency. When omitted the configuration will be determined by the Maven scope parameter. If that parameter is also omitted, configuration will be determined based on where types matching `onlyIfUsing` appear in source code."",""example"":""implementation""},{""name"":""scope"",""type"":""String"",""displayName"":""Maven scope"",""description"":""The Maven scope within which to place the dependency. When omitted scope will be determined based on where types matching `onlyIfUsing` appear in source code."",""example"":""runtime"",""valid"":[""compile"",""provided"",""runtime"",""test""]},{""name"":""releasesOnly"",""type"":""Boolean"",""displayName"":""Releases only"",""description"":""For Maven only, Whether to exclude snapshots from consideration when using a semver selector""},{""name"":""type"",""type"":""String"",""displayName"":""Type"",""description"":""For Maven only, The type of dependency to add. If omitted Maven defaults to assuming the type is \""jar\""."",""example"":""jar"",""valid"":[""jar"",""pom"",""war""]},{""name"":""optional"",""type"":""Boolean"",""displayName"":""Optional"",""description"":""Set the value of the `` tag. No `` tag will be added when this is `null`.""},{""name"":""acceptTransitive"",""type"":""Boolean"",""displayName"":""Accept transitive"",""description"":""Default false. If enabled, the dependency will not be added if it is already on the classpath as a transitive dependency."",""example"":""true""}]", maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.ChangeDependency,Change Gradle or Maven dependency,"Change the group ID, artifact ID, and/or the version of a specified Gradle or Maven dependency.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""oldGroupId"",""type"":""String"",""displayName"":""Old group ID"",""description"":""The old group ID to replace. The group ID is the first part of a dependency coordinate 'com.google.guava:guava:VERSION'. Supports glob expressions."",""example"":""org.openrewrite.recipe"",""required"":true},{""name"":""oldArtifactId"",""type"":""String"",""displayName"":""Old artifact ID"",""description"":""The old artifact ID to replace. The artifact ID is the second part of a dependency coordinate 'com.google.guava:guava:VERSION'. Supports glob expressions."",""example"":""rewrite-testing-frameworks"",""required"":true},{""name"":""newGroupId"",""type"":""String"",""displayName"":""New group ID"",""description"":""The new group ID to use. Defaults to the existing group ID."",""example"":""corp.internal.openrewrite.recipe""},{""name"":""newArtifactId"",""type"":""String"",""displayName"":""New artifact ID"",""description"":""The new artifact ID to use. Defaults to the existing artifact ID."",""example"":""rewrite-testing-frameworks""},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""29.X""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""overrideManagedVersion"",""type"":""Boolean"",""displayName"":""Override managed version"",""description"":""If the new dependency has a managed version, this flag can be used to explicitly set the version on the dependency. The default for this flag is `false`.""},{""name"":""changeManagedDependency"",""type"":""Boolean"",""displayName"":""Update dependency management"",""description"":""Also update the dependency management section. The default for this flag is `true`.""}]", +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.DependencyInsight,Dependency insight for Gradle and Maven,"Finds dependencies, including transitive dependencies, in both Gradle and Maven projects. Matches within all Gradle dependency configurations and Maven scopes.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupIdPattern"",""type"":""String"",""displayName"":""Group pattern"",""description"":""Group ID glob pattern used to match dependencies."",""example"":""com.fasterxml.jackson*"",""required"":true},{""name"":""artifactIdPattern"",""type"":""String"",""displayName"":""Artifact pattern"",""description"":""Artifact ID glob pattern used to match dependencies."",""example"":""jackson-*"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used. All versions are searched by default."",""example"":""1.x""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Match dependencies with the specified Maven scope. All scopes are searched by default."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided"",""system""]}]","[{""name"":""org.openrewrite.maven.table.DependenciesInUse"",""displayName"":""Dependencies in use"",""instanceName"":""Dependencies in use"",""description"":""Direct and transitive dependencies in use."",""columns"":[{""name"":""projectName"",""type"":""String"",""displayName"":""Project name"",""description"":""The name of the project that contains the dependency.""},{""name"":""sourceSet"",""type"":""String"",""displayName"":""Source set"",""description"":""The source set that contains the dependency.""},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The resolved version.""},{""name"":""datedSnapshotVersion"",""type"":""String"",""displayName"":""Dated snapshot version"",""description"":""The resolved dated snapshot version or `null` if this dependency is not a snapshot.""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Dependency scope. This will be `compile` if the dependency is direct and a scope is not explicitly specified in the POM.""},{""name"":""count"",""type"":""Integer"",""displayName"":""Count"",""description"":""How many times does this dependency appear.""}]}]" +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.DependencyList,Dependency report,Emits a data table detailing all Gradle and Maven dependencies. This recipe makes no changes to any source file.,1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""scope"",""type"":""Scope"",""displayName"":""Scope"",""description"":""The scope of the dependencies to include in the report.Defaults to \""Compile\"""",""example"":""Compile"",""valid"":[""Compile"",""Runtime"",""TestRuntime""]},{""name"":""includeTransitive"",""type"":""boolean"",""displayName"":""Include transitive dependencies"",""description"":""Whether or not to include transitive dependencies in the report. Defaults to including only direct dependencies.Defaults to false."",""example"":""true"",""value"":false},{""name"":""validateResolvable"",""type"":""boolean"",""displayName"":""Validate dependencies are resolvable"",""description"":""When enabled the recipe will attempt to download every dependency it encounters, reporting on any failures. This can be useful for identifying dependencies that have become unavailable since an LST was produced.Defaults to false."",""example"":""true"",""valid"":[""true"",""false""],""value"":false}]","[{""name"":""org.openrewrite.java.dependencies.table.DependencyListReport"",""displayName"":""Dependency report"",""instanceName"":""Dependency report"",""description"":""Lists all Gradle and Maven dependencies"",""columns"":[{""name"":""buildTool"",""type"":""String"",""displayName"":""Build tool"",""description"":""The build tool used to manage dependencies (Gradle or Maven).""},{""name"":""path"",""type"":""String"",""displayName"":""Path"",""description"":""Path to the build file declaring the dependency""},{""name"":""groupId"",""type"":""String"",""displayName"":""Group id"",""description"":""The Group ID of the Gradle project or Maven module requesting the dependency.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The Artifact ID of the Gradle project or Maven module requesting the dependency.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of Gradle project or Maven module requesting the dependency.""},{""name"":""dependencyGroupId"",""type"":""String"",""displayName"":""Dependency group id"",""description"":""The Group ID of the dependency.""},{""name"":""dependencyArtifactId"",""type"":""String"",""displayName"":""Dependency artifact id"",""description"":""The Artifact ID of the dependency.""},{""name"":""dependencyVersion"",""type"":""String"",""displayName"":""Dependency version"",""description"":""The version of the dependency.""},{""name"":""direct"",""type"":""boolean"",""displayName"":""Direct Dependency"",""description"":""When `true` the project directly depends on the dependency. When `false` the project depends on the dependency transitively through at least one direct dependency.""},{""name"":""resolutionFailure"",""type"":""String"",""displayName"":""Resolution failure"",""description"":""The reason why the dependency could not be resolved. Blank when resolution was not attempted.""}]},{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.DependencyResolutionDiagnostic,Dependency resolution diagnostic,"Recipes which manipulate dependencies must be able to successfully access the artifact repositories and resolve dependencies from them. This recipe produces two data tables used to understand the state of dependency resolution. + +The Repository accessibility report lists all the artifact repositories known to the project and whether respond to network access. The network access is attempted while the recipe is run and so is representative of current conditions. + +The Gradle dependency configuration errors lists all the dependency configurations that failed to resolve one or more dependencies when the project was parsed. This is representative of conditions at the time the LST was parsed.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The group ID of a dependency to attempt to download from the repository. Default value is \""com.fasterxml.jackson.core\"". If this dependency is not found in the repository the error will be noted in the report. There is no need to specify an alternate value for this parameter unless the repository is known not to contain jackson-core."",""example"":""com.fasterxml.jackson.core""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The artifact ID of a dependency to attempt to download from the repository. Default value is \""jackson-core\"". If this dependency is not found in the repository the error will be noted in the report. There is no need to specify an alternate value for this parameter unless the repository is known not to contain jackson-core."",""example"":""jackson-core""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of a dependency to attempt to download from the repository. Default value is \""2.16.0\"". If this dependency is not found in the repository the error will be noted in the report. There is no need to specify an alternate value for this parameter unless the repository is known not to contain jackson-core."",""example"":""2.16.0""}]","[{""name"":""org.openrewrite.java.dependencies.table.RepositoryAccessibilityReport"",""displayName"":""Repository accessibility report"",""instanceName"":""Repository accessibility report"",""description"":""Listing of all dependency repositories and whether they are accessible."",""columns"":[{""name"":""uri"",""type"":""String"",""displayName"":""Repository URI"",""description"":""The URI of the repository""},{""name"":""pingExceptionType"",""type"":""String"",""displayName"":""Ping exception type"",""description"":""Empty if the repository responded to a ping. Otherwise, the type of exception encountered when attempting to access the repository.""},{""name"":""pingExceptionMessage"",""type"":""String"",""displayName"":""Ping error message"",""description"":""Empty if the repository was accessible. Otherwise, the error message encountered when attempting to access the repository.""},{""name"":""pingHttpCode"",""type"":""Integer"",""displayName"":""Ping HTTP code"",""description"":""The HTTP response code returned by the repository. May be empty for non-HTTP repositories.""},{""name"":""dependencyResolveExceptionType"",""type"":""String"",""displayName"":""Dependency resolution exception type"",""description"":""Empty if ping failed, or if the repository successfully downloaded the specified dependency. Otherwise, the type of exception encountered when attempting to access the repository.""},{""name"":""dependencyResolveExceptionMessage"",""type"":""String"",""displayName"":""Dependency resolution error message"",""description"":""Empty if ping failed, or if the repository successfully downloaded the specified dependency. Otherwise, the error message encountered when attempting to access the repository.""}]},{""name"":""org.openrewrite.java.dependencies.table.GradleDependencyConfigurationErrors"",""displayName"":""Gradle dependency configuration errors"",""instanceName"":""Gradle dependency configuration errors"",""description"":""Records Gradle dependency configurations which failed to resolve during parsing. Partial success/failure is common, a failure in this list does not mean that every dependency failed to resolve."",""columns"":[{""name"":""projectPath"",""type"":""String"",""displayName"":""Project path"",""description"":""The path of the project which contains the dependency configuration.""},{""name"":""configurationName"",""type"":""String"",""displayName"":""Configuration name"",""description"":""The name of the dependency configuration which failed to resolve.""},{""name"":""exceptionType"",""type"":""String"",""displayName"":""Exception type"",""description"":""The type of exception encountered when attempting to resolve the dependency configuration.""},{""name"":""exceptionMessage"",""type"":""String"",""displayName"":""Error message"",""description"":""The error message encountered when attempting to resolve the dependency configuration.""}]}]" +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.FindDependency,Find Maven and Gradle dependencies,Finds direct dependencies declared in Maven and Gradle build files. This does *not* search transitive dependencies. To detect both direct and transitive dependencies use `org.openrewrite.java.dependencies.DependencyInsight` This recipe works for both Maven and Gradle projects.,1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate identifying its publisher."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate uniquely identifying it among artifacts from the same publisher."",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""3.0.0""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example, setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""configuration"",""type"":""String"",""displayName"":""Configuration"",""description"":""For Gradle only, the dependency configuration to search for dependencies in. If omitted then all configurations will be searched."",""example"":""api""}]", maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.FindRepositoryOrder,Maven repository order,Determine the order in which dependencies will be resolved for each `pom.xml` or `build.gradle` based on its defined repositories and effective settings.,1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenRepositoryOrder"",""displayName"":""Maven repository order"",""instanceName"":""Maven repository order"",""description"":""The order in which dependencies will be resolved for each `pom.xml` based on its defined repositories and effective `settings.xml`."",""columns"":[{""name"":""id"",""type"":""String"",""displayName"":""Repository ID"",""description"":""The ID of the repository. Note that projects may define the same physical repository with different IDs.""},{""name"":""uri"",""type"":""String"",""displayName"":""Repository URI"",""description"":""The URI of the repository.""},{""name"":""knownToExist"",""type"":""boolean"",""displayName"":""Known to exist"",""description"":""If the repository is provably reachable. If false, does not guarantee that the repository does not exist.""},{""name"":""rank"",""type"":""int"",""displayName"":""Rank"",""description"":""The index order of this repository in the repository list.""}]}]" -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RemoveRedundantDependencies,Remove redundant explicit dependencies,"Remove explicit dependencies that are already provided transitively by a specified dependency. This recipe downloads and resolves the parent dependency's POM to determine its true transitive dependencies, allowing it to detect redundancies even when both dependencies are explicitly declared.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""com.fasterxml.jackson.core"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""jackson-databind"",""required"":true}]", +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RelocatedDependencyCheck,Find relocated dependencies,"Find Maven and Gradle dependencies and Maven plugins that have relocated to a new `groupId` or `artifactId`. Relocation information comes from the [oga-maven-plugin](https://github.com/jonathanlermitage/oga-maven-plugin/) maintained by Jonathan Lermitage, Filipe Roque and others. + +This recipe makes no changes to any source file by default. Add `changeDependencies=true` to change dependencies, but note that you might need to run additional recipes to update imports and adopt other breaking changes.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""changeDependencies"",""type"":""Boolean"",""displayName"":""Change dependencies"",""description"":""Whether to change dependencies to their relocated groupId and artifactId.""}]","[{""name"":""org.openrewrite.java.dependencies.table.RelocatedDependencyReport"",""displayName"":""Relocated dependencies"",""instanceName"":""Relocated dependencies"",""description"":""A list of dependencies in use that have relocated."",""columns"":[{""name"":""dependencyGroupId"",""type"":""String"",""displayName"":""Dependency group id"",""description"":""The Group ID of the dependency in use.""},{""name"":""dependencyArtifactId"",""type"":""String"",""displayName"":""Dependency artifact id"",""description"":""The Artifact ID of the dependency in use.""},{""name"":""relocatedGroupId"",""type"":""String"",""displayName"":""Relocated dependency group id"",""description"":""The Group ID of the relocated dependency.""},{""name"":""relocatedArtifactId"",""type"":""String"",""displayName"":""Relocated ependency artifact id"",""description"":""The Artifact ID of the relocated dependency.""},{""name"":""context"",""type"":""String"",""displayName"":""Context"",""description"":""Context for the relocation, if any.""}]}]" +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RemoveDependency,Remove a Gradle or Maven dependency,"For Gradle project, removes a single dependency from the dependencies section of the `build.gradle`. +For Maven project, removes a single dependency from the `` section of the pom.xml.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""com.fasterxml.jackson*"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""jackson-module*"",""required"":true},{""name"":""unlessUsing"",""type"":""String"",""displayName"":""Unless using"",""description"":""Do not remove if type is in use. Supports glob expressions."",""example"":""org.aspectj.lang.*""},{""name"":""configuration"",""type"":""String"",""displayName"":""The dependency configuration"",""description"":""The dependency configuration to remove from."",""example"":""api""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Only remove dependencies if they are in this scope. If 'runtime', this willalso remove dependencies in the 'compile' scope because 'compile' dependencies are part of the runtime dependency set"",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""]}]", +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RemoveRedundantDependencies,Remove redundant explicit dependencies,"Remove explicit dependencies that are already provided transitively by a specified dependency. This recipe downloads and resolves the parent dependency's POM to determine its true transitive dependencies, allowing it to detect redundancies even when both dependencies are explicitly declared. A direct dependency is only removed when the transitive one provides it at the exact same scope and with equivalent exclusions, so that removing it does not change the effective classpath.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""com.fasterxml.jackson.core"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""jackson-databind"",""required"":true}]", maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.UpgradeDependencyVersion,Upgrade Gradle or Maven dependency versions,"For Gradle projects, upgrade the version of a dependency in a `build.gradle` file. Supports updating dependency declarations of various forms: * `String` notation: `""group:artifact:version""` * `Map` notation: `group: 'group', name: 'artifact', version: 'version'` It is possible to update version numbers which are defined earlier in the same file in variable declarations. For Maven projects, upgrade the version of a dependency by specifying a group ID and (optionally) an artifact ID using Node Semver advanced range selectors, allowing more precise control over version updates to patch or minor releases.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""com.fasterxml.jackson*"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""jackson-module*"",""required"":true},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""An exact version number or node-style semver selector used to select the version number. "",""example"":""29.X"",""required"":true},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""overrideManagedVersion"",""type"":""Boolean"",""displayName"":""Override managed version"",""description"":""For Maven project only, This flag can be set to explicitly override a managed dependency's version. The default for this flag is `false`.""},{""name"":""retainVersions"",""type"":""List"",""displayName"":""Retain versions"",""description"":""For Maven project only, accepts a list of GAVs. For each GAV, if it is a project direct dependency, and it is removed from dependency management after the changes from this recipe, then it will be retained with an explicit version. The version can be omitted from the GAV to use the old value from dependency management."",""example"":""com.jcraft:jsch""}]", -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RemoveDependency,Remove a Gradle or Maven dependency,"For Gradle project, removes a single dependency from the dependencies section of the `build.gradle`. -For Maven project, removes a single dependency from the `` section of the pom.xml.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""com.fasterxml.jackson*"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""jackson-module*"",""required"":true},{""name"":""unlessUsing"",""type"":""String"",""displayName"":""Unless using"",""description"":""Do not remove if type is in use. Supports glob expressions."",""example"":""org.aspectj.lang.*""},{""name"":""configuration"",""type"":""String"",""displayName"":""The dependency configuration"",""description"":""The dependency configuration to remove from."",""example"":""api""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Only remove dependencies if they are in this scope. If 'runtime', this willalso remove dependencies in the 'compile' scope because 'compile' dependencies are part of the runtime dependency set"",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""]}]", -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.FindDependency,Find Maven and Gradle dependencies,Finds direct dependencies declared in Maven and Gradle build files. This does *not* search transitive dependencies. To detect both direct and transitive dependencies use `org.openrewrite.java.dependencies.DependencyInsight` This recipe works for both Maven and Gradle projects.,1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate identifying its publisher."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate uniquely identifying it among artifacts from the same publisher."",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""3.0.0""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example, setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""configuration"",""type"":""String"",""displayName"":""Configuration"",""description"":""For Gradle only, the dependency configuration to search for dependencies in. If omitted then all configurations will be searched."",""example"":""api""}]", -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.DependencyList,Dependency report,Emits a data table detailing all Gradle and Maven dependencies. This recipe makes no changes to any source file.,1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""scope"",""type"":""Scope"",""displayName"":""Scope"",""description"":""The scope of the dependencies to include in the report.Defaults to \""Compile\"""",""example"":""Compile"",""valid"":[""Compile"",""Runtime"",""TestRuntime""]},{""name"":""includeTransitive"",""type"":""boolean"",""displayName"":""Include transitive dependencies"",""description"":""Whether or not to include transitive dependencies in the report. Defaults to including only direct dependencies.Defaults to false."",""example"":""true"",""value"":false},{""name"":""validateResolvable"",""type"":""boolean"",""displayName"":""Validate dependencies are resolvable"",""description"":""When enabled the recipe will attempt to download every dependency it encounters, reporting on any failures. This can be useful for identifying dependencies that have become unavailable since an LST was produced.Defaults to false."",""example"":""true"",""valid"":[""true"",""false""],""value"":false}]","[{""name"":""org.openrewrite.java.dependencies.table.DependencyListReport"",""displayName"":""Dependency report"",""instanceName"":""Dependency report"",""description"":""Lists all Gradle and Maven dependencies"",""columns"":[{""name"":""buildTool"",""type"":""String"",""displayName"":""Build tool"",""description"":""The build tool used to manage dependencies (Gradle or Maven).""},{""name"":""groupId"",""type"":""String"",""displayName"":""Group id"",""description"":""The Group ID of the Gradle project or Maven module requesting the dependency.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The Artifact ID of the Gradle project or Maven module requesting the dependency.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of Gradle project or Maven module requesting the dependency.""},{""name"":""dependencyGroupId"",""type"":""String"",""displayName"":""Dependency group id"",""description"":""The Group ID of the dependency.""},{""name"":""dependencyArtifactId"",""type"":""String"",""displayName"":""Dependency artifact id"",""description"":""The Artifact ID of the dependency.""},{""name"":""dependencyVersion"",""type"":""String"",""displayName"":""Dependency version"",""description"":""The version of the dependency.""},{""name"":""direct"",""type"":""boolean"",""displayName"":""Direct Dependency"",""description"":""When `true` the project directly depends on the dependency. When `false` the project depends on the dependency transitively through at least one direct dependency.""},{""name"":""resolutionFailure"",""type"":""String"",""displayName"":""Resolution failure"",""description"":""The reason why the dependency could not be resolved. Blank when resolution was not attempted.""}]},{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RelocatedDependencyCheck,Find relocated dependencies,"Find Maven and Gradle dependencies and Maven plugins that have relocated to a new `groupId` or `artifactId`. Relocation information comes from the [oga-maven-plugin](https://github.com/jonathanlermitage/oga-maven-plugin/) maintained by Jonathan Lermitage, Filipe Roque and others. - -This recipe makes no changes to any source file by default. Add `changeDependencies=true` to change dependencies, but note that you might need to run additional recipes to update imports and adopt other breaking changes.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""changeDependencies"",""type"":""Boolean"",""displayName"":""Change dependencies"",""description"":""Whether to change dependencies to their relocated groupId and artifactId.""}]","[{""name"":""org.openrewrite.java.dependencies.table.RelocatedDependencyReport"",""displayName"":""Relocated dependencies"",""instanceName"":""Relocated dependencies"",""description"":""A list of dependencies in use that have relocated."",""columns"":[{""name"":""dependencyGroupId"",""type"":""String"",""displayName"":""Dependency group id"",""description"":""The Group ID of the dependency in use.""},{""name"":""dependencyArtifactId"",""type"":""String"",""displayName"":""Dependency artifact id"",""description"":""The Artifact ID of the dependency in use.""},{""name"":""relocatedGroupId"",""type"":""String"",""displayName"":""Relocated dependency group id"",""description"":""The Group ID of the relocated dependency.""},{""name"":""relocatedArtifactId"",""type"":""String"",""displayName"":""Relocated ependency artifact id"",""description"":""The Artifact ID of the relocated dependency.""},{""name"":""context"",""type"":""String"",""displayName"":""Context"",""description"":""Context for the relocation, if any.""}]}]" -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.DependencyInsight,Dependency insight for Gradle and Maven,"Finds dependencies, including transitive dependencies, in both Gradle and Maven projects. Matches within all Gradle dependency configurations and Maven scopes.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupIdPattern"",""type"":""String"",""displayName"":""Group pattern"",""description"":""Group ID glob pattern used to match dependencies."",""example"":""com.fasterxml.jackson*"",""required"":true},{""name"":""artifactIdPattern"",""type"":""String"",""displayName"":""Artifact pattern"",""description"":""Artifact ID glob pattern used to match dependencies."",""example"":""jackson-*"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used. All versions are searched by default."",""example"":""1.x""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Match dependencies with the specified Maven scope. All scopes are searched by default."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided"",""system""]}]","[{""name"":""org.openrewrite.maven.table.DependenciesInUse"",""displayName"":""Dependencies in use"",""instanceName"":""Dependencies in use"",""description"":""Direct and transitive dependencies in use."",""columns"":[{""name"":""projectName"",""type"":""String"",""displayName"":""Project name"",""description"":""The name of the project that contains the dependency.""},{""name"":""sourceSet"",""type"":""String"",""displayName"":""Source set"",""description"":""The source set that contains the dependency.""},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The resolved version.""},{""name"":""datedSnapshotVersion"",""type"":""String"",""displayName"":""Dated snapshot version"",""description"":""The resolved dated snapshot version or `null` if this dependency is not a snapshot.""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Dependency scope. This will be `compile` if the dependency is direct and a scope is not explicitly specified in the POM.""},{""name"":""count"",""type"":""Integer"",""displayName"":""Count"",""description"":""How many times does this dependency appear.""}]}]" -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.DependencyResolutionDiagnostic,Dependency resolution diagnostic,"Recipes which manipulate dependencies must be able to successfully access the artifact repositories and resolve dependencies from them. This recipe produces two data tables used to understand the state of dependency resolution. - -The Repository accessibility report lists all the artifact repositories known to the project and whether respond to network access. The network access is attempted while the recipe is run and so is representative of current conditions. - -The Gradle dependency configuration errors lists all the dependency configurations that failed to resolve one or more dependencies when the project was parsed. This is representative of conditions at the time the LST was parsed.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The group ID of a dependency to attempt to download from the repository. Default value is \""com.fasterxml.jackson.core\"". If this dependency is not found in the repository the error will be noted in the report. There is no need to specify an alternate value for this parameter unless the repository is known not to contain jackson-core."",""example"":""com.fasterxml.jackson.core""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The artifact ID of a dependency to attempt to download from the repository. Default value is \""jackson-core\"". If this dependency is not found in the repository the error will be noted in the report. There is no need to specify an alternate value for this parameter unless the repository is known not to contain jackson-core."",""example"":""jackson-core""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of a dependency to attempt to download from the repository. Default value is \""2.16.0\"". If this dependency is not found in the repository the error will be noted in the report. There is no need to specify an alternate value for this parameter unless the repository is known not to contain jackson-core."",""example"":""2.16.0""}]","[{""name"":""org.openrewrite.java.dependencies.table.RepositoryAccessibilityReport"",""displayName"":""Repository accessibility report"",""instanceName"":""Repository accessibility report"",""description"":""Listing of all dependency repositories and whether they are accessible."",""columns"":[{""name"":""uri"",""type"":""String"",""displayName"":""Repository URI"",""description"":""The URI of the repository""},{""name"":""pingExceptionType"",""type"":""String"",""displayName"":""Ping exception type"",""description"":""Empty if the repository responded to a ping. Otherwise, the type of exception encountered when attempting to access the repository.""},{""name"":""pingExceptionMessage"",""type"":""String"",""displayName"":""Ping error message"",""description"":""Empty if the repository was accessible. Otherwise, the error message encountered when attempting to access the repository.""},{""name"":""pingHttpCode"",""type"":""Integer"",""displayName"":""Ping HTTP code"",""description"":""The HTTP response code returned by the repository. May be empty for non-HTTP repositories.""},{""name"":""dependencyResolveExceptionType"",""type"":""String"",""displayName"":""Dependency resolution exception type"",""description"":""Empty if ping failed, or if the repository successfully downloaded the specified dependency. Otherwise, the type of exception encountered when attempting to access the repository.""},{""name"":""dependencyResolveExceptionMessage"",""type"":""String"",""displayName"":""Dependency resolution error message"",""description"":""Empty if ping failed, or if the repository successfully downloaded the specified dependency. Otherwise, the error message encountered when attempting to access the repository.""}]},{""name"":""org.openrewrite.java.dependencies.table.GradleDependencyConfigurationErrors"",""displayName"":""Gradle dependency configuration errors"",""instanceName"":""Gradle dependency configuration errors"",""description"":""Records Gradle dependency configurations which failed to resolve during parsing. Partial success/failure is common, a failure in this list does not mean that every dependency failed to resolve."",""columns"":[{""name"":""projectPath"",""type"":""String"",""displayName"":""Project path"",""description"":""The path of the project which contains the dependency configuration.""},{""name"":""configurationName"",""type"":""String"",""displayName"":""Configuration name"",""description"":""The name of the dependency configuration which failed to resolve.""},{""name"":""exceptionType"",""type"":""String"",""displayName"":""Exception type"",""description"":""The type of exception encountered when attempting to resolve the dependency configuration.""},{""name"":""exceptionMessage"",""type"":""String"",""displayName"":""Error message"",""description"":""The error message encountered when attempting to resolve the dependency configuration.""}]}]" -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.AddDependency,Add Gradle or Maven dependency,"For a Gradle project, add a gradle dependency to a `build.gradle` file in the correct configuration based on where it is used. Or For a maven project, Add a Maven dependency to a `pom.xml` file in the correct scope based on where it is used.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`"",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""29.X""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example, Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""onlyIfUsing"",""type"":""String"",""displayName"":""Only if using"",""description"":""Used to determine if the dependency will be added and in which scope it should be placed."",""example"":""org.junit.jupiter.api.*""},{""name"":""classifier"",""type"":""String"",""displayName"":""Classifier"",""description"":""A classifier to add. Commonly used to select variants of a library."",""example"":""test""},{""name"":""familyPattern"",""type"":""String"",""displayName"":""Family pattern"",""description"":""A pattern, applied to groupIds, used to determine which other dependencies should have aligned version numbers. Accepts '*' as a wildcard character."",""example"":""com.fasterxml.jackson*""},{""name"":""extension"",""type"":""String"",""displayName"":""Extension"",""description"":""For Gradle only, The extension of the dependency to add. If omitted Gradle defaults to assuming the type is \""jar\""."",""example"":""jar""},{""name"":""configuration"",""type"":""String"",""displayName"":""Gradle configuration"",""description"":""The Gradle dependency configuration name within which to place the dependency. When omitted the configuration will be determined by the Maven scope parameter. If that parameter is also omitted, configuration will be determined based on where types matching `onlyIfUsing` appear in source code."",""example"":""implementation""},{""name"":""scope"",""type"":""String"",""displayName"":""Maven scope"",""description"":""The Maven scope within which to place the dependency. When omitted scope will be determined based on where types matching `onlyIfUsing` appear in source code."",""example"":""runtime"",""valid"":[""compile"",""provided"",""runtime"",""test""]},{""name"":""releasesOnly"",""type"":""Boolean"",""displayName"":""Releases only"",""description"":""For Maven only, Whether to exclude snapshots from consideration when using a semver selector""},{""name"":""type"",""type"":""String"",""displayName"":""Type"",""description"":""For Maven only, The type of dependency to add. If omitted Maven defaults to assuming the type is \""jar\""."",""example"":""jar"",""valid"":[""jar"",""pom"",""war""]},{""name"":""optional"",""type"":""Boolean"",""displayName"":""Optional"",""description"":""Set the value of the `` tag. No `` tag will be added when this is `null`.""},{""name"":""acceptTransitive"",""type"":""Boolean"",""displayName"":""Accept transitive"",""description"":""Default false. If enabled, the dependency will not be added if it is already on the classpath as a transitive dependency."",""example"":""true""}]", +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.UpgradeTransitiveDependencyVersion,Upgrade transitive Gradle or Maven dependencies,"Upgrades the version of a transitive dependency in a Maven pom.xml or Gradle build.gradle. Leaves direct dependencies unmodified. Can be paired with the regular Upgrade Dependency Version recipe to upgrade a dependency everywhere, regardless of whether it is direct or transitive.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.apache.logging.log4j:ARTIFACT_ID:VERSION'."",""example"":""org.apache.logging.log4j"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.apache.logging.log4j:log4j-bom:VERSION'."",""example"":""log4j-bom"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""latest.release"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""An optional scope to use for the dependency management tag. Relevant only to Maven."",""example"":""import"",""valid"":[""import"",""runtime"",""provided"",""test""]},{""name"":""type"",""type"":""String"",""displayName"":""Type"",""description"":""An optional type to use for the dependency management tag. Relevant only to Maven builds."",""example"":""pom"",""valid"":[""jar"",""pom"",""war""]},{""name"":""classifier"",""type"":""String"",""displayName"":""Classifier"",""description"":""An optional classifier to use for the dependency management tag. Relevant only to Maven."",""example"":""test""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select 29.0-jre"",""example"":""-jre""},{""name"":""because"",""type"":""String"",""displayName"":""Because"",""description"":""The reason for upgrading the transitive dependency. For example, we could be responding to a vulnerability."",""example"":""CVE-2021-1234""},{""name"":""releasesOnly"",""type"":""Boolean"",""displayName"":""Releases only"",""description"":""Whether to exclude snapshots from consideration when using a semver selector""},{""name"":""onlyIfUsing"",""type"":""String"",""displayName"":""Only if using glob expression for group:artifact"",""description"":""Only add managed dependencies to projects having a dependency matching the expression."",""example"":""org.apache.logging.log4j:log4j*""},{""name"":""addToRootPom"",""type"":""Boolean"",""displayName"":""Add to the root pom"",""description"":""Add to the root pom where root is the eldest parent of the pom within the source set.""}]", maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.search.DoesNotIncludeDependency,Does not include dependency for Gradle and Maven,"A precondition which returns false if visiting a Gradle file / Maven pom which includes the specified dependency in the classpath of some Gradle configuration / Maven scope. For compatibility with multimodule projects, this should most often be applied as a precondition.",1,Search,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified resolved version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used. All versions are searched by default."",""example"":""1.x""},{""name"":""onlyDirect"",""type"":""Boolean"",""displayName"":""Only direct dependencies"",""description"":""Default false. If enabled, transitive dependencies will not be considered."",""example"":""true""},{""name"":""scope"",""type"":""String"",""displayName"":""Maven scope"",""description"":""Default any. If specified, only the requested scope's classpaths will be checked."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""]},{""name"":""configuration"",""type"":""String"",""displayName"":""Gradle configuration"",""description"":""Match dependencies with the specified configuration. If not specified, all configurations will be searched."",""example"":""compileClasspath""}]", -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.search.RepositoryHasDependency,Repository has dependency,"Searches for both Gradle and Maven modules that have a dependency matching the specified groupId and artifactId. Places a `SearchResult` marker on all sources within a repository with a matching dependency. This recipe is intended to be used as a precondition for other recipes. For example this could be used to limit the application of a spring boot migration to only projects that use a springframework dependency, limiting unnecessary upgrading. If the search result you want is instead just the build.gradle(.kts) or pom.xml file applying the plugin, use the `FindDependency` recipe instead.",1,Search,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupIdPattern"",""type"":""String"",""displayName"":""Group pattern"",""description"":""Group glob pattern used to match dependencies."",""example"":""com.fasterxml.jackson.module"",""required"":true},{""name"":""artifactIdPattern"",""type"":""String"",""displayName"":""Artifact pattern"",""description"":""Artifact glob pattern used to match dependencies."",""example"":""jackson-module-*"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Match dependencies with the specified scope. All scopes are searched by default."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided"",""system""]},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used.All versions are searched by default."",""example"":""1.x""}]", maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.search.FindDuplicateClasses,Find duplicate classes on the classpath,Detects classes that appear in multiple dependencies on the classpath. This is similar to what the Maven duplicate-finder-maven-plugin does. Duplicate classes can cause runtime issues when different versions of the same class are loaded.,1,Search,Dependencies,Java,,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.dependencies.table.DuplicateClassesReport"",""displayName"":""Duplicate classes report"",""instanceName"":""Duplicate classes report"",""description"":""Lists classes that appear in multiple dependencies on the classpath"",""columns"":[{""name"":""projectName"",""type"":""String"",""displayName"":""Project name"",""description"":""The project containing the duplicate.""},{""name"":""sourceSet"",""type"":""String"",""displayName"":""Source set"",""description"":""The source set containing the duplicate (e.g., main, test).""},{""name"":""typeName"",""type"":""String"",""displayName"":""Type name"",""description"":""The fully qualified name of the duplicate class.""},{""name"":""dependency1"",""type"":""String"",""displayName"":""Dependency 1"",""description"":""The first dependency containing the class (group:artifact:version).""},{""name"":""dependency2"",""type"":""String"",""displayName"":""Dependency 2"",""description"":""The second dependency containing the class (group:artifact:version).""},{""name"":""additionalDependencies"",""type"":""String"",""displayName"":""Additional dependencies"",""description"":""Any additional dependencies beyond the first two that also contain this class, comma-separated.""}]}]" maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.search.FindMinimumDependencyVersion,Find the oldest matching dependency version in use,"The oldest dependency version in use is the lowest dependency version in use in any source set of any subproject of a repository. It is possible that, for example, the main source set of a project uses Jackson 2.11, but a test source set uses Jackson 2.16. In this case, the oldest Jackson version in use is Java 2.11.",1,Search,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupIdPattern"",""type"":""String"",""displayName"":""Group pattern"",""description"":""Group ID glob pattern used to match dependencies."",""example"":""com.fasterxml.jackson.module"",""required"":true},{""name"":""artifactIdPattern"",""type"":""String"",""displayName"":""Artifact pattern"",""description"":""Artifact ID glob pattern used to match dependencies."",""example"":""jackson-module-*"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used. All versions are searched by default."",""example"":""1.x""}]","[{""name"":""org.openrewrite.maven.table.DependenciesInUse"",""displayName"":""Dependencies in use"",""instanceName"":""Dependencies in use"",""description"":""Direct and transitive dependencies in use."",""columns"":[{""name"":""projectName"",""type"":""String"",""displayName"":""Project name"",""description"":""The name of the project that contains the dependency.""},{""name"":""sourceSet"",""type"":""String"",""displayName"":""Source set"",""description"":""The source set that contains the dependency.""},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The resolved version.""},{""name"":""datedSnapshotVersion"",""type"":""String"",""displayName"":""Dated snapshot version"",""description"":""The resolved dated snapshot version or `null` if this dependency is not a snapshot.""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Dependency scope. This will be `compile` if the dependency is direct and a scope is not explicitly specified in the POM.""},{""name"":""count"",""type"":""Integer"",""displayName"":""Count"",""description"":""How many times does this dependency appear.""}]}]" maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.search.FindMinimumJUnitVersion,Find minimum JUnit version,"A recipe to find the minimum version of JUnit dependencies. This recipe is designed to return the minimum version of JUnit in a project. It will search for JUnit 4 and JUnit 5 dependencies in the project. If both versions are found, it will return the minimum version of JUnit 4. @@ -32,3 +31,4 @@ If a minimumVersion is provided, the recipe will search to see if the minimum ve For example: if the minimumVersion is 4, and the project has JUnit 4.12 and JUnit 5.7, the recipe will return JUnit 4.12. If the project has only JUnit 5.7, the recipe will return JUnit 5.7. Another example: if the minimumVersion is 5, and the project has JUnit 4.12 and JUnit 5.7, the recipe will not return any results.",1,Search,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""minimumVersion"",""type"":""String"",""displayName"":""Version"",""description"":""Determine if the provided version is the minimum JUnit version. If both JUnit 4 and JUnit 5 are present, the minimum version is JUnit 4. If only one version is present, that version is the minimum version."",""example"":""4"",""valid"":[""4"",""5""]}]","[{""name"":""org.openrewrite.maven.table.DependenciesInUse"",""displayName"":""Dependencies in use"",""instanceName"":""Dependencies in use"",""description"":""Direct and transitive dependencies in use."",""columns"":[{""name"":""projectName"",""type"":""String"",""displayName"":""Project name"",""description"":""The name of the project that contains the dependency.""},{""name"":""sourceSet"",""type"":""String"",""displayName"":""Source set"",""description"":""The source set that contains the dependency.""},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The resolved version.""},{""name"":""datedSnapshotVersion"",""type"":""String"",""displayName"":""Dated snapshot version"",""description"":""The resolved dated snapshot version or `null` if this dependency is not a snapshot.""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Dependency scope. This will be `compile` if the dependency is direct and a scope is not explicitly specified in the POM.""},{""name"":""count"",""type"":""Integer"",""displayName"":""Count"",""description"":""How many times does this dependency appear.""}]}]" maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.search.ModuleHasDependency,Module has dependency,"Searches for both Gradle and Maven modules that have a dependency matching the specified groupId and artifactId. Places a `SearchResult` marker on all sources within a module with a matching dependency. This recipe is intended to be used as a precondition for other recipes. For example this could be used to limit the application of a spring boot migration to only projects that use spring-boot-starter, limiting unnecessary upgrading. If the search result you want is instead just the build.gradle(.kts) or pom.xml file applying the plugin, use the `FindDependency` recipe instead.",1,Search,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupIdPattern"",""type"":""String"",""displayName"":""Group pattern"",""description"":""Group glob pattern used to match dependencies."",""example"":""com.fasterxml.jackson.module"",""required"":true},{""name"":""artifactIdPattern"",""type"":""String"",""displayName"":""Artifact pattern"",""description"":""Artifact glob pattern used to match dependencies."",""example"":""jackson-module-*"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Match dependencies with the specified scope. All scopes are searched by default."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided"",""system""]},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used.All versions are searched by default."",""example"":""1.x""},{""name"":""invertMarking"",""type"":""Boolean"",""displayName"":""Invert marking"",""description"":""If `true`, will invert the check for whether to mark a file. Defaults to `false`.""}]", +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.search.RepositoryHasDependency,Repository has dependency,"Searches for both Gradle and Maven modules that have a dependency matching the specified groupId and artifactId. Places a `SearchResult` marker on all sources within a repository with a matching dependency. This recipe is intended to be used as a precondition for other recipes. For example this could be used to limit the application of a spring boot migration to only projects that use a springframework dependency, limiting unnecessary upgrading. If the search result you want is instead just the build.gradle(.kts) or pom.xml file applying the plugin, use the `FindDependency` recipe instead.",1,Search,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupIdPattern"",""type"":""String"",""displayName"":""Group pattern"",""description"":""Group glob pattern used to match dependencies."",""example"":""com.fasterxml.jackson.module"",""required"":true},{""name"":""artifactIdPattern"",""type"":""String"",""displayName"":""Artifact pattern"",""description"":""Artifact glob pattern used to match dependencies."",""example"":""jackson-module-*"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Match dependencies with the specified scope. All scopes are searched by default."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided"",""system""]},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used.All versions are searched by default."",""example"":""1.x""}]", From 2ce7067717bcebc1b3a6a72e9fc313211805bcc8 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 28 Jul 2026 17:22:36 +0200 Subject: [PATCH 8/9] Keep dependencies whose declared exclusions differ Comparing declared exclusions still required resolving each coordinate's own dependency closure to discard exclusions it could never have honoured, costing an isolated POM download and graph resolve per candidate. Treat any difference in declared exclusions as "not redundant" instead. This is strictly conservative: it can only keep a dependency that was previously removed, never the reverse. The cost is that a transitive which merely inherited an unrelated exclusion from an ancestor no longer compares equal to a direct declaration carrying none, so jakarta.ws.rs-api is now kept rather than removed. That test is retained, inverted, to document the limitation. --- .../RemoveRedundantDependencies.java | 108 ++++-------------- .../RemoveRedundantDependenciesTest.java | 30 +---- 2 files changed, 28 insertions(+), 110 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java index 0482a16c..1bd75af8 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java @@ -29,7 +29,6 @@ import org.openrewrite.maven.tree.*; import java.util.*; -import java.util.function.Consumer; import static java.util.Collections.*; @@ -53,15 +52,12 @@ public class RemoveRedundantDependencies extends ScanningRecipe scope/configuration -> Set of transitive dependencies Map>> transitivesByProjectAndScope; - // Map from coordinate -> group:artifacts it pulls in on its own, at compile scope and versionless - // because exclusions match on group:artifact. Run-scoped, as every miss costs a POM download. - Map> compileClosureByGav; } @Value @@ -72,7 +68,7 @@ public static class TransitiveDependency { @Override public Accumulator getInitialValue(ExecutionContext ctx) { - return new Accumulator(new HashMap<>(), new HashMap<>()); + return new Accumulator(new HashMap<>()); } @Override @@ -149,17 +145,17 @@ private void resolveTransitivesFromPom( MavenPomDownloader downloader, ExecutionContext ctx, Set transitives) { + List repos = withMavenCentral(repositories); try { // Get the resolved dependencies for compile scope (which includes most transitives) - ResolvedPom resolvedPom = resolvePom( - gav.asGroupArtifactVersion(), withMavenCentral(repositories), downloader, ctx); + Pom pom = downloader.download(gav.asGroupArtifactVersion(), null, null, repos); + ResolvedPom resolvedPom = pom.resolve(emptyList(), downloader, repos, ctx); ResolvedPom patchedPom = applyExclusions(resolvedPom, effectiveExclusions); // Collect all dependencies (both direct and transitive of the parent) Set visited = new HashSet<>(); for (ResolvedDependency dep : patchedPom.resolveDependencies(Scope.Compile, downloader, ctx)) { - walkDependencies(dep, visited, d -> - transitives.add(new TransitiveDependency(d.getGav(), declaredExclusions(d)))); + collectAllDependencies(dep, transitives, visited); } } catch (MavenDownloadingException | MavenDownloadingExceptions e) { // If we can't download/resolve the POM, fall back to not detecting redundancies @@ -175,6 +171,16 @@ private ResolvedPom applyExclusions(ResolvedPom resolvedPom, List .anyMatch(e -> e.getGroupId().equals(d.getGroupId()) && e.getArtifactId().equals(d.getArtifactId()))); return patchedPom; } + + private void collectAllDependencies(ResolvedDependency dep, Set transitives, + Set visited) { + if (visited.add(dep.getGav())) { + transitives.add(new TransitiveDependency(dep.getGav(), declaredExclusions(dep))); + for (ResolvedDependency transitive : dep.getDependencies()) { + collectAllDependencies(transitive, transitives, visited); + } + } + } }; } @@ -186,22 +192,6 @@ private static Set declaredExclusions(ResolvedDependency dep) { return exclusions == null || exclusions.isEmpty() ? emptySet() : new HashSet<>(exclusions); } - private static void walkDependencies(ResolvedDependency dep, Set visited, - Consumer action) { - if (visited.add(dep.getGav())) { - action.accept(dep); - for (ResolvedDependency transitive : dep.getDependencies()) { - walkDependencies(transitive, visited, action); - } - } - } - - private static ResolvedPom resolvePom(GroupArtifactVersion gav, List repos, - MavenPomDownloader downloader, ExecutionContext ctx) - throws MavenDownloadingException, MavenDownloadingExceptions { - return downloader.download(gav, null, null, repos).resolve(emptyList(), downloader, repos, ctx); - } - private static List withMavenCentral(List repositories) { List effectiveRepos = new ArrayList<>(repositories); if (effectiveRepos.stream().noneMatch(r -> r.getUri().contains("repo.maven.apache.org") || @@ -211,39 +201,6 @@ private static List withMavenCentral(List repo return effectiveRepos; } - // Resolves what a coordinate can bring in on its own, so that exclusions targeting artifacts it could never - // have brought can be discarded before comparing two declarations. - private static class ClosureResolver { - private final List repositories; - private final MavenPomDownloader downloader; - private final ExecutionContext ctx; - private final Map> cache; - - private ClosureResolver(List repositories, ExecutionContext ctx, - Map> cache) { - this.repositories = withMavenCentral(repositories); - this.downloader = new MavenPomDownloader(ctx); - this.ctx = ctx; - this.cache = cache; - } - - private Set closureOf(GroupArtifactVersion gav) { - return cache.computeIfAbsent(gav, g -> { - Set closure = new HashSet<>(); - Set visited = new HashSet<>(); - try { - for (ResolvedDependency d : resolvePom(g, repositories, downloader, ctx) - .resolveDependencies(Scope.Compile, downloader, ctx)) { - walkDependencies(d, visited, r -> closure.add(r.getGav().asGroupArtifact())); - } - } catch (MavenDownloadingException | MavenDownloadingExceptions e) { - // Best-effort: an empty closure makes every exclusion look like a no-op - } - return closure; - }); - } - } - @Override public TreeVisitor getVisitor(Accumulator acc) { return new TreeVisitor() { @@ -272,7 +229,6 @@ public TreeVisitor getVisitor(Accumulator acc) { String projectId = gradle.getGroup() + ":" + gradle.getName(); Map> scopeToTransitives = acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); - ClosureResolver resolver = new ClosureResolver(gradle.getMavenRepositories(), ctx, acc.compileClosureByGav); for (GradleDependencyConfiguration conf : gradle.getConfigurations()) { Set transitives = getCompatibleGradleTransitives( @@ -284,7 +240,7 @@ public TreeVisitor getVisitor(Accumulator acc) { for (ResolvedDependency dep : conf.getResolved()) { if (dep.isDirect() && doesNotMatchArguments(dep) && - isRedundant(dep, transitives, resolver)) { + isRedundant(dep, transitives)) { // This direct dependency is transitively provided, remove it // Don't specify configuration - Gradle's resolved config names differ from declaration names result = new RemoveDependency( @@ -300,7 +256,6 @@ public TreeVisitor getVisitor(Accumulator acc) { String projectId = maven.getPom().getGroupId() + ":" + maven.getPom().getArtifactId(); Map> scopeToTransitives = acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); - ClosureResolver resolver = new ClosureResolver(maven.getPom().getRepositories(), ctx, acc.compileClosureByGav); // A direct dependency appears under every scope bucket it is visible in; evaluate each // one once using its own effective scope so a wider transitive scope does not falsely @@ -314,7 +269,7 @@ public TreeVisitor getVisitor(Accumulator acc) { Scope depScope = Scope.fromName(dep.getRequested().getScope()); Set transitives = scopeToTransitives.getOrDefault( depScope.name().toLowerCase(), emptySet()); - if (isRedundant(dep, transitives, resolver)) { + if (isRedundant(dep, transitives)) { // This direct dependency is transitively provided at the same scope and // with the same exclusions, remove it. result = new RemoveDependency( @@ -332,39 +287,20 @@ private boolean doesNotMatchArguments(ResolvedDependency dep) { !StringUtils.matchesGlob(dep.getArtifactId(), artifactId); } - private boolean isRedundant(ResolvedDependency dep, Set transitives, - ClosureResolver resolver) { + private boolean isRedundant(ResolvedDependency dep, Set transitives) { Set depExclusions = declaredExclusions(dep); - Set closure = null; for (TransitiveDependency transitive : transitives) { ResolvedGroupArtifactVersion gav = transitive.getGav(); if (dep.getGroupId().equals(gav.getGroupId()) && dep.getArtifactId().equals(gav.getArtifactId()) && - dep.getVersion().equals(gav.getVersion())) { - if (depExclusions.equals(transitive.getExclusions())) { - return true; - } - // Differing declarations can still be equivalent once exclusions that the coordinate - // could never have honoured anyway are discarded. Both sides share one closure, and - // every candidate here has the same coordinate, so this resolves at most once. - if (closure == null) { - closure = resolver.closureOf(gav.asGroupArtifactVersion()); - } - if (retainClosure(depExclusions, closure).equals( - retainClosure(transitive.getExclusions(), closure))) { - return true; - } + dep.getVersion().equals(gav.getVersion()) && + depExclusions.equals(transitive.getExclusions())) { + return true; } } return false; } - private Set retainClosure(Set exclusions, Set closure) { - Set relevant = new HashSet<>(exclusions); - relevant.retainAll(closure); - return relevant; - } - private Set getCompatibleGradleTransitives( Map> scopeToTransitives, String targetScope) { diff --git a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java index 00162261..cdcdb3ff 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java @@ -497,10 +497,12 @@ void keepsDirectCompileTomcatEmbedCoreWhenProviderIsProvidedScoped() { } @Test - void removesJakartaClientWhenTransitiveExclusionsAreEquivalent() { - // In the starter's tree the transitive jakarta.ws.rs-api gains a spurious exclusion of - // jakarta.activation-api (which it can never bring on its own); the direct declaration has none, - // so the two are effectively equivalent and the direct one is redundant. + void keepsJakartaClientWhenTransitiveInheritsUnrelatedExclusions() { + // Known conservative limitation: resolution propagates ancestor exclusions into each transitive's + // requested dependency, so the transitive jakarta.ws.rs-api carries an inherited jakarta.activation-api + // exclusion it could never have honoured, while the direct declaration carries none. Removing this + // would in fact be safe, but telling that apart requires resolving the coordinate's own closure, so + // the recipe keeps the dependency rather than risk an unsafe removal. rewriteRun( spec -> spec.recipe(new RemoveRedundantDependencies( "org.springframework.boot", "spring-boot-starter-*")), @@ -530,26 +532,6 @@ void removesJakartaClientWhenTransitiveExclusionsAreEquivalent() { - """, - """ - - 4.0.0 - com.sample - sample - 1.0-SNAPSHOT - - org.springframework.boot - spring-boot-starter-parent - 3.2.3 - - - - - org.springframework.boot - spring-boot-starter-jersey - - - """ ) ); From 7008876774491e92b3eeceb4fc0ca244c9495365 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 28 Jul 2026 17:22:36 +0200 Subject: [PATCH 9/9] Regenerate recipes.csv --- src/main/resources/META-INF/rewrite/recipes.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index d3eae2aa..4c54eb4e 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -15,7 +15,7 @@ maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.depe This recipe makes no changes to any source file by default. Add `changeDependencies=true` to change dependencies, but note that you might need to run additional recipes to update imports and adopt other breaking changes.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""changeDependencies"",""type"":""Boolean"",""displayName"":""Change dependencies"",""description"":""Whether to change dependencies to their relocated groupId and artifactId.""}]","[{""name"":""org.openrewrite.java.dependencies.table.RelocatedDependencyReport"",""displayName"":""Relocated dependencies"",""instanceName"":""Relocated dependencies"",""description"":""A list of dependencies in use that have relocated."",""columns"":[{""name"":""dependencyGroupId"",""type"":""String"",""displayName"":""Dependency group id"",""description"":""The Group ID of the dependency in use.""},{""name"":""dependencyArtifactId"",""type"":""String"",""displayName"":""Dependency artifact id"",""description"":""The Artifact ID of the dependency in use.""},{""name"":""relocatedGroupId"",""type"":""String"",""displayName"":""Relocated dependency group id"",""description"":""The Group ID of the relocated dependency.""},{""name"":""relocatedArtifactId"",""type"":""String"",""displayName"":""Relocated ependency artifact id"",""description"":""The Artifact ID of the relocated dependency.""},{""name"":""context"",""type"":""String"",""displayName"":""Context"",""description"":""Context for the relocation, if any.""}]}]" maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RemoveDependency,Remove a Gradle or Maven dependency,"For Gradle project, removes a single dependency from the dependencies section of the `build.gradle`. For Maven project, removes a single dependency from the `` section of the pom.xml.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""com.fasterxml.jackson*"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""jackson-module*"",""required"":true},{""name"":""unlessUsing"",""type"":""String"",""displayName"":""Unless using"",""description"":""Do not remove if type is in use. Supports glob expressions."",""example"":""org.aspectj.lang.*""},{""name"":""configuration"",""type"":""String"",""displayName"":""The dependency configuration"",""description"":""The dependency configuration to remove from."",""example"":""api""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Only remove dependencies if they are in this scope. If 'runtime', this willalso remove dependencies in the 'compile' scope because 'compile' dependencies are part of the runtime dependency set"",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""]}]", -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RemoveRedundantDependencies,Remove redundant explicit dependencies,"Remove explicit dependencies that are already provided transitively by a specified dependency. This recipe downloads and resolves the parent dependency's POM to determine its true transitive dependencies, allowing it to detect redundancies even when both dependencies are explicitly declared. A direct dependency is only removed when the transitive one provides it at the exact same scope and with equivalent exclusions, so that removing it does not change the effective classpath.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""com.fasterxml.jackson.core"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""jackson-databind"",""required"":true}]", +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RemoveRedundantDependencies,Remove redundant explicit dependencies,"Remove explicit dependencies that are already provided transitively by a specified dependency. This recipe downloads and resolves the parent dependency's POM to determine its true transitive dependencies, allowing it to detect redundancies even when both dependencies are explicitly declared. A direct dependency is only removed when the transitive one provides it at the exact same scope and with the same declared exclusions, so that removing it does not change the effective classpath.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""com.fasterxml.jackson.core"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""jackson-databind"",""required"":true}]", maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.UpgradeDependencyVersion,Upgrade Gradle or Maven dependency versions,"For Gradle projects, upgrade the version of a dependency in a `build.gradle` file. Supports updating dependency declarations of various forms: * `String` notation: `""group:artifact:version""` * `Map` notation: `group: 'group', name: 'artifact', version: 'version'`