From 4c789cbe192a9bae679b19247e48fb80377aa94e Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Tue, 14 Jul 2026 12:41:16 -0700 Subject: [PATCH 01/18] Include in Dependency Report the path to the build file declaring the dependencies being reported on --- .../java/dependencies/DependencyList.java | 17 +++--- .../table/DependencyListReport.java | 4 ++ .../java/dependencies/DependencyListTest.java | 10 ++-- .../DependencyResolutionDiagnosticTest.java | 57 ++++++++++--------- 4 files changed, 48 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/DependencyList.java b/src/main/java/org/openrewrite/java/dependencies/DependencyList.java index 6f4527fc..1703787b 100644 --- a/src/main/java/org/openrewrite/java/dependencies/DependencyList.java +++ b/src/main/java/org/openrewrite/java/dependencies/DependencyList.java @@ -36,6 +36,7 @@ import java.util.Set; import static java.util.Collections.emptyMap; +import static org.openrewrite.PathUtils.separatorsToUnix; @EqualsAndHashCode(callSuper = false) @Value @@ -89,6 +90,7 @@ public TreeVisitor getVisitor() { if (tree == null) { return null; } + String path = tree instanceof SourceFile ? separatorsToUnix(((SourceFile) tree).getSourcePath().toString()) : ""; Markers m = tree.getMarkers(); Set seen = new HashSet<>(); m.findFirst(GradleProject.class) @@ -100,7 +102,7 @@ public TreeVisitor getVisitor() { if (dep.getDepth() > 0) { continue; } - insertDependency(ctx, gradle, seen, dep, true); + insertDependency(ctx, gradle, path, seen, dep, true); } } }); @@ -109,7 +111,7 @@ public TreeVisitor getVisitor() { if (dep.getDepth() > 0) { continue; } - insertDependency(ctx, maven, seen, dep, true); + insertDependency(ctx, maven, path, seen, dep, true); } }); return tree; @@ -121,14 +123,13 @@ private Scope scope() { return scope == null ? Scope.Compile : scope; } - private void insertDependency(ExecutionContext ctx, GradleProject gradle, Set seen, ResolvedDependency dep, boolean direct) { + private void insertDependency(ExecutionContext ctx, GradleProject gradle, String path, Set seen, ResolvedDependency dep, boolean direct) { if (!seen.add(dep.getGav())) { return; } String resolutionFailure = ""; if (validateResolvable) { try { - //noinspection DataFlowIssue metadataFailures.insertRows(ctx, () -> new MavenPomDownloader( emptyMap(), ctx, null, @@ -140,6 +141,7 @@ private void insertDependency(ExecutionContext ctx, GradleProject gradle, Set seen, ResolvedDependency dep, boolean direct) { + private void insertDependency(ExecutionContext ctx, MavenResolutionResult maven, String path, Set seen, ResolvedDependency dep, boolean direct) { if (!seen.add(dep.getGav())) { return; } @@ -180,6 +182,7 @@ private void insertDependency(ExecutionContext ctx, MavenResolutionResult maven, } report.insertRow(ctx, new DependencyListReport.Row( "Maven", + path, maven.getPom().getGroupId(), maven.getPom().getArtifactId(), maven.getPom().getVersion(), @@ -191,7 +194,7 @@ private void insertDependency(ExecutionContext ctx, MavenResolutionResult maven, )); if (includeTransitive) { for (ResolvedDependency transitive : dep.getDependencies()) { - insertDependency(ctx, maven, seen, transitive, false); + insertDependency(ctx, maven, path, seen, transitive, false); } } } diff --git a/src/main/java/org/openrewrite/java/dependencies/table/DependencyListReport.java b/src/main/java/org/openrewrite/java/dependencies/table/DependencyListReport.java index 4f5e1d3f..8878bf9b 100644 --- a/src/main/java/org/openrewrite/java/dependencies/table/DependencyListReport.java +++ b/src/main/java/org/openrewrite/java/dependencies/table/DependencyListReport.java @@ -36,6 +36,10 @@ public static class Row { description = "The build tool used to manage dependencies (Gradle or Maven).") String buildTool; + @Column(displayName = "Path", + description = "Path to the build file declaring the dependency") + String path; + @Column(displayName = "Group id", description = "The Group ID of the Gradle project or Maven module requesting the dependency.") String groupId; diff --git a/src/test/java/org/openrewrite/java/dependencies/DependencyListTest.java b/src/test/java/org/openrewrite/java/dependencies/DependencyListTest.java index 43dd1342..fa9b0df2 100644 --- a/src/test/java/org/openrewrite/java/dependencies/DependencyListTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/DependencyListTest.java @@ -92,12 +92,10 @@ void directOnly() { rewriteRun( spec -> spec.recipe(new DependencyList(DependencyList.Scope.Compile, false, false)) .beforeRecipe(withToolingApi()) - .dataTable(DependencyListReport.Row.class, rows -> { - assertThat(rows) - .containsExactlyInAnyOrder( - new DependencyListReport.Row("Gradle", "com.test", "test", "1.0.0", "io.micrometer.prometheus", "prometheus-rsocket-client", "1.5.3", true, ""), - new DependencyListReport.Row("Maven", "com.test", "test", "1.0.0", "io.micrometer.prometheus", "prometheus-rsocket-client", "1.5.3", true, "")); - }), + .dataTable(DependencyListReport.Row.class, rows -> assertThat(rows) + .containsExactlyInAnyOrder( + new DependencyListReport.Row("Gradle", "build.gradle", "com.test", "test", "1.0.0", "io.micrometer.prometheus", "prometheus-rsocket-client", "1.5.3", true, ""), + new DependencyListReport.Row("Maven", "pom.xml", "com.test", "test", "1.0.0", "io.micrometer.prometheus", "prometheus-rsocket-client", "1.5.3", true, ""))), settingsGradle("rootProject.name = 'test'"), buildGradle( //language=groovy diff --git a/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java b/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java index 5826dd86..cd5d0ea7 100644 --- a/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java @@ -91,7 +91,7 @@ void gradle() { url "https://nonexistent.moderne.io/maven2" } } - + dependencies { implementation("org.openrewrite.nonexistent:nonexistent:0.0.0") } @@ -107,14 +107,12 @@ void gradleNoDefaultRepos() { spec -> spec.beforeRecipe(withToolingApi()) // It is a limitation of the tooling API which prevents configuration-granularity error information from being collected. // So the GradleDependencyConfigurationErrors table will never be populated in unit tests. - .dataTable(RepositoryAccessibilityReport.Row.class, rows -> { - assertThat(rows) - .hasSize(2) - .contains( - new RepositoryAccessibilityReport.Row("https://plugins.gradle.org/m2", "", "", 200, "", "")) - .contains( - new RepositoryAccessibilityReport.Row("https://nonexistent.moderne.io/maven2", "java.net.UnknownHostException", "nonexistent.moderne.io", null, "", "")); - }), + .dataTable(RepositoryAccessibilityReport.Row.class, rows -> assertThat(rows) + .hasSize(2) + .contains( + new RepositoryAccessibilityReport.Row("https://plugins.gradle.org/m2", "", "", 200, "", "")) + .contains( + new RepositoryAccessibilityReport.Row("https://nonexistent.moderne.io/maven2", "java.net.UnknownHostException", "nonexistent.moderne.io", null, "", ""))), //language=groovy buildGradle( """ @@ -126,7 +124,7 @@ void gradleNoDefaultRepos() { url "https://nonexistent.moderne.io/maven2" } } - + dependencies { implementation("org.openrewrite.nonexistent:nonexistent:0.0.0") } @@ -158,13 +156,11 @@ void mavenSettingsWithMirrors() { """.getBytes())), ctx); ctx.setMavenSettings(settings); spec.beforeRecipe(withToolingApi()) - .dataTable(RepositoryAccessibilityReport.Row.class, rows -> { - assertThat(rows) - .contains( - new RepositoryAccessibilityReport.Row("https://nonexistent.moderne.io/maven2", "java.net.UnknownHostException", "nonexistent.moderne.io", null, "", "") - ) - .noneMatch(repo -> repo.getUri().contains("https://repo.maven.apache.org/maven2")); - }) + .dataTable(RepositoryAccessibilityReport.Row.class, rows -> assertThat(rows) + .contains( + new RepositoryAccessibilityReport.Row("https://nonexistent.moderne.io/maven2", "java.net.UnknownHostException", "nonexistent.moderne.io", null, "", "") + ) + .noneMatch(repo -> repo.getUri().contains("https://repo.maven.apache.org/maven2"))) .executionContext(ctx); }, //language=xml @@ -183,15 +179,22 @@ void mavenSettingsWithMirrors() { @Test void maven() { rewriteRun( - spec -> spec.beforeRecipe(withToolingApi()) - .dataTable(RepositoryAccessibilityReport.Row.class, rows -> { - assertThat(rows).contains( - new RepositoryAccessibilityReport.Row("https://repo.maven.apache.org/maven2", "", "", 200, "", "")); - assertThat(rows).filteredOn(row -> row.getUri().startsWith("file:/") && "".equals(row.getPingExceptionMessage())).hasSize(1); - assertThat(rows).contains( - new RepositoryAccessibilityReport.Row("https://nonexistent.moderne.io/maven2", "java.net.UnknownHostException", "nonexistent.moderne.io", null, "", "") - ); - }), + spec -> { + // Use empty Maven settings so the developer's local ~/.m2/settings.xml mirrors do not + // rewrite Maven Central to some other repository, which would make this test environment-dependent. + MavenExecutionContextView ctx = MavenExecutionContextView.view(new InMemoryExecutionContext()); + ctx.setMavenSettings(new MavenSettings(null, null, null, null, null, null, null)); + spec + .executionContext(ctx) + .dataTable(RepositoryAccessibilityReport.Row.class, rows -> { + assertThat(rows).contains( + new RepositoryAccessibilityReport.Row("https://repo.maven.apache.org/maven2", "", "", 200, "", "")); + assertThat(rows).filteredOn(row -> row.getUri().startsWith("file:/") && "".equals(row.getPingExceptionMessage())).hasSize(1); + assertThat(rows).contains( + new RepositoryAccessibilityReport.Row("https://nonexistent.moderne.io/maven2", "java.net.UnknownHostException", "nonexistent.moderne.io", null, "", "") + ); + }); + }, //language=xml pomXml( """ @@ -199,7 +202,7 @@ void maven() { com.example test 0.1.0 - + nonexistent From c9b92d0fbc6dfa31b22561057cbe7c4403901de2 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 15 May 2026 10:52:22 +0000 Subject: [PATCH 02/18] OpenRewrite recipe best practices Use this link to re-run the recipe: https://app.moderne.io/recipes/org.openrewrite.recipes.rewrite.OpenRewriteRecipeBestPractices?organizationId=QUxML01vZGVybmUvTW9kZXJuZSArIE9wZW5SZXdyaXRl Co-authored-by: Moderne --- .../dependencies/ChangeDependencyTest.java | 3 +-- .../java/dependencies/DependencyListTest.java | 19 ++++++++++--------- .../UpgradeDependencyVersionTest.java | 4 +--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/openrewrite/java/dependencies/ChangeDependencyTest.java b/src/test/java/org/openrewrite/java/dependencies/ChangeDependencyTest.java index 9fe9a886..d8dc3370 100644 --- a/src/test/java/org/openrewrite/java/dependencies/ChangeDependencyTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/ChangeDependencyTest.java @@ -18,7 +18,6 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.Validated; import org.openrewrite.test.RewriteTest; import static org.assertj.core.api.Assertions.assertThat; @@ -201,7 +200,7 @@ void changeMavenDependencyManagementPomImport() { @Test void validateCascadesToRecipes() { - Validated validate = new ChangeDependency( + var validate = new ChangeDependency( "org.springframework.boot", "spring-boot-dependencies", "org.springframework.boot", "spring-boot-dependencies", "3.2.2", null, null, null diff --git a/src/test/java/org/openrewrite/java/dependencies/DependencyListTest.java b/src/test/java/org/openrewrite/java/dependencies/DependencyListTest.java index fa9b0df2..ef3d9151 100644 --- a/src/test/java/org/openrewrite/java/dependencies/DependencyListTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/DependencyListTest.java @@ -22,8 +22,9 @@ import org.openrewrite.test.RewriteTest; import java.nio.file.Path; +import java.util.List; +import java.util.Map; -import static java.util.Collections.*; import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.Tree.randomId; import static org.openrewrite.gradle.Assertions.buildGradle; @@ -175,17 +176,17 @@ void validateResolvable() { .requested(Pom.builder() .gav(rgav) .build()) - .repositories(singletonList(pretendRepo)) + .repositories(List.of(pretendRepo)) .build(), - emptyList(), + List.of(), null, - singletonMap(Scope.Compile, singletonList(new ResolvedDependency( - pretendRepo, - rgav, requested, emptyList(), emptyList(), null, null, null, 0, null) - )), + Map.of(Scope.Compile, List.of(new ResolvedDependency( + pretendRepo, + rgav, requested, List.of(), List.of(), null, null, null, 0, null) + )), null, - emptyList(), - emptyMap() + List.of(), + Map.of() )); } ) diff --git a/src/test/java/org/openrewrite/java/dependencies/UpgradeDependencyVersionTest.java b/src/test/java/org/openrewrite/java/dependencies/UpgradeDependencyVersionTest.java index d63fcfd7..55e7b761 100644 --- a/src/test/java/org/openrewrite/java/dependencies/UpgradeDependencyVersionTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/UpgradeDependencyVersionTest.java @@ -22,8 +22,6 @@ import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.test.RewriteTest; -import java.util.Optional; - import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.gradle.Assertions.buildGradle; import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; @@ -69,7 +67,7 @@ void upgradeGuavaInGradleProject() { } """, spec -> spec.afterRecipe(after -> { - Optional maybeGp = after.getMarkers().findFirst(GradleProject.class); + var maybeGp = after.getMarkers().findFirst(GradleProject.class); assertThat(maybeGp).isPresent(); GradleProject gp = maybeGp.get(); GradleDependencyConfiguration compileClasspath = gp.getConfiguration("compileClasspath"); From 71269575b462edb24b3b552a8d898c8a11d2263d Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 18 May 2026 21:24:18 +0200 Subject: [PATCH 03/18] Pin empty MavenSettings in DependencyResolutionDiagnosticTest.maven() (#178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit openrewrite/gh-automation#95 installs ~/.m2/settings.xml with `*` to route Maven through `artifactory.moderne.ninja` and avoid HTTP 429 from Maven Central. The mirror rewrites every repository request — including the test pom's deliberately-unreachable `https://nonexistent.moderne.io/maven2` — so the diagnostic recipe only sees the mirror and the local file cache. Neither Maven Central nor the test repo appear in the data table, and both assertions fail. Inject an explicit empty `MavenSettings` via `MavenExecutionContextView` (same pattern as `mavenSettingsWithMirrors`) so the recipe ignores `~/.m2/settings.xml` and sees the pom-declared repositories directly. The original three assertions are restored unchanged. --- .../DependencyResolutionDiagnosticTest.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java b/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java index cd5d0ea7..9202662c 100644 --- a/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java @@ -180,12 +180,20 @@ void mavenSettingsWithMirrors() { void maven() { rewriteRun( spec -> { - // Use empty Maven settings so the developer's local ~/.m2/settings.xml mirrors do not - // rewrite Maven Central to some other repository, which would make this test environment-dependent. + // CI may inject ~/.m2/settings.xml with `*` to route Maven through a cache. + // That rewrites the pom-declared `nonexistent` repo to the mirror URL, so neither Maven Central + // nor the test's `nonexistent.moderne.io` repo appear in the diagnostic. Force empty settings + // here so the recipe sees the pom-declared repositories directly. MavenExecutionContextView ctx = MavenExecutionContextView.view(new InMemoryExecutionContext()); - ctx.setMavenSettings(new MavenSettings(null, null, null, null, null, null, null)); - spec - .executionContext(ctx) + MavenSettings emptySettings = MavenSettings.parse(new Parser.Input(Path.of("settings.xml"), () -> new ByteArrayInputStream( + //language=xml + """ + + """.getBytes())), ctx); + ctx.setMavenSettings(emptySettings); + spec.beforeRecipe(withToolingApi()) .dataTable(RepositoryAccessibilityReport.Row.class, rows -> { assertThat(rows).contains( new RepositoryAccessibilityReport.Row("https://repo.maven.apache.org/maven2", "", "", 200, "", "")); @@ -193,7 +201,8 @@ void maven() { assertThat(rows).contains( new RepositoryAccessibilityReport.Row("https://nonexistent.moderne.io/maven2", "java.net.UnknownHostException", "nonexistent.moderne.io", null, "", "") ); - }); + }) + .executionContext(ctx); }, //language=xml pomXml( From f06761d2b7205e9ea5d1721c5dc562da8bfcf11d Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Thu, 21 May 2026 03:39:37 -0400 Subject: [PATCH 04/18] Speed up ModuleHasDependency and RepositoryHasDependency by removing DependencyInsight (#176) * Speed up ModuleHasDependency and RepositoryHasDependency by removing DependencyInsight Both recipes ran a fresh DependencyInsight visitor against every source file during scanning, which performs exhaustive Gradle + Maven dependency analysis just to answer a binary yes/no question about whether the module contains a given GAV. Replace the visitor with a direct check against MavenResolutionResult.findDependencies for Maven modules, and against GradleProject's configurations + ResolvedDependency.findDependency for Gradle modules. This mirrors the pattern Sam applied to the single-build-system versions of ModuleHasDependency in openrewrite/rewrite (commit 919c9f5 / PR #6664). These recipes are used as preconditions in many declarative migration recipes. Avoiding the DependencyInsight allocation + traversal per source file substantially reduces scanner time on repositories with many Java files. * Skip dependency scan in ModuleHasDependency once project is known to match Avoids re-running the per-tree dependency lookup for every source file in a module after the JavaProject has already been added to the accumulator. --------- Co-authored-by: Tim te Beek --- .../search/ModuleHasDependency.java | 43 +++++++++++++++-- .../search/RepositoryHasDependency.java | 48 +++++++++++++++---- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java b/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java index 842c0692..749b3ab7 100644 --- a/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java @@ -19,11 +19,18 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.java.dependencies.DependencyInsight; +import org.openrewrite.gradle.marker.GradleDependencyConfiguration; +import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.java.marker.JavaProject; import org.openrewrite.marker.SearchResult; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.Scope; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; import java.util.HashSet; +import java.util.List; import java.util.Optional; import java.util.Set; @@ -87,10 +94,7 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { tree.getMarkers() .findFirst(JavaProject.class) .ifPresent(jp -> { - Tree t = new DependencyInsight(groupIdPattern, artifactIdPattern, version, scope) - .getVisitor() - .visit(tree, ctx); - if (t != tree) { + if (!acc.contains(jp) && hasDependency(tree)) { acc.add(jp); } }); @@ -99,6 +103,35 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { }; } + private boolean hasDependency(Tree tree) { + VersionComparator versionComparator = version != null ? Semver.validate(version, null).getValue() : null; + + MavenResolutionResult mavenResult = tree.getMarkers().findFirst(MavenResolutionResult.class).orElse(null); + if (mavenResult != null) { + Scope requestedScope = scope == null ? null : Scope.fromName(scope); + List dependencies = mavenResult.findDependencies(groupIdPattern, artifactIdPattern, requestedScope); + for (ResolvedDependency dependency : dependencies) { + if (versionComparator == null || versionComparator.isValid(null, dependency.getVersion())) { + return true; + } + } + return false; + } + + GradleProject gp = tree.getMarkers().findFirst(GradleProject.class).orElse(null); + if (gp != null) { + for (GradleDependencyConfiguration c : gp.getConfigurations()) { + for (ResolvedDependency resolvedDependency : c.getDirectResolved()) { + ResolvedDependency found = resolvedDependency.findDependency(groupIdPattern, artifactIdPattern); + if (found != null && (versionComparator == null || versionComparator.isValid(null, found.getVersion()))) { + return true; + } + } + } + } + return false; + } + @Override public TreeVisitor getVisitor(Set acc) { return new TreeVisitor() { diff --git a/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java b/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java index 0b67f7a2..c3357780 100644 --- a/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java @@ -19,10 +19,17 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.java.dependencies.DependencyInsight; +import org.openrewrite.gradle.marker.GradleDependencyConfiguration; +import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.java.marker.JavaProject; import org.openrewrite.marker.SearchResult; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.Scope; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @EqualsAndHashCode(callSuper = false) @@ -75,18 +82,14 @@ public TreeVisitor getScanner(AtomicBoolean acc) { return new TreeVisitor() { @Override public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { - if(acc.get()) { - assert tree != null; + assert tree != null; + if (acc.get()) { return tree; } - assert tree != null; tree.getMarkers() .findFirst(JavaProject.class) .ifPresent(jp -> { - Tree t = new DependencyInsight(groupIdPattern, artifactIdPattern, scope, version) - .getVisitor() - .visit(tree, ctx); - if (t != tree) { + if (hasDependency(tree)) { acc.set(true); } }); @@ -95,6 +98,35 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { }; } + private boolean hasDependency(Tree tree) { + VersionComparator versionComparator = version != null ? Semver.validate(version, null).getValue() : null; + + MavenResolutionResult mavenResult = tree.getMarkers().findFirst(MavenResolutionResult.class).orElse(null); + if (mavenResult != null) { + Scope requestedScope = scope == null ? null : Scope.fromName(scope); + List dependencies = mavenResult.findDependencies(groupIdPattern, artifactIdPattern, requestedScope); + for (ResolvedDependency dependency : dependencies) { + if (versionComparator == null || versionComparator.isValid(null, dependency.getVersion())) { + return true; + } + } + return false; + } + + GradleProject gp = tree.getMarkers().findFirst(GradleProject.class).orElse(null); + if (gp != null) { + for (GradleDependencyConfiguration c : gp.getConfigurations()) { + for (ResolvedDependency resolvedDependency : c.getDirectResolved()) { + ResolvedDependency found = resolvedDependency.findDependency(groupIdPattern, artifactIdPattern); + if (found != null && (versionComparator == null || versionComparator.isValid(null, found.getVersion()))) { + return true; + } + } + } + } + return false; + } + @Override public TreeVisitor getVisitor(AtomicBoolean acc) { if (acc.get()) { From a90e41c62e2c328187c47790b63883c93846f7ee Mon Sep 17 00:00:00 2001 From: Jente Sondervorst Date: Thu, 28 May 2026 18:47:08 +0200 Subject: [PATCH 05/18] Match requested dependencies in ModuleHasDependency and RepositoryHasDependency (#179) --- .../search/ModuleHasDependency.java | 43 +++++++ .../search/RepositoryHasDependency.java | 43 +++++++ .../search/ModuleHasDependencyTest.java | 110 ++++++++++++++++++ 3 files changed, 196 insertions(+) diff --git a/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java b/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java index 749b3ab7..0b73c394 100644 --- a/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java @@ -21,8 +21,10 @@ import org.openrewrite.*; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; +import org.openrewrite.internal.StringUtils; import org.openrewrite.java.marker.JavaProject; import org.openrewrite.marker.SearchResult; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.MavenResolutionResult; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.Scope; @@ -115,6 +117,11 @@ private boolean hasDependency(Tree tree) { return true; } } + for (Dependency requested : mavenResult.getPom().getRequestedDependencies()) { + if (matchesRequested(requested, requestedScope, versionComparator)) { + return true; + } + } return false; } @@ -128,10 +135,46 @@ private boolean hasDependency(Tree tree) { } } } + for (GradleDependencyConfiguration c : gp.getConfigurations()) { + for (Dependency requested : c.getRequested()) { + if (matchesRequested(requested, null, versionComparator)) { + return true; + } + } + } } return false; } + private boolean matchesRequested(Dependency dep, @Nullable Scope requestedScope, @Nullable VersionComparator versionComparator) { + if (dep.getGroupId() == null || dep.getArtifactId() == null) { + return false; + } + if (!StringUtils.matchesGlob(dep.getGroupId(), groupIdPattern)) { + return false; + } + if (!StringUtils.matchesGlob(dep.getArtifactId(), artifactIdPattern)) { + return false; + } + if (requestedScope != null) { + Scope depScope = dep.getScope() == null ? Scope.Compile : Scope.fromName(dep.getScope()); + if (!depScope.isInClasspathOf(requestedScope)) { + return false; + } + } + return versionMatches(dep.getVersion(), versionComparator); + } + + private static boolean versionMatches(@Nullable String version, @Nullable VersionComparator cmp) { + if (cmp == null || version == null) { + return true; + } + if (version.startsWith("${")) { + return false; + } + return cmp.isValid(null, version); + } + @Override public TreeVisitor getVisitor(Set acc) { return new TreeVisitor() { diff --git a/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java b/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java index c3357780..b982d0e2 100644 --- a/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java @@ -21,8 +21,10 @@ import org.openrewrite.*; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; +import org.openrewrite.internal.StringUtils; import org.openrewrite.java.marker.JavaProject; import org.openrewrite.marker.SearchResult; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.MavenResolutionResult; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.Scope; @@ -110,6 +112,11 @@ private boolean hasDependency(Tree tree) { return true; } } + for (Dependency requested : mavenResult.getPom().getRequestedDependencies()) { + if (matchesRequested(requested, requestedScope, versionComparator)) { + return true; + } + } return false; } @@ -123,10 +130,46 @@ private boolean hasDependency(Tree tree) { } } } + for (GradleDependencyConfiguration c : gp.getConfigurations()) { + for (Dependency requested : c.getRequested()) { + if (matchesRequested(requested, null, versionComparator)) { + return true; + } + } + } } return false; } + private boolean matchesRequested(Dependency dep, @Nullable Scope requestedScope, @Nullable VersionComparator versionComparator) { + if (dep.getGroupId() == null || dep.getArtifactId() == null) { + return false; + } + if (!StringUtils.matchesGlob(dep.getGroupId(), groupIdPattern)) { + return false; + } + if (!StringUtils.matchesGlob(dep.getArtifactId(), artifactIdPattern)) { + return false; + } + if (requestedScope != null) { + Scope depScope = dep.getScope() == null ? Scope.Compile : Scope.fromName(dep.getScope()); + if (!depScope.isInClasspathOf(requestedScope)) { + return false; + } + } + return versionMatches(dep.getVersion(), versionComparator); + } + + private static boolean versionMatches(@Nullable String version, @Nullable VersionComparator cmp) { + if (cmp == null || version == null) { + return true; + } + if (version.startsWith("${")) { + return false; + } + return cmp.isValid(null, version); + } + @Override public TreeVisitor getVisitor(AtomicBoolean acc) { if (acc.get()) { diff --git a/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java b/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java index 61cd0285..0efe3269 100644 --- a/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java @@ -21,9 +21,16 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.maven.MavenExecutionContextView; +import org.openrewrite.maven.MavenSettings; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import java.io.ByteArrayInputStream; +import java.nio.file.Path; + import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.gradle.Assertions.buildGradle; import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; @@ -346,6 +353,109 @@ void whenModuleDoesNotHaveDependencyButInvertedMarkingMarks() { ); } + @Nested + class WhenDependencyIsRequestedButNotResolved { + + @Language("groovy") + private final static String GradleNoRepositories = """ + plugins { + id 'java-library' + } + dependencies { + implementation 'org.springframework:spring-beans:6.0.0' + } + """; + + @Language("xml") + private final static String MavenNoRepositories = """ + + com.example + foo + 1.0.0 + + + org.springframework + spring-beans + 6.0.0 + + + + """; + + @Test + void gradleMatchesOnRequested() { + rewriteRun( + spec -> spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, null, null)), + mavenProject("project-gradle", + buildGradle( + GradleNoRepositories, + spec -> spec.after(actual -> + assertThat(actual) + .startsWith(GradleMarkerPositive) + .actual() + ) + ), + java( + GradleJava, + spec -> spec.after(actual -> + assertThat(actual) + .startsWith(JavaMarkerPositive) + .actual() + ) + ) + ) + ); + } + + @Test + void mavenMatchesOnRequested() { + rewriteRun( + spec -> { + MavenExecutionContextView ctx = MavenExecutionContextView.view(new InMemoryExecutionContext()); + MavenSettings emptySettings = MavenSettings.parse(new Parser.Input(Path.of("settings.xml"), () -> new ByteArrayInputStream( + //language=xml + """ + + """.getBytes())), ctx); + ctx.setMavenSettings(emptySettings); + spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, null, null)) + .executionContext(ctx); + }, + mavenProject("project-maven", + pomXml( + MavenNoRepositories, + spec -> spec.after(actual -> + assertThat(actual) + .startsWith(MavenMarkerPositive) + .actual() + ) + ), + java( + MavenJava, + spec -> spec.after(actual -> + assertThat(actual) + .startsWith(JavaMarkerPositive) + .actual() + ) + ) + ) + ); + } + + @Test + void gradleVersionRangeOnRequestedDoesNotMatchWhenOutOfRange() { + rewriteRun( + spec -> spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, "[7.0,)", null)), + mavenProject("project-gradle", + buildGradle(GradleNoRepositories), + java(GradleJava) + ) + ); + } + } + @Nested class WithVersionsPattern { @ParameterizedTest From 29eb6b1d5a14811786ed1195bae34b4e7aca7462 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 28 May 2026 20:34:09 +0200 Subject: [PATCH 06/18] Regenerate recipes.csv (#180) --- .../resources/META-INF/rewrite/recipes.csv | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index 28d683ea..82d291e6 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -1,20 +1,7 @@ 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.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"",""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"",""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"",""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.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"",""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.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.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"",""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"",""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.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"",""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.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.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""` @@ -22,13 +9,26 @@ maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.depe 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.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.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.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.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"",""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"",""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.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. If a minimumVersion is provided, the recipe will search to see if the minimum version of JUnit used by the project is no lower than the minimumVersion. 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"",""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.""}]}]" +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 4b3f838c0cbbb1cd81b70ab6adfb24dbd04d7e72 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 28 May 2026 19:51:53 +0000 Subject: [PATCH 07/18] Update Gradle wrapper 9.5.1 Use this link to re-run the recipe: https://app.moderne.io/recipes/org.openrewrite.gradle.UpdateGradleWrapper?organizationId=QUxML01vZGVybmUgKyBPcGVuUmV3cml0ZS9PcGVuUmV3cml0ZQ%3D%3D#defaults=W3sidmFsdWUiOiI5LjUuMSIsIm5hbWUiOiJ2ZXJzaW9uIn0seyJ2YWx1ZSI6ImJpbiIsIm5hbWUiOiJkaXN0cmlidXRpb24ifSx7InZhbHVlIjoiYmFmYzE0MWI2MTlhZDYzNTBmZDk3NWZjOTAzMTU2ZGQ1YzE1MTk5OGNjOGIwNThlOGMxMDQ0YWI1ZjdiMDMxZiIsIm5hbWUiOiJkaXN0cmlidXRpb25DaGVja3N1bSJ9XQ== Co-authored-by: Moderne --- gradle/wrapper/gradle-wrapper.jar | Bin 46175 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 61285a659d17295f1de7c53e24fdf13ad755c379..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39773 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%0D6K65^m-0x6bZ9rDjF>In@OH( z-}n(gbEW`7N;Uw_nvT;^Dka<4HW`~@d4Ar1vP6&Q8=(0P)eG+4q=iBy>>s6#LLaiI z8Ius$PjnUlOR@A0RT&$X@hAoJ70tH@R`t4bsi-gdvtDkF(|IT~X#VhfF}CW+LQ7FW zSCNg@0d5;A>uzYV4~NGgNQxY^mrkR5QK&vnGw3CnNw3furU7!AZlW^NZ8XbtJC&P% zUZvTlH)xLOZJKMIO)m3%nrB`_^Ubxiz~?+yp|iN&T=EwTkfQDE%!hMSq#BO znPfZowh7rr_-LTI6JC5QuwEpv41d*sTb<#r!bL{Oe)x&GLyY# zLIBZRGLO=gqKSY}SBMZm*Ua_TQ9^uh;QMA2wwOAK9o3H6%iT8%>4Q0Pe|TBUf%$0VOHR=*E~+%SzZrDd+t#Ea7=v2I9{w8WcjX}z#b;jQh&*4=4IZK>gAkr&I%Q-uf7#`b@v z445^+&wQ(+H4C;az4Zb~B9#ysl|-y|$yh#%^l+I5GKSgjYy2pU*>B>c zTd{C8PsNu@i6PRUvsF;PGHb-8HBi`z0ayr&YC?+eT?{WbUxFJB6jmX3I0_AI-2=7(f^3?W&}|!N zOYhPXc4q+M`adSf=X)-NHXLhyh%EbDTX3O48 zY-q^Lv~;AhxmYr3kdvwU>e!xGOEQ+))v~$wYBQcQ$j#(Vrg!Z!GfXFUR+!(Z2UjzB z`qFz-$#krbskRd1I(rzbGlpWhfwkGJIoO*N!HX*KUtG?ENej$<@nk-m*rPXpvo;<# zv)Qg#DyCICuUtYRl`}J`ShA%bj4jD@d^fDrvVPj>5bi!VkxJy&FkMl1f=!-qTW`FF z`b(J{b@i9}shZ~a$e*4GQ>Z9@Y41Ce8Aai{|^uLtgo|z)wD^l5eN@$xk!n z+o??R;3$dy;dO~@E|ciZi+^r^NvCs6o9a6C*(cI1vdrqv2~X(BiIc6aue3kgd6w9; zUA!9B^q0#rXc6pd?!%f{z5mPnw1k%WXfd5W$>pqt460)a=w9fTY-vv?lkh}nnl-3_ z$!iV{<%T;UjcHC@edh$H^sV+&n3{alNX=kPqDNG~y0h!*FQ2?cKb=W`%S4OiOtYqp z8FV&N&4lQ+nM_QtX;7<=R?5LuC9>-h8EBTy4EyOEYeNxiFxH0~5UA^%dY4wq#?Phm zWIh~i=48bUx`4?@WY^2?{M1HEt7t8?GhKC|HhHM8?94)EEX|$~>Pws1P%51Z#nY)= zERhPuQo~@gbV``n=Fs|oeM4xNeW6@B)SpQ8h0J}icrG~{T2|s4ZuZc6oKJr`GZ=$O zɵ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 Hv&Q@%MAWx6 delta 37584 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 Date: Fri, 29 May 2026 11:38:56 +0200 Subject: [PATCH 08/18] Trust resolved version over declared dep in dependency search (#181) * Trust resolved version over declared dep in dependency search PR #179 extended ModuleHasDependency / RepositoryHasDependency to also match against requested/declared dependencies so they still match when resolution fails. This introduced false positives whenever a version range is supplied: the declared version string could satisfy the comparator even when the resolved version did not (e.g. a Gradle resolutionStrategy.force or platform alignment overriding the declared coordinate). - When a coordinate is present in the resolved dependencies, trust the resolved check and skip the declared-dependency fallback for that group:artifact. Only fall back for declared deps not already resolved. - Tighten versionMatches so a null declared version no longer auto-matches when a version constraint is supplied (same treatment as a ${...} property reference). Adds regression tests covering both rules in ModuleHasDependencyTest and RepositoryHasDependencyTest. * Apply suggestions from code review Co-authored-by: Jente Sondervorst * Add Maven regression tests for BOM-managed dependency version range --------- Co-authored-by: Jente Sondervorst --- .../search/ModuleHasDependency.java | 20 +++- .../search/RepositoryHasDependency.java | 22 ++++- .../search/ModuleHasDependencyTest.java | 94 +++++++++++++++++++ .../search/RepositoryHasDependencyTest.java | 91 ++++++++++++++++++ 4 files changed, 219 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java b/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java index 0b73c394..469fc9df 100644 --- a/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java @@ -112,12 +112,17 @@ private boolean hasDependency(Tree tree) { if (mavenResult != null) { Scope requestedScope = scope == null ? null : Scope.fromName(scope); List dependencies = mavenResult.findDependencies(groupIdPattern, artifactIdPattern, requestedScope); + Set resolvedGAs = new HashSet<>(); for (ResolvedDependency dependency : dependencies) { + resolvedGAs.add(dependency.getGroupId() + ":" + dependency.getArtifactId()); if (versionComparator == null || versionComparator.isValid(null, dependency.getVersion())) { return true; } } for (Dependency requested : mavenResult.getPom().getRequestedDependencies()) { + if (resolvedGAs.contains(requested.getGroupId() + ":" + requested.getArtifactId())) { + continue; + } if (matchesRequested(requested, requestedScope, versionComparator)) { return true; } @@ -127,16 +132,23 @@ private boolean hasDependency(Tree tree) { GradleProject gp = tree.getMarkers().findFirst(GradleProject.class).orElse(null); if (gp != null) { + Set resolvedGAs = new HashSet<>(); for (GradleDependencyConfiguration c : gp.getConfigurations()) { for (ResolvedDependency resolvedDependency : c.getDirectResolved()) { ResolvedDependency found = resolvedDependency.findDependency(groupIdPattern, artifactIdPattern); - if (found != null && (versionComparator == null || versionComparator.isValid(null, found.getVersion()))) { - return true; + if (found != null) { + resolvedGAs.add(found.getGroupId() + ":" + found.getArtifactId()); + if (versionComparator == null || versionComparator.isValid(null, found.getVersion())) { + return true; + } } } } for (GradleDependencyConfiguration c : gp.getConfigurations()) { for (Dependency requested : c.getRequested()) { + if (resolvedGAs.contains(requested.getGroupId() + ":" + requested.getArtifactId())) { + continue; + } if (matchesRequested(requested, null, versionComparator)) { return true; } @@ -166,10 +178,10 @@ private boolean matchesRequested(Dependency dep, @Nullable Scope requestedScope, } private static boolean versionMatches(@Nullable String version, @Nullable VersionComparator cmp) { - if (cmp == null || version == null) { + if (cmp == null) { return true; } - if (version.startsWith("${")) { + if (version == null || version.startsWith("${")) { return false; } return cmp.isValid(null, version); diff --git a/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java b/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java index b982d0e2..59c48ee8 100644 --- a/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java @@ -31,7 +31,9 @@ import org.openrewrite.semver.Semver; import org.openrewrite.semver.VersionComparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @EqualsAndHashCode(callSuper = false) @@ -107,12 +109,17 @@ private boolean hasDependency(Tree tree) { if (mavenResult != null) { Scope requestedScope = scope == null ? null : Scope.fromName(scope); List dependencies = mavenResult.findDependencies(groupIdPattern, artifactIdPattern, requestedScope); + Set resolvedGAs = new HashSet<>(); for (ResolvedDependency dependency : dependencies) { + resolvedGAs.add(dependency.getGroupId() + ":" + dependency.getArtifactId()); if (versionComparator == null || versionComparator.isValid(null, dependency.getVersion())) { return true; } } for (Dependency requested : mavenResult.getPom().getRequestedDependencies()) { + if (resolvedGAs.contains(requested.getGroupId() + ":" + requested.getArtifactId())) { + continue; + } if (matchesRequested(requested, requestedScope, versionComparator)) { return true; } @@ -122,16 +129,23 @@ private boolean hasDependency(Tree tree) { GradleProject gp = tree.getMarkers().findFirst(GradleProject.class).orElse(null); if (gp != null) { + Set resolvedGAs = new HashSet<>(); for (GradleDependencyConfiguration c : gp.getConfigurations()) { for (ResolvedDependency resolvedDependency : c.getDirectResolved()) { ResolvedDependency found = resolvedDependency.findDependency(groupIdPattern, artifactIdPattern); - if (found != null && (versionComparator == null || versionComparator.isValid(null, found.getVersion()))) { - return true; + if (found != null) { + resolvedGAs.add(found.getGroupId() + ":" + found.getArtifactId()); + if (versionComparator == null || versionComparator.isValid(null, found.getVersion())) { + return true; + } } } } for (GradleDependencyConfiguration c : gp.getConfigurations()) { for (Dependency requested : c.getRequested()) { + if (resolvedGAs.contains(requested.getGroupId() + ":" + requested.getArtifactId())) { + continue; + } if (matchesRequested(requested, null, versionComparator)) { return true; } @@ -161,10 +175,10 @@ private boolean matchesRequested(Dependency dep, @Nullable Scope requestedScope, } private static boolean versionMatches(@Nullable String version, @Nullable VersionComparator cmp) { - if (cmp == null || version == null) { + if (cmp == null) { return true; } - if (version.startsWith("${")) { + if (version == null || version.startsWith("${")) { return false; } return cmp.isValid(null, version); diff --git a/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java b/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java index 0efe3269..a5ae2358 100644 --- a/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java @@ -454,6 +454,100 @@ void gradleVersionRangeOnRequestedDoesNotMatchWhenOutOfRange() { ) ); } + + @Language("groovy") + private final static String GradleNoRepositoriesNoVersion = """ + plugins { + id 'java-library' + } + dependencies { + implementation 'org.springframework:spring-beans' + } + """; + + @Test + void gradleRequestedWithoutVersionAndConstraintDoesNotMatch() { + // Force resolution failure (no repositories), so the requested fallback fires. + rewriteRun( + spec -> spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, "[1.0,)", null)), + mavenProject("project-gradle", + buildGradle(GradleNoRepositoriesNoVersion), + java(GradleJava) + ) + ); + } + } + + @Nested + class WhenResolvedVersionIsSourceOfTruth { + + @Language("groovy") + private final static String GradleForcedOutOfRange = """ + plugins { + id 'java-library' + } + repositories { + mavenCentral() + } + configurations.all { + resolutionStrategy { + force 'org.springframework:spring-beans:6.0.0' + } + } + dependencies { + implementation 'org.springframework:spring-beans:5.3.0' + } + """; + + @Test + void gradleVersionRangeDoesNotMatchDeclaredWhenResolvedVersionIsOutOfRange() { + // The declared-dependency fallback must be skipped for an already-resolved coordinate (resolutionStrategy). + rewriteRun( + spec -> spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, "[5.0,6.0)", null)), + mavenProject("project-gradle", + buildGradle(GradleForcedOutOfRange), + java(GradleJava) + ) + ); + } + + @Language("xml") + private final static String MavenBomManagedOutOfRange = """ + + com.example + foo + 1.0.0 + + + + org.springframework.boot + spring-boot-dependencies + 3.0.0 + pom + import + + + + + + org.springframework + spring-beans + + + + """; + + @Test + void mavenVersionRangeDoesNotMatchBomManagedDependencyWhenResolvedIsOutOfRange() { + // Regression for rewrite-third-party#76: BOM-managed version (null on requested) must not match the range. + rewriteRun( + spec -> spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, "[5.0,6.0)", null)), + mavenProject("project-maven", + pomXml(MavenBomManagedOutOfRange), + java(MavenJava) + ) + ); + } } @Nested diff --git a/src/test/java/org/openrewrite/java/dependencies/search/RepositoryHasDependencyTest.java b/src/test/java/org/openrewrite/java/dependencies/search/RepositoryHasDependencyTest.java index c3058c0b..becc7434 100644 --- a/src/test/java/org/openrewrite/java/dependencies/search/RepositoryHasDependencyTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/search/RepositoryHasDependencyTest.java @@ -15,12 +15,15 @@ */ package org.openrewrite.java.dependencies.search; +import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import static org.openrewrite.gradle.Assertions.buildGradle; import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; +import static org.openrewrite.java.Assertions.java; import static org.openrewrite.java.Assertions.mavenProject; import static org.openrewrite.maven.Assertions.pomXml; @@ -84,4 +87,92 @@ void usedAsDeclarativePrecondition() { ) ); } + + @Language("java") + private final static String GradleJava = """ + public class AGradle {} + """; + + @Test + void gradleVersionRangeDoesNotMatchDeclaredWhenResolvedVersionIsOutOfRange() { + // The declared-dependency fallback must be skipped for an already-resolved coordinate (resolutionStrategy). + rewriteRun( + spec -> spec.recipe(new RepositoryHasDependency("org.springframework", "spring-beans", null, "[5.0,6.0)")), + mavenProject("project-gradle", + //language=groovy + buildGradle(""" + plugins { + id 'java-library' + } + repositories { + mavenCentral() + } + configurations.all { + resolutionStrategy { + force 'org.springframework:spring-beans:6.0.0' + } + } + dependencies { + implementation 'org.springframework:spring-beans:5.3.0' + } + """), + java(GradleJava) + ) + ); + } + + @Test + void mavenVersionRangeDoesNotMatchBomManagedDependencyWhenResolvedIsOutOfRange() { + // Regression for rewrite-third-party#76: BOM-managed version (null on requested) must not match the range. + rewriteRun( + spec -> spec.recipe(new RepositoryHasDependency("org.springframework", "spring-beans", null, "[5.0,6.0)")), + mavenProject("project-maven", + //language=xml + pomXml(""" + + com.example + foo + 1.0.0 + + + + org.springframework.boot + spring-boot-dependencies + 3.0.0 + pom + import + + + + + + org.springframework + spring-beans + + + + """) + ) + ); + } + + @Test + void gradleRequestedWithoutVersionAndConstraintDoesNotMatch() { + // Force resolution failure (no repositories), so the requested fallback fires. + rewriteRun( + spec -> spec.recipe(new RepositoryHasDependency("org.springframework", "spring-beans", null, "[1.0,)")), + mavenProject("project-gradle", + //language=groovy + buildGradle(""" + plugins { + id 'java-library' + } + dependencies { + implementation 'org.springframework:spring-beans' + } + """), + java(GradleJava) + ) + ); + } } From 203fec4f8b50365ba394287fce8ec45968e84968 Mon Sep 17 00:00:00 2001 From: Greg Oledzki Date: Tue, 9 Jun 2026 11:30:21 +0000 Subject: [PATCH 09/18] git-ignore .context/ Use this link to re-run the recipe: https://app.moderne.io/recipes/org.openrewrite.AddToGitignore?organizationId=QUxML01vZGVybmUgKyBPcGVuUmV3cml0ZQ%3D%3D#defaults=W3sidmFsdWUiOiIuY29udGV4dC8iLCJuYW1lIjoiZW50cmllcyJ9XQ== Co-authored-by: Moderne --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5f519985..8f15722d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ bin/ *.log advisory-database/ oga-maven-plugin/ + +.context/ From 027334841e2cf81a2ff6f2f2bb03a953efecd1ff Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Tue, 16 Jun 2026 16:54:31 -0400 Subject: [PATCH 10/18] Fix RemoveDependency.unlessUsing for multi-module Maven projects (#183) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix `RemoveDependency.unlessUsing` for multi-module Maven projects `RemoveDependency` scans every source file and records whether `unlessUsing` matched against the file's `JavaProject` marker. In a multi-module Maven project the parent pom carries its own `JavaProject` distinct from the children, so the parent module's accumulator entry never reflects type usages found in child modules. The visitor would then strip the `` from the parent pom even though child modules in the same reactor still relied on inheriting it. Now the scanner additionally records the `MavenResolutionResult.id -> JavaProject` mapping for every pom it visits. When the visitor is about to remove a `` from a pom, it walks that pom's descendant modules via `MavenResolutionResult.getModules()` and preserves the declaration if any descendant's `JavaProject` shows the type in use. * Allow `RemoveDependency` to remove ancestor decl when descendants self-declare The descendant-aware preservation introduced in the previous commit was intentionally conservative: any descendant module whose Java sources used the `unlessUsing` type blocked removal of the dep from an ancestor pom. That over-preserves when a descendant module also declares the same dependency directly in its own `` — in that case the descendant is self-sufficient and the ancestor's declaration is dead weight. Refine the check: a descendant only blocks removal if it uses the type AND does not declare the dependency itself. The self-declaration check looks at the raw `requested` pom (as-written), not the resolved/inherited dependency list, so a child that inherits the dep from the ancestor we might modify still correctly blocks removal. --- .../java/dependencies/RemoveDependency.java | 65 +++- .../dependencies/RemoveDependencyTest.java | 341 ++++++++++++++++++ 2 files changed, 398 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveDependency.java b/src/main/java/org/openrewrite/java/dependencies/RemoveDependency.java index bc28a50e..ee67bf53 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveDependency.java @@ -21,13 +21,18 @@ import org.openrewrite.*; import org.openrewrite.java.marker.JavaProject; import org.openrewrite.java.search.UsesType; +import org.openrewrite.maven.tree.Dependency; +import org.openrewrite.maven.tree.MavenResolutionResult; import java.util.HashMap; import java.util.Map; +import java.util.UUID; + +import static org.openrewrite.internal.StringUtils.matchesGlob; @EqualsAndHashCode(callSuper = false) @Value -public class RemoveDependency extends ScanningRecipe> { +public class RemoveDependency extends ScanningRecipe { @Option(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*") @@ -68,13 +73,18 @@ public class RemoveDependency extends ScanningRecipe> String description = "For Gradle project, removes a single dependency from the dependencies section of the `build.gradle`.\n" + "For Maven project, removes a single dependency from the `` section of the pom.xml."; + public static class Accumulator { + final Map projectToInUse = new HashMap<>(); + final Map mavenIdToProject = new HashMap<>(); + } + @Override - public Map getInitialValue(ExecutionContext ctx) { - return new HashMap<>(); + public Accumulator getInitialValue(ExecutionContext ctx) { + return new Accumulator(); } @Override - public TreeVisitor getScanner(Map projectToInUse) { + public TreeVisitor getScanner(Accumulator acc) { if (unlessUsing == null) { return TreeVisitor.noop(); } @@ -83,15 +93,19 @@ public TreeVisitor getScanner(Map pro @Override public Tree preVisit(Tree tree, ExecutionContext ctx) { stopAfterPreVisit(); - tree.getMarkers().findFirst(JavaProject.class).ifPresent(javaProject -> - projectToInUse.compute(javaProject, (jp, foundSoFar) -> Boolean.TRUE.equals(foundSoFar) || tree != usesType.visit(tree, ctx))); + tree.getMarkers().findFirst(JavaProject.class).ifPresent(javaProject -> { + acc.projectToInUse.compute(javaProject, (jp, foundSoFar) -> + Boolean.TRUE.equals(foundSoFar) || tree != usesType.visit(tree, ctx)); + tree.getMarkers().findFirst(MavenResolutionResult.class).ifPresent(mrr -> + acc.mavenIdToProject.put(mrr.getId(), javaProject)); + }); return tree; } }; } @Override - public TreeVisitor getVisitor(Map projectToInUse) { + public TreeVisitor getVisitor(Accumulator acc) { return new TreeVisitor() { final TreeVisitor gradleRemoveDep = new org.openrewrite.gradle.RemoveDependency(groupId, artifactId, configuration).getVisitor(); final TreeVisitor mavenRemoveDep = new org.openrewrite.maven.RemoveDependency(groupId, artifactId, scope).getVisitor(); @@ -103,7 +117,11 @@ public TreeVisitor getVisitor(Map pro } if (unlessUsing != null) { JavaProject jp = tree.getMarkers().findFirst(JavaProject.class).orElse(null); - if (jp == null || Boolean.TRUE.equals(projectToInUse.get(jp))) { + if (jp == null || Boolean.TRUE.equals(acc.projectToInUse.get(jp))) { + return tree; + } + MavenResolutionResult mrr = tree.getMarkers().findFirst(MavenResolutionResult.class).orElse(null); + if (mrr != null && anyDescendantPreservesDependency(mrr, acc)) { return tree; } } @@ -118,4 +136,35 @@ public TreeVisitor getVisitor(Map pro } }; } + + /** + * Keep this pom's `` declaration if any descendant module in the reactor uses the + * `unlessUsing` type AND does not also declare the dependency itself. A descendant that + * self-declares the dependency in its own pom is unaffected by removing it from an ancestor, + * so it should not block removal. + */ + private boolean anyDescendantPreservesDependency(MavenResolutionResult mrr, Accumulator acc) { + for (MavenResolutionResult child : mrr.getModules()) { + JavaProject childJp = acc.mavenIdToProject.get(child.getId()); + if (childJp != null && Boolean.TRUE.equals(acc.projectToInUse.get(childJp)) && !declaresDependency(child)) { + return true; + } + if (anyDescendantPreservesDependency(child, acc)) { + return true; + } + } + return false; + } + + private boolean declaresDependency(MavenResolutionResult mrr) { + // Look at the raw `requested` pom — the as-written `` of this module only, + // excluding inheritance from a parent pom. A module that inherits the dep from the parent + // we may be modifying should still block removal. + for (Dependency d : mrr.getPom().getRequested().getDependencies()) { + if (matchesGlob(d.getGroupId(), groupId) && matchesGlob(d.getArtifactId(), artifactId)) { + return true; + } + } + return false; + } } diff --git a/src/test/java/org/openrewrite/java/dependencies/RemoveDependencyTest.java b/src/test/java/org/openrewrite/java/dependencies/RemoveDependencyTest.java index 5482189d..d4858119 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RemoveDependencyTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RemoveDependencyTest.java @@ -177,6 +177,347 @@ class MyLoggingInterceptor { ); } + @Test + void doNotRemoveSpringRetryWhenRetryTemplateInUse() { + rewriteRun( + spec -> spec + .parser(JavaParser.fromJavaVersion().dependsOn( + //language=java + """ + package org.springframework.retry.support; + public class RetryTemplate { + } + """, + """ + package org.springframework.retry.policy; + public class SimpleRetryPolicy { + } + """, + """ + package org.springframework.retry.backoff; + public class ExponentialBackOffPolicy { + } + """ + )) + .recipe(new RemoveDependency("org.springframework.retry", "spring-retry", "org.springframework.retry..*", null, null)), + mavenProject("example", + //language=java + srcMainJava( + java( + """ + import org.springframework.retry.support.RetryTemplate; + import org.springframework.retry.policy.SimpleRetryPolicy; + import org.springframework.retry.backoff.ExponentialBackOffPolicy; + + class RetryConfig { + RetryTemplate retryTemplate() { + new SimpleRetryPolicy(); + new ExponentialBackOffPolicy(); + return new RetryTemplate(); + } + } + """ + ) + ), + //language=xml + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + org.springframework.retry + spring-retry + 2.0.0 + + + + """ + ) + ) + ); + } + + @Test + void doNotRemoveSpringRetryWhenOnlyImportsPresent() { + rewriteRun( + spec -> spec + .parser(JavaParser.fromJavaVersion().dependsOn( + //language=java + """ + package org.springframework.retry.support; + public class RetryTemplate { + } + """ + )) + .recipe(new RemoveDependency("org.springframework.retry", "spring-retry", "org.springframework.retry..*", null, null)), + mavenProject("example", + //language=java + srcMainJava( + java( + """ + import org.springframework.retry.support.RetryTemplate; + + class RetryConfig { + // imports but no body usage — should still keep the dep + } + """ + ) + ), + //language=xml + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + org.springframework.retry + spring-retry + 2.0.0 + + + + """ + ) + ) + ); + } + + @Test + void doNotRemoveSpringRetryInMultiModuleWhenChildUsesIt() { + rewriteRun( + spec -> spec + .parser(JavaParser.fromJavaVersion().dependsOn( + //language=java + """ + package org.springframework.retry.support; + public class RetryTemplate { + } + """ + )) + .recipe(new RemoveDependency("org.springframework.retry", "spring-retry", "org.springframework.retry..*", null, null)), + mavenProject("parent", + //language=xml + pomXml( + """ + + 4.0.0 + com.mycompany.app + parent + 1 + pom + + + org.springframework.retry + spring-retry + 2.0.0 + + + + child + + + """ + ), + mavenProject("child", + //language=xml + pomXml( + """ + + 4.0.0 + + com.mycompany.app + parent + 1 + + child + + """ + ), + //language=java + srcMainJava( + java( + """ + import org.springframework.retry.support.RetryTemplate; + + class RetryConfig { + RetryTemplate retryTemplate() { + return new RetryTemplate(); + } + } + """ + ) + ) + ) + ) + ); + } + + @Test + void doNotRemoveSpringRetryWhenDepAndUsageInSameChildModule() { + rewriteRun( + spec -> spec + .parser(JavaParser.fromJavaVersion().dependsOn( + //language=java + """ + package org.springframework.retry.support; + public class RetryTemplate { + } + """ + )) + .recipe(new RemoveDependency("org.springframework.retry", "spring-retry", "org.springframework.retry..*", null, null)), + mavenProject("parent", + //language=xml + pomXml( + """ + + 4.0.0 + com.mycompany.app + parent + 1 + pom + + child + + + """ + ), + mavenProject("child", + //language=xml + pomXml( + """ + + 4.0.0 + + com.mycompany.app + parent + 1 + + child + + + org.springframework.retry + spring-retry + 2.0.0 + + + + """ + ), + //language=java + srcMainJava( + java( + """ + import org.springframework.retry.support.RetryTemplate; + + class RetryConfig { + RetryTemplate retryTemplate() { + return new RetryTemplate(); + } + } + """ + ) + ) + ) + ) + ); + } + + @Test + void removeParentDependencyWhenChildSelfDeclaresAndUses() { + rewriteRun( + spec -> spec + .parser(JavaParser.fromJavaVersion().dependsOn( + //language=java + """ + package org.springframework.retry.support; + public class RetryTemplate { + } + """ + )) + .recipe(new RemoveDependency("org.springframework.retry", "spring-retry", "org.springframework.retry..*", null, null)), + mavenProject("parent", + //language=xml + pomXml( + """ + + 4.0.0 + com.mycompany.app + parent + 1 + pom + + + org.springframework.retry + spring-retry + 2.0.0 + + + + child + + + """, + """ + + 4.0.0 + com.mycompany.app + parent + 1 + pom + + child + + + """ + ), + mavenProject("child", + //language=xml + pomXml( + """ + + 4.0.0 + + com.mycompany.app + parent + 1 + + child + + + org.springframework.retry + spring-retry + 2.0.0 + + + + """ + ), + //language=java + srcMainJava( + java( + """ + import org.springframework.retry.support.RetryTemplate; + + class RetryConfig { + RetryTemplate retryTemplate() { + return new RetryTemplate(); + } + } + """ + ) + ) + ) + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite-java-dependencies/issues/11") @Test void doRemoveIfNotInUse() { From 3df816053aa28bdfd4baeb1f249d2d7d95f3a963 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 17 Jun 2026 17:36:11 +0000 Subject: [PATCH 11/18] Remove out-of-date OWASP suppressions Use this link to re-run the recipe: https://app.moderne.io/recipes/org.openrewrite.xml.security.RemoveOwaspSuppressions?organizationId=QUxML01vZGVybmUgKyBPcGVuUmV3cml0ZQ%3D%3D#defaults=W3sidmFsdWUiOiIyMDI2LTA2LTE3IiwibmFtZSI6ImN1dE9mZkRhdGUifV0= Co-authored-by: Moderne --- suppressions.xml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 suppressions.xml diff --git a/suppressions.xml b/suppressions.xml deleted file mode 100644 index cbc052c4..00000000 --- a/suppressions.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file From 0bc54b00479a1f5bdf65a2bdf2a92f5ddfbb77cb Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 19 Jun 2026 09:42:40 +0000 Subject: [PATCH 12/18] Use GitHub actions/checkout@v7 Use this link to re-run the recipe: https://app.moderne.io/recipes/org.openrewrite.github.ChangeActionVersion?organizationId=QUxML01vZGVybmUgKyBPcGVuUmV3cml0ZQ%3D%3D#defaults=W3sidmFsdWUiOiJhY3Rpb25zL2NoZWNrb3V0IiwibmFtZSI6ImFjdGlvbiJ9LHsidmFsdWUiOiJ2NyIsIm5hbWUiOiJ2ZXJzaW9uIn1d Co-authored-by: Moderne --- .github/workflows/migrations.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index 38519f65..e9526d2d 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -13,7 +13,7 @@ jobs: steps: # Checkout and build parser - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - uses: actions/setup-java@v5 @@ -24,7 +24,7 @@ jobs: # Update migrations - name: Checkout oga-maven-plugin - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: jonathanlermitage/oga-maven-plugin path: oga-maven-plugin From 85ae1d398b7add245dd55e47175f6cfd08890e40 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 21 Jun 2026 23:00:03 +0200 Subject: [PATCH 13/18] Cache delegate recipes per recipe instance to avoid repeated construction (#184) The Gradle/Maven dependency wrapper recipes (`AddDependency`, `ChangeDependency`, `UpgradeDependencyVersion`, `UpgradeTransitiveDependencyVersion`) each delegate to a Gradle and a Maven `ScanningRecipe`. Previously a fresh delegate instance was constructed on every `getInitialValue`/`getScanner`/`getVisitor`/`validate` call (and, in `AddDependency`, once per source file inside the scanner's `visit`). Each `ScanningRecipe` construction runs `UUID.randomUUID()` for its accumulator-message key, which draws from `SecureRandom` and dominated profiles of recipe-heavy runs. Memoize each delegate lazily on the wrapper recipe instance (a `final transient AtomicReference` that is excluded from the generated constructor and from `equals`/`hashCode`) so it is built at most once per wrapper instance and reused across the scanning and editing phases. The delegate is derived from the wrapper's options, so it belongs to the recipe instance rather than the accumulator: callers such as `rewrite-spring` deliberately reuse a single accumulator across two wrapper instances that have different options, so caching the delegate on the accumulator would reuse the wrong options. The `Accumulator` types are left unchanged, preserving their public constructors for downstream callers that build them directly. --- .../java/dependencies/AddDependency.java | 47 ++++++++++++++----- .../java/dependencies/ChangeDependency.java | 31 +++++++++--- .../UpgradeDependencyVersion.java | 22 ++++++++- .../UpgradeTransitiveDependencyVersion.java | 24 +++++++++- 4 files changed, 102 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/AddDependency.java b/src/main/java/org/openrewrite/java/dependencies/AddDependency.java index f3733a00..8e50a48b 100644 --- a/src/main/java/org/openrewrite/java/dependencies/AddDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/AddDependency.java @@ -15,12 +15,16 @@ */ package org.openrewrite.java.dependencies; +import lombok.AccessLevel; import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.maven.tree.Scope; +import java.util.concurrent.atomic.AtomicReference; + @EqualsAndHashCode(callSuper = false) @Value public class AddDependency extends ScanningRecipe { @@ -141,10 +145,13 @@ public Accumulator getInitialValue(ExecutionContext ctx) { @Override public TreeVisitor getScanner(Accumulator acc) { return new TreeVisitor() { + final TreeVisitor gradleAddDep = gradleAddDep().getScanner(acc.gradleAccumulator); + final TreeVisitor mavenAddDep = mavenAddDep().getScanner(acc.mavenAccumulator); + @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { - gradleAddDep().getScanner(acc.gradleAccumulator).visit(tree, ctx); - mavenAddDep().getScanner(acc.mavenAccumulator).visit(tree, ctx); + gradleAddDep.visit(tree, ctx); + mavenAddDep.visit(tree, ctx); return tree; } }; @@ -184,20 +191,36 @@ public static class Accumulator { org.openrewrite.maven.AddDependency.Scanned mavenAccumulator; } + @Getter(AccessLevel.NONE) + private final transient AtomicReference gradleDelegate = new AtomicReference<>(); + + @Getter(AccessLevel.NONE) + private final transient AtomicReference mavenDelegate = new AtomicReference<>(); + private org.openrewrite.gradle.AddDependency gradleAddDep() { - String configurationName = null; - if(configuration != null) { - configurationName = configuration; - } else if(scope != null) { - configurationName = Scope.asGradleConfigurationName(Scope.fromName(scope)); + org.openrewrite.gradle.AddDependency recipe = gradleDelegate.get(); + if (recipe == null) { + String configurationName = null; + if(configuration != null) { + configurationName = configuration; + } else if(scope != null) { + configurationName = Scope.asGradleConfigurationName(Scope.fromName(scope)); + } + recipe = new org.openrewrite.gradle.AddDependency(groupId, artifactId, version, versionPattern, + configurationName, onlyIfUsing, classifier, extension, familyPattern, acceptTransitive); + gradleDelegate.set(recipe); } - return new org.openrewrite.gradle.AddDependency(groupId, artifactId, version, versionPattern, - configurationName, onlyIfUsing, classifier, extension, familyPattern, acceptTransitive); + return recipe; } private org.openrewrite.maven.AddDependency mavenAddDep() { - return new org.openrewrite.maven.AddDependency(groupId, artifactId, version != null ? version : "latest.release", - versionPattern, scope, releasesOnly, onlyIfUsing, type, classifier, optional, familyPattern, - acceptTransitive); + org.openrewrite.maven.AddDependency recipe = mavenDelegate.get(); + if (recipe == null) { + recipe = new org.openrewrite.maven.AddDependency(groupId, artifactId, version != null ? version : "latest.release", + versionPattern, scope, releasesOnly, onlyIfUsing, type, classifier, optional, familyPattern, + acceptTransitive); + mavenDelegate.set(recipe); + } + return recipe; } } diff --git a/src/main/java/org/openrewrite/java/dependencies/ChangeDependency.java b/src/main/java/org/openrewrite/java/dependencies/ChangeDependency.java index 2b5e78d4..28bf5eb4 100644 --- a/src/main/java/org/openrewrite/java/dependencies/ChangeDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/ChangeDependency.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java.dependencies; +import lombok.AccessLevel; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -22,6 +23,8 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; +import java.util.concurrent.atomic.AtomicReference; + import static java.util.Objects.requireNonNull; @EqualsAndHashCode(callSuper = false) @@ -152,16 +155,32 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { }; } + @Getter(AccessLevel.NONE) + private final transient AtomicReference mavenDelegate = new AtomicReference<>(); + + @Getter(AccessLevel.NONE) + private final transient AtomicReference gradleDelegate = new AtomicReference<>(); + org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId getMavenChangeDependency() { - return new org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId( - oldGroupId, oldArtifactId, newGroupId, newArtifactId, - newVersion, versionPattern, overrideManagedVersion, changeManagedDependency); + org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId recipe = mavenDelegate.get(); + if (recipe == null) { + recipe = new org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId( + oldGroupId, oldArtifactId, newGroupId, newArtifactId, + newVersion, versionPattern, overrideManagedVersion, changeManagedDependency); + mavenDelegate.set(recipe); + } + return recipe; } org.openrewrite.gradle.ChangeDependency getGradleChangeDependency() { - return new org.openrewrite.gradle.ChangeDependency( - oldGroupId, oldArtifactId, newGroupId, newArtifactId, - newVersion, versionPattern, overrideManagedVersion, true); + org.openrewrite.gradle.ChangeDependency recipe = gradleDelegate.get(); + if (recipe == null) { + recipe = new org.openrewrite.gradle.ChangeDependency( + oldGroupId, oldArtifactId, newGroupId, newArtifactId, + newVersion, versionPattern, overrideManagedVersion, true); + gradleDelegate.set(recipe); + } + return recipe; } @Data diff --git a/src/main/java/org/openrewrite/java/dependencies/UpgradeDependencyVersion.java b/src/main/java/org/openrewrite/java/dependencies/UpgradeDependencyVersion.java index b2c0e14a..b3d41021 100644 --- a/src/main/java/org/openrewrite/java/dependencies/UpgradeDependencyVersion.java +++ b/src/main/java/org/openrewrite/java/dependencies/UpgradeDependencyVersion.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java.dependencies; +import lombok.AccessLevel; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -23,6 +24,7 @@ import org.openrewrite.*; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import static java.util.Objects.requireNonNull; @@ -139,12 +141,28 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { }; } + @Getter(AccessLevel.NONE) + private final transient AtomicReference mavenDelegate = new AtomicReference<>(); + + @Getter(AccessLevel.NONE) + private final transient AtomicReference gradleDelegate = new AtomicReference<>(); + org.openrewrite.maven.UpgradeDependencyVersion getUpgradeMavenDependencyVersion() { - return new org.openrewrite.maven.UpgradeDependencyVersion(groupId, artifactId, newVersion, versionPattern, overrideManagedVersion, retainVersions); + org.openrewrite.maven.UpgradeDependencyVersion recipe = mavenDelegate.get(); + if (recipe == null) { + recipe = new org.openrewrite.maven.UpgradeDependencyVersion(groupId, artifactId, newVersion, versionPattern, overrideManagedVersion, retainVersions); + mavenDelegate.set(recipe); + } + return recipe; } public org.openrewrite.gradle.UpgradeDependencyVersion getUpgradeGradleDependencyVersion() { - return new org.openrewrite.gradle.UpgradeDependencyVersion(groupId, artifactId, newVersion, versionPattern); + org.openrewrite.gradle.UpgradeDependencyVersion recipe = gradleDelegate.get(); + if (recipe == null) { + recipe = new org.openrewrite.gradle.UpgradeDependencyVersion(groupId, artifactId, newVersion, versionPattern); + gradleDelegate.set(recipe); + } + return recipe; } @Data diff --git a/src/main/java/org/openrewrite/java/dependencies/UpgradeTransitiveDependencyVersion.java b/src/main/java/org/openrewrite/java/dependencies/UpgradeTransitiveDependencyVersion.java index a12e0bcd..250ee033 100644 --- a/src/main/java/org/openrewrite/java/dependencies/UpgradeTransitiveDependencyVersion.java +++ b/src/main/java/org/openrewrite/java/dependencies/UpgradeTransitiveDependencyVersion.java @@ -15,12 +15,16 @@ */ package org.openrewrite.java.dependencies; +import lombok.AccessLevel; import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.maven.AddManagedDependency; +import java.util.concurrent.atomic.AtomicReference; + @EqualsAndHashCode(callSuper = false) @Value public class UpgradeTransitiveDependencyVersion extends ScanningRecipe { @@ -154,11 +158,27 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { }; } + @Getter(AccessLevel.NONE) + private final transient AtomicReference gradleDelegate = new AtomicReference<>(); + + @Getter(AccessLevel.NONE) + private final transient AtomicReference mavenDelegate = new AtomicReference<>(); + private org.openrewrite.gradle.UpgradeTransitiveDependencyVersion getGradleUpgradeTransitive() { - return new org.openrewrite.gradle.UpgradeTransitiveDependencyVersion(groupId, artifactId, version, versionPattern, because, null); + org.openrewrite.gradle.UpgradeTransitiveDependencyVersion recipe = gradleDelegate.get(); + if (recipe == null) { + recipe = new org.openrewrite.gradle.UpgradeTransitiveDependencyVersion(groupId, artifactId, version, versionPattern, because, null); + gradleDelegate.set(recipe); + } + return recipe; } private org.openrewrite.maven.UpgradeTransitiveDependencyVersion getMavenUpgradeTransitive() { - return new org.openrewrite.maven.UpgradeTransitiveDependencyVersion(groupId, artifactId, version, scope, type, classifier, versionPattern, releasesOnly, onlyIfUsing, addToRootPom, because); + org.openrewrite.maven.UpgradeTransitiveDependencyVersion recipe = mavenDelegate.get(); + if (recipe == null) { + recipe = new org.openrewrite.maven.UpgradeTransitiveDependencyVersion(groupId, artifactId, version, scope, type, classifier, versionPattern, releasesOnly, onlyIfUsing, addToRootPom, because); + mavenDelegate.set(recipe); + } + return recipe; } } From b88b73b37c3f9c36e78c46b032fc077398d57120 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 23 Jun 2026 10:46:27 +0200 Subject: [PATCH 14/18] Add software.amazon.ion:ion-java -> com.amazon.ion:ion-java relocation (stopgap) (#185) --- src/main/resources/migrations.csv | 1 + .../RelocatedDependencyCheckTest.java | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/main/resources/migrations.csv b/src/main/resources/migrations.csv index 6c9c8e23..88faa512 100644 --- a/src/main/resources/migrations.csv +++ b/src/main/resources/migrations.csv @@ -574,6 +574,7 @@ postgresql,,org.postgresql,, saxon,saxon,net.sf.saxon,saxon, servletapi,servlet-api,javax.servlet,servlet-api, servletapi,servletapi,javax.servlet,servlet-api, +software.amazon.ion,"ion-java","com.amazon.ion","ion-java", springframework,,org.springframework,, stax-utils,stax-utils,net.java.dev.stax-utils,stax-utils, tagsoup,tagsoup,org.ccil.cowan.tagsoup,tagsoup, diff --git a/src/test/java/org/openrewrite/java/dependencies/RelocatedDependencyCheckTest.java b/src/test/java/org/openrewrite/java/dependencies/RelocatedDependencyCheckTest.java index 61431cb8..04b9e482 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RelocatedDependencyCheckTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RelocatedDependencyCheckTest.java @@ -150,6 +150,48 @@ void changeRelocatedMavenDependencies() { ); } + @Test + void findRelocatedIonJava() { + rewriteRun( + recipe -> recipe.dataTable(RelocatedDependencyReport.Row.class, rows -> assertThat(rows).containsExactly( + new RelocatedDependencyReport.Row("software.amazon.ion", "ion-java", "com.amazon.ion", "ion-java", null) + )), + //language=xml + pomXml( + """ + + 4.0.0 + org.openrewrite.example + rewrite-example + 1.0-SNAPSHOT + + + software.amazon.ion + ion-java + 1.5.1 + + + + """, + """ + + 4.0.0 + org.openrewrite.example + rewrite-example + 1.0-SNAPSHOT + + + software.amazon.ion + ion-java + 1.5.1 + + + + """ + ) + ); + } + @Test void findRelocatedMavenPlugins() { rewriteRun( From 731d38f687fc4fedf16d8d2e80749c6d0c9b0b24 Mon Sep 17 00:00:00 2001 From: "team-moderne[bot]" Date: Tue, 23 Jun 2026 17:23:04 +0000 Subject: [PATCH 15/18] [Auto] Old GroupId migrations as of 2026-06-23T1723 --- src/main/resources/migrations.csv | 58 ++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/main/resources/migrations.csv b/src/main/resources/migrations.csv index 88faa512..a0a13c81 100644 --- a/src/main/resources/migrations.csv +++ b/src/main/resources/migrations.csv @@ -354,6 +354,62 @@ opensymphony,quartz-weblogic,org.opensymphony.quartz,quartz-weblogic, org.acegisecurity,,"org.springframework.security",, org.agrona,Agrona,org.agrona,agrona, org.akashihi.osm,parallelpbf,com.wolt,parallelpbf, +org.apache.activemq,artemis-amqp-protocol,org.apache.artemis,artemis-amqp-protocol,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-bom,org.apache.artemis,artemis-bom,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-boot,org.apache.artemis,artemis-boot,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-cdi-client,org.apache.artemis,artemis-cdi-client,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-cli,org.apache.artemis,artemis-cli,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-commons,org.apache.artemis,artemis-commons,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-console,org.apache.artemis,artemis-console,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-console-extension",org.apache.artemis,"artemis-console-extension","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-console-project,org.apache.artemis,artemis-console-project,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-console-war,org.apache.artemis,artemis-console-war,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-core-client,org.apache.artemis,artemis-core-client,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-core-client-all,org.apache.artemis,artemis-core-client-all,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-core-client-osgi,org.apache.artemis,artemis-core-client-osgi,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-dto,org.apache.artemis,artemis-dto,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-features,org.apache.artemis,artemis-features,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-hornetq-protocol,org.apache.artemis,artemis-hornetq-protocol,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-hqclient-protocol",org.apache.artemis,"artemis-hqclient-protocol","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-image,org.apache.artemis,artemis-image,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-image-examples,org.apache.artemis,artemis-image-examples,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-jakarta-cdi-client",org.apache.artemis,"artemis-jakarta-cdi-client","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-jakarta-client,org.apache.artemis,artemis-jakarta-client,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-jakarta-client-all",org.apache.artemis,"artemis-jakarta-client-all","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-jakarta-client-osgi",org.apache.artemis,"artemis-jakarta-client-osgi","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-jakarta-openwire-protocol",org.apache.artemis,"artemis-jakarta-openwire-protocol","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-jakarta-ra,org.apache.artemis,artemis-jakarta-ra,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-jakarta-server,org.apache.artemis,artemis-jakarta-server,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-jakarta-service-extensions",org.apache.artemis,"artemis-jakarta-service-extensions","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-jdbc-store,org.apache.artemis,artemis-jdbc-store,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-jms-client,org.apache.artemis,artemis-jms-client,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-jms-client-all,org.apache.artemis,artemis-jms-client-all,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-jms-client-osgi,org.apache.artemis,artemis-jms-client-osgi,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-jms-server,org.apache.artemis,artemis-jms-server,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-journal,org.apache.artemis,artemis-journal,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-junit,org.apache.artemis,artemis-junit,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-junit-5,org.apache.artemis,artemis-junit-5,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-junit-commons,org.apache.artemis,artemis-junit-commons,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-junit-parent,org.apache.artemis,artemis-junit-parent,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-lockmanager,org.apache.artemis,artemis-lockmanager,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-lockmanager-api,org.apache.artemis,artemis-lockmanager-api,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-lockmanager-ri,org.apache.artemis,artemis-lockmanager-ri,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-log-annotation-processor",org.apache.artemis,"artemis-log-annotation-processor","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-maven-plugin,org.apache.artemis,artemis-maven-plugin,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-mqtt-protocol,org.apache.artemis,artemis-mqtt-protocol,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-openwire-protocol",org.apache.artemis,"artemis-openwire-protocol","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-pom,org.apache.artemis,artemis-pom,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-project,org.apache.artemis,artemis-project,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-protocols,org.apache.artemis,artemis-protocols,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-ra,org.apache.artemis,artemis-ra,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-selector,org.apache.artemis,artemis-selector,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-server,org.apache.artemis,artemis-server,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-server-osgi,org.apache.artemis,artemis-server-osgi,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-service-extensions",org.apache.artemis,"artemis-service-extensions","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-stomp-protocol,org.apache.artemis,artemis-stomp-protocol,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,"artemis-unit-test-support",org.apache.artemis,"artemis-unit-test-support","Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-web,org.apache.artemis,artemis-web,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" +org.apache.activemq,artemis-website,org.apache.artemis,artemis-website,"Artemis has moved to being an independent Apache project, switching groupId from org.apache.activemq to org.apache.artemis. See https://artemis.apache.org/artemis-tlp-groupid-migration" org.apache.axis,axis,org.apache.axis2,axis2, org.apache.commons,commons-io,commons-io,commons-io, org.apache.commons,commons-math,org.apache.commons,commons-math3,"Check https://issues.apache.org/jira/browse/MATH-444 for more details" @@ -574,7 +630,7 @@ postgresql,,org.postgresql,, saxon,saxon,net.sf.saxon,saxon, servletapi,servlet-api,javax.servlet,servlet-api, servletapi,servletapi,javax.servlet,servlet-api, -software.amazon.ion,"ion-java","com.amazon.ion","ion-java", +software.amazon.ion,ion-java,com.amazon.ion,ion-java,"From version 1.4.0 onward, Amazon Ion for Java moved to the new group id and Java package name ""com.amazon.ion"". The legacy ""software.amazon.ion"" group id is deprecated. See https://amazon-ion.github.io/ion-docs/news/2020/01/15/software.amazon.ion-deprecated.html" springframework,,org.springframework,, stax-utils,stax-utils,net.java.dev.stax-utils,stax-utils, tagsoup,tagsoup,org.ccil.cowan.tagsoup,tagsoup, From 6daad4b6474e8b89db6c5eeab44dcafd9272b8d9 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 24 Jun 2026 23:02:11 +0200 Subject: [PATCH 16/18] Update findRelocatedIonJava test for new relocation context (#186) The [Auto] Old GroupId migrations update (5253cf5) added a context note to the software.amazon.ion:ion-java relocation in migrations.csv. The RelocatedDependencyCheck recipe now surfaces that context both in the RelocatedDependencyReport data table row and in the inline search-result marker comment, so update the test expectations to match. Fixes the scheduled CI failure in run 28122079598. --- .../java/dependencies/RelocatedDependencyCheckTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/openrewrite/java/dependencies/RelocatedDependencyCheckTest.java b/src/test/java/org/openrewrite/java/dependencies/RelocatedDependencyCheckTest.java index 04b9e482..d37a8554 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RelocatedDependencyCheckTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RelocatedDependencyCheckTest.java @@ -154,7 +154,8 @@ void changeRelocatedMavenDependencies() { void findRelocatedIonJava() { rewriteRun( recipe -> recipe.dataTable(RelocatedDependencyReport.Row.class, rows -> assertThat(rows).containsExactly( - new RelocatedDependencyReport.Row("software.amazon.ion", "ion-java", "com.amazon.ion", "ion-java", null) + new RelocatedDependencyReport.Row("software.amazon.ion", "ion-java", "com.amazon.ion", "ion-java", + "From version 1.4.0 onward, Amazon Ion for Java moved to the new group id and Java package name \"com.amazon.ion\". The legacy \"software.amazon.ion\" group id is deprecated. See https://amazon-ion.github.io/ion-docs/news/2020/01/15/software.amazon.ion-deprecated.html") )), //language=xml pomXml( @@ -180,7 +181,7 @@ void findRelocatedIonJava() { rewrite-example 1.0-SNAPSHOT - + software.amazon.ion ion-java 1.5.1 From a113ca5ed39ea44e9f92384d6f110ed14d2be9f8 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sat, 11 Jul 2026 01:13:03 +0200 Subject: [PATCH 17/18] Keep direct dependencies whose scope or exclusions differ from the transitive one (#188) * Keep direct dependencies whose scope or exclusions differ from the transitive one RemoveRedundantDependencies removed a direct dependency whenever a matched parent provided the same coordinate transitively, ignoring scope and exclusions. This silently changed the effective classpath when the direct declaration carried information the transitive entry did not. A direct dependency is now only removed when the transitive one provides it at the exact same effective scope and with the same exclusions: - Track each transitive dependency's effective exclusions and only treat a direct dependency as redundant when its exclusions match exactly. - Key Maven transitives by the parent dependency's own effective scope and compare against the direct dependency's effective scope, rather than treating a broader transitive scope as covering a narrower direct one. Fixes the five scenarios from openrewrite/rewrite#8235. * Simplify test POMs: drop XML declaration and project attributes * Extract handleGradle/handleMaven and trim comments * Rename getCompatibleTransitives to getCompatibleGradleTransitives --- .../RemoveRedundantDependencies.java | 198 +++++++++--------- .../RemoveRedundantDependenciesTest.java | 184 ++++++++++++++++ 2 files changed, 284 insertions(+), 98 deletions(-) diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java index 2062339c..ef8f53ad 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java @@ -50,12 +50,20 @@ public class RemoveRedundantDependencies extends ScanningRecipe scope/configuration -> Set of transitive GAVs - Map>> transitivesByProjectAndScope; + // Map from project identifier -> scope/configuration -> Set of transitive dependencies + Map>> transitivesByProjectAndScope; + } + + @Value + public static class TransitiveDependency { + ResolvedGroupArtifactVersion gav; + Set exclusions; } @Override @@ -82,7 +90,7 @@ public TreeVisitor getScanner(Accumulator acc) { StringUtils.matchesGlob(dep.getGroupId(), groupId) && StringUtils.matchesGlob(dep.getArtifactId(), artifactId)) { // This is a matching parent dependency, resolve its transitives independently - Set transitives = acc.transitivesByProjectAndScope + Set transitives = acc.transitivesByProjectAndScope .computeIfAbsent(projectId, k -> new HashMap<>()) .computeIfAbsent(conf.getName(), k -> new HashSet<>()); resolveTransitivesFromPom( @@ -101,14 +109,18 @@ public TreeVisitor getScanner(Accumulator acc) { String projectId = maven.getPom().getGroupId() + ":" + maven.getPom().getArtifactId(); MavenPomDownloader downloader = new MavenPomDownloader(ctx); - for (Map.Entry> entry : maven.getDependencies().entrySet()) { - Scope depScope = entry.getKey(); - for (ResolvedDependency dep : entry.getValue()) { + // A direct dependency appears under every scope bucket it is visible in, so process + // each matching parent once, keyed by its own effective (declared) scope. + Set processed = new HashSet<>(); + for (List deps : maven.getDependencies().values()) { + for (ResolvedDependency dep : deps) { if (dep.isDirect() && StringUtils.matchesGlob(dep.getGroupId(), groupId) && - StringUtils.matchesGlob(dep.getArtifactId(), artifactId)) { + StringUtils.matchesGlob(dep.getArtifactId(), artifactId) && + processed.add(dep.getGroupId() + ":" + dep.getArtifactId())) { // This is a matching parent dependency, resolve its transitives independently - Set transitives = acc.transitivesByProjectAndScope + Scope depScope = Scope.fromName(dep.getRequested().getScope()); + Set transitives = acc.transitivesByProjectAndScope .computeIfAbsent(projectId, k -> new HashMap<>()) .computeIfAbsent(depScope.name().toLowerCase(), k -> new HashSet<>()); resolveTransitivesFromPom( @@ -132,7 +144,7 @@ private void resolveTransitivesFromPom( List repositories, MavenPomDownloader downloader, ExecutionContext ctx, - Set transitives) { + Set transitives) { try { // Ensure we have Maven Central in the repositories List effectiveRepos = new ArrayList<>(repositories); @@ -148,8 +160,9 @@ private void resolveTransitivesFromPom( 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); + collectAllDependencies(dep, transitives, visited); } } catch (MavenDownloadingException | MavenDownloadingExceptions e) { // If we can't download/resolve the POM, fall back to not detecting redundancies @@ -166,10 +179,12 @@ private ResolvedPom applyExclusions(ResolvedPom resolvedPom, List return patchedPom; } - private void collectAllDependencies(ResolvedDependency dep, Set transitives) { - if (transitives.add(dep.getGav())) { + private void collectAllDependencies(ResolvedDependency dep, Set transitives, + Set visited) { + if (visited.add(dep.getGav())) { + transitives.add(new TransitiveDependency(dep.getGav(), new HashSet<>(dep.getEffectiveExclusions()))); for (ResolvedDependency transitive : dep.getDependencies()) { - collectAllDependencies(transitive, transitives); + collectAllDependencies(transitive, transitives, visited); } } } @@ -185,70 +200,76 @@ public TreeVisitor getVisitor(Accumulator acc) { return tree; } - SourceFile sf = (SourceFile) tree; - Tree result = sf; - - // Handle Gradle - Optional gradleOpt = sf.getMarkers().findFirst(GradleProject.class); + Optional gradleOpt = tree.getMarkers().findFirst(GradleProject.class); if (gradleOpt.isPresent()) { GradleProject gradle = gradleOpt.get(); - String projectId = gradle.getGroup() + ":" + gradle.getName(); - Map> scopeToTransitives = - acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); - - for (GradleDependencyConfiguration conf : gradle.getConfigurations()) { - Set transitives = getCompatibleTransitives( - scopeToTransitives, conf.getName(), true); - if (transitives.isEmpty()) { - continue; - } - - for (ResolvedDependency dep : conf.getResolved()) { - if (dep.isDirect() && - doesNotMatchArguments(dep) && - isInTransitives(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( - dep.getGroupId(), dep.getArtifactId(), null, null, null) - .getVisitor().visit(result, ctx); - } - } - } - return result; + return handleGradle(ctx, gradle, tree); } - // Handle Maven - Optional mavenOpt = sf.getMarkers().findFirst(MavenResolutionResult.class); + Optional mavenOpt = tree.getMarkers().findFirst(MavenResolutionResult.class); if (mavenOpt.isPresent()) { MavenResolutionResult maven = mavenOpt.get(); - String projectId = maven.getPom().getGroupId() + ":" + maven.getPom().getArtifactId(); - Map> scopeToTransitives = - acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); - - for (Map.Entry> entry : maven.getDependencies().entrySet()) { - String scope = entry.getKey().name().toLowerCase(); - Set transitives = getCompatibleTransitives( - scopeToTransitives, scope, false); - if (transitives.isEmpty()) { - continue; + return handleMaven(ctx, maven, tree); + } + + return tree; + } + + private @Nullable Tree handleGradle(ExecutionContext ctx, GradleProject gradle, Tree result) { + String projectId = gradle.getGroup() + ":" + gradle.getName(); + Map> scopeToTransitives = + acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); + + for (GradleDependencyConfiguration conf : gradle.getConfigurations()) { + Set transitives = getCompatibleGradleTransitives( + scopeToTransitives, conf.getName()); + if (transitives.isEmpty()) { + continue; + } + + for (ResolvedDependency dep : conf.getResolved()) { + if (dep.isDirect() && + doesNotMatchArguments(dep) && + 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( + dep.getGroupId(), dep.getArtifactId(), null, null, null) + .getVisitor().visit(result, ctx); } + } + } + return result; + } - for (ResolvedDependency dep : entry.getValue()) { - if (dep.isDirect() && - doesNotMatchArguments(dep) && - isInTransitives(dep, transitives)) { - // This direct dependency is transitively provided, remove it + private @Nullable Tree handleMaven(ExecutionContext ctx, MavenResolutionResult maven, Tree result) { + String projectId = maven.getPom().getGroupId() + ":" + maven.getPom().getArtifactId(); + Map> scopeToTransitives = + acc.transitivesByProjectAndScope.getOrDefault(projectId, emptyMap()); + + // 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 + // mark a narrower direct declaration as redundant. + Set processed = new HashSet<>(); + for (List deps : maven.getDependencies().values()) { + for (ResolvedDependency dep : deps) { + if (dep.isDirect() && + doesNotMatchArguments(dep) && + processed.add(dep.getGroupId() + ":" + dep.getArtifactId())) { + Scope depScope = Scope.fromName(dep.getRequested().getScope()); + Set transitives = scopeToTransitives.getOrDefault( + depScope.name().toLowerCase(), emptySet()); + if (isRedundant(dep, transitives)) { + // This direct dependency is transitively provided at the same scope and + // with the same exclusions, remove it. result = new RemoveDependency( - dep.getGroupId(), dep.getArtifactId(), null, null, scope) + dep.getGroupId(), dep.getArtifactId(), null, null, depScope.name().toLowerCase()) .getVisitor().visit(result, ctx); } } } - return result; } - - return tree; + return result; } private boolean doesNotMatchArguments(ResolvedDependency dep) { @@ -256,41 +277,35 @@ private boolean doesNotMatchArguments(ResolvedDependency dep) { !StringUtils.matchesGlob(dep.getArtifactId(), artifactId); } - private boolean isInTransitives(ResolvedDependency dep, Set transitives) { - // Check if this dependency's GAV matches any transitive - // We match on groupId:artifactId:version exactly - for (ResolvedGroupArtifactVersion transitive : transitives) { - if (dep.getGroupId().equals(transitive.getGroupId()) && - dep.getArtifactId().equals(transitive.getArtifactId()) && - dep.getVersion().equals(transitive.getVersion())) { + private boolean isRedundant(ResolvedDependency dep, Set transitives) { + Set depExclusions = new HashSet<>(dep.getEffectiveExclusions()); + 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; } } return false; } - /** - * Get transitives from this scope/configuration and any broader ones. - */ - private Set getCompatibleTransitives( - Map> scopeToTransitives, - String targetScope, - boolean isGradle) { + private Set getCompatibleGradleTransitives( + Map> scopeToTransitives, + String targetScope) { - Set result = new HashSet<>(); + Set result = new HashSet<>(); // Always include transitives from the same scope - Set sameScope = scopeToTransitives.get(targetScope); + Set sameScope = scopeToTransitives.get(targetScope); if (sameScope != null) { result.addAll(sameScope); } // Include transitives from broader scopes - List broaderScopes = isGradle ? - getBroaderGradleScopes(targetScope) : - getBroaderMavenScopes(targetScope); - for (String broader : broaderScopes) { - Set broaderTransitives = scopeToTransitives.get(broader); + for (String broader : getBroaderGradleScopes(targetScope)) { + Set broaderTransitives = scopeToTransitives.get(broader); if (broaderTransitives != null) { result.addAll(broaderTransitives); } @@ -313,19 +328,6 @@ private List getBroaderGradleScopes(String scope) { return emptyList(); } } - - private List getBroaderMavenScopes(String scope) { - switch (scope.toLowerCase()) { - case "runtime": - return singletonList("compile"); - case "provided": - return Arrays.asList("compile", "runtime"); - case "test": - return Arrays.asList("compile", "runtime", "provided"); - default: - return emptyList(); - } - } }; } } diff --git a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java index ef8d0014..f163f7da 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java @@ -311,6 +311,190 @@ void noMatchingParentDependency() { ); } + @Test + void keepsTomcatEmbedCoreWhenExclusionsDiffer() { + 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-embed-programmatic + + + + + + """ + ) + ); + } + + @Test + void keepsTomcatEmbedCoreWhenDirectHasNoExclusions() { + 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 + + + + """ + ) + ); + } + + @Test + void keepsDirectCompileJunitJupiterWhenTransitiveIsTestScoped() { + 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.junit.jupiter + junit-jupiter + + + org.springframework.boot + spring-boot-starter-test + test + + + + """ + ) + ); + } + + @Test + void keepsRuntimeTomcatEmbedCoreWhenTransitiveIsCompileScoped() { + 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 + runtime + + + + """ + ) + ); + } + + @Test + void keepsDirectCompileTomcatEmbedCoreWhenProviderIsProvidedScoped() { + 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 + provided + + + org.apache.tomcat.embed + tomcat-embed-core + + + + """ + ) + ); + } + @Test void removeRedundantGradleDependency() { rewriteRun( From 5dd03f6ca69b9a0bd4e68787239d074911b685f7 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Tue, 14 Jul 2026 12:48:36 -0700 Subject: [PATCH 18/18] Polish --- .../DependencyResolutionDiagnosticTest.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java b/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java index 9202662c..3367eb62 100644 --- a/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/DependencyResolutionDiagnosticTest.java @@ -185,15 +185,9 @@ void maven() { // nor the test's `nonexistent.moderne.io` repo appear in the diagnostic. Force empty settings // here so the recipe sees the pom-declared repositories directly. MavenExecutionContextView ctx = MavenExecutionContextView.view(new InMemoryExecutionContext()); - MavenSettings emptySettings = MavenSettings.parse(new Parser.Input(Path.of("settings.xml"), () -> new ByteArrayInputStream( - //language=xml - """ - - """.getBytes())), ctx); - ctx.setMavenSettings(emptySettings); - spec.beforeRecipe(withToolingApi()) + ctx.setMavenSettings(new MavenSettings(null, null, null, null, null, null)); + spec + .executionContext(ctx) .dataTable(RepositoryAccessibilityReport.Row.class, rows -> { assertThat(rows).contains( new RepositoryAccessibilityReport.Row("https://repo.maven.apache.org/maven2", "", "", 200, "", "")); @@ -201,8 +195,7 @@ void maven() { assertThat(rows).contains( new RepositoryAccessibilityReport.Row("https://nonexistent.moderne.io/maven2", "java.net.UnknownHostException", "nonexistent.moderne.io", null, "", "") ); - }) - .executionContext(ctx); + }); }, //language=xml pomXml(