From af69a3083360efc78d7152285581663fdd1dc147 Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Wed, 8 Apr 2026 15:08:22 -0400 Subject: [PATCH 1/5] ChangeDependency skips dependencies whose new coordinates don't resolve When using glob patterns like `hibernate-*` to change a dependency's groupId, artifacts that don't exist under the new groupId (e.g. hibernate-validator under org.hibernate.orm) were incorrectly modified. Three fixes: - Scanner: use putIfAbsent for exceptions so a failed resolution from one artifact doesn't overwrite a successful resolution from another sharing the same version variable - Edit (!canUpdateVariable path): return the original tree instead of adding a warning marker when the new coordinates can't be resolved - Edit (canUpdateVariable path): verify the new coordinates resolve before applying groupId/artifactId changes --- .../openrewrite/gradle/ChangeDependency.java | 23 ++++++++++- .../gradle/ChangeDependencyTest.java | 39 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index 5810f8a2bab..12c5d5b0ae1 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -232,7 +232,9 @@ private void resolveAndRecordVersion(String varName, J.MethodInvocation m, Gradl acc.versionVariableUpdates.put(varName, resolvedVersion); } } catch (MavenDownloadingException e) { - acc.versionVariableUpdates.put(varName, e); + // Don't overwrite a successful resolution with a failure from a different artifact + // sharing the same version variable (e.g. hibernate-core resolved but hibernate-validator didn't) + acc.versionVariableUpdates.putIfAbsent(varName, e); } } }; @@ -360,7 +362,8 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, GradleDependen if (varName != null && !canUpdateVariable(varName)) { Object scanResult = acc.versionVariableUpdates.get(varName); if (scanResult instanceof Exception) { - return ((MavenDownloadingException) scanResult).warn(m); + // New coordinates can't be resolved — leave this dependency unchanged + return m; } if (scanResult instanceof String) { updated = updated.withDeclaredVersion((String) scanResult); @@ -382,6 +385,22 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, GradleDependen updated = updated.withDeclaredVersion(resolvedVersion); } } + } else if (!StringUtils.isBlank(newVersion)) { + // varName != null && canUpdateVariable — verify the new coordinates actually resolve + // before applying groupId/artifactId changes (glob patterns may match artifacts + // that don't exist under the new coordinates) + try { + String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) + .select(new GroupArtifact( + !StringUtils.isBlank(newGroupId) ? newGroupId : dep.getGroupId(), + !StringUtils.isBlank(newArtifactId) ? newArtifactId : dep.getArtifactId()), + dep.getConfigurationName(), newVersion, versionPattern, ctx); + if (resolvedVersion == null) { + return m; + } + } catch (MavenDownloadingException e) { + return m; + } } return updated.getTree(); diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java index 510d0f723ba..d0a0e1954c9 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java @@ -1104,4 +1104,43 @@ void isNotAcceptableForPlainTextWithGradleProjectMarker() { .build()))); assertThat(visitor.isAcceptable(sourceFile, new InMemoryExecutionContext())).isFalse(); } + + @Test + void doesNotChangeGroupIdWhenNewCoordinatesDontResolve() { + rewriteRun( + spec -> spec.recipe(new ChangeDependency("org.hibernate", "hibernate-*", "org.hibernate.orm", null, "6.0.x", null, null, true)), + buildGradle( + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + def hibernateVersion = '5.6.15.Final' + dependencies { + implementation "org.hibernate:hibernate-core:${hibernateVersion}" + implementation "org.hibernate:hibernate-validator:${hibernateVersion}" + } + """, + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + def hibernateVersion = '6.0.2.Final' + dependencies { + implementation "org.hibernate.orm:hibernate-core:${hibernateVersion}" + implementation "org.hibernate:hibernate-validator:${hibernateVersion}" + } + """ + ) + ); + } } From c57843ecbbb59f689eec32ea054dfdc4d79d229b Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Wed, 8 Apr 2026 15:19:43 -0400 Subject: [PATCH 2/5] Apply same fix to Maven ChangeDependency variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ChangeDependencyGroupIdAndArtifactId already reverted the groupId change on resolution failure (returning `tag` not `t`), but still added a warning marker via `e.warn(tag)`. Changed to return `tag` unchanged. ChangeManagedDependencyGroupIdAndArtifactId was worse — it returned `e.warn(t)` which kept the modified groupId/artifactId AND added a warning. Changed to return the original `tag`. --- .../maven/ChangeDependencyGroupIdAndArtifactId.java | 3 ++- .../maven/ChangeManagedDependencyGroupIdAndArtifactId.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java index 41b663cbfc1..de4fb7b2499 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java @@ -391,7 +391,8 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { deferUpdate = true; } } catch (MavenDownloadingException e) { - return e.warn(tag); + // New coordinates can't be resolved — leave this dependency unchanged + return tag; } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java index ea1c43618c9..c1cf15fa254 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java @@ -177,7 +177,8 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { } } } catch (MavenDownloadingException e) { - return e.warn(t); + // New coordinates can't be resolved — leave this dependency unchanged + return tag; } } if (t != tag) { From 27f89f0be5bef78e3700f8fcb4b84b2339bd7ab6 Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Wed, 8 Apr 2026 15:40:15 -0400 Subject: [PATCH 3/5] Track per-artifact resolution failures to protect shared version variables When a glob pattern matches artifacts that don't exist under the new coordinates (e.g. hibernate-validator under org.hibernate.orm), the shared version variable should not be updated. Instead, deps that resolve successfully get their version hardcoded, and deps that fail are left completely unchanged. Previously, canUpdateVariable only checked whether all deps sharing a variable matched the pattern. Now it also checks whether any of those deps failed version resolution in the scanner. This routes failed deps through the !canUpdateVariable path where they are skipped, while successful deps get hardcoded versions and detach from the variable. --- .../openrewrite/gradle/ChangeDependency.java | 26 +++++++------------ .../gradle/ChangeDependencyTest.java | 4 +-- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index 12c5d5b0ae1..a029e60a8c8 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -165,6 +165,7 @@ public Validated validate() { public static class Accumulator { Map versionVariableUpdates = new HashMap<>(); Map> versionVariableUsages = new HashMap<>(); + Set failedResolutions = new HashSet<>(); } @Override @@ -232,6 +233,7 @@ private void resolveAndRecordVersion(String varName, J.MethodInvocation m, Gradl acc.versionVariableUpdates.put(varName, resolvedVersion); } } catch (MavenDownloadingException e) { + acc.failedResolutions.add(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); // Don't overwrite a successful resolution with a failure from a different artifact // sharing the same version variable (e.g. hibernate-core resolved but hibernate-validator didn't) acc.versionVariableUpdates.putIfAbsent(varName, e); @@ -349,6 +351,10 @@ private boolean canUpdateVariable(String varName) { } private J.MethodInvocation updateDependency(J.MethodInvocation m, GradleDependency dep, ExecutionContext ctx) { + if (acc.failedResolutions.contains(new GroupArtifact(dep.getGroupId(), dep.getArtifactId()))) { + return m; + } + GradleDependency updated = dep; if (!StringUtils.isBlank(newGroupId)) { @@ -362,7 +368,6 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, GradleDependen if (varName != null && !canUpdateVariable(varName)) { Object scanResult = acc.versionVariableUpdates.get(varName); if (scanResult instanceof Exception) { - // New coordinates can't be resolved — leave this dependency unchanged return m; } if (scanResult instanceof String) { @@ -385,22 +390,6 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, GradleDependen updated = updated.withDeclaredVersion(resolvedVersion); } } - } else if (!StringUtils.isBlank(newVersion)) { - // varName != null && canUpdateVariable — verify the new coordinates actually resolve - // before applying groupId/artifactId changes (glob patterns may match artifacts - // that don't exist under the new coordinates) - try { - String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(new GroupArtifact( - !StringUtils.isBlank(newGroupId) ? newGroupId : dep.getGroupId(), - !StringUtils.isBlank(newArtifactId) ? newArtifactId : dep.getArtifactId()), - dep.getConfigurationName(), newVersion, versionPattern, ctx); - if (resolvedVersion == null) { - return m; - } - } catch (MavenDownloadingException e) { - return m; - } } return updated.getTree(); @@ -533,6 +522,9 @@ private boolean canUpdateVariable(String varName, DependencyMatcher depMatcher, if (!depMatcher.matches(ga.getGroupId(), ga.getArtifactId())) { return false; } + if (acc.failedResolutions.contains(ga)) { + return false; + } } return true; } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java index d0a0e1954c9..57b525288ee 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java @@ -1134,9 +1134,9 @@ void doesNotChangeGroupIdWhenNewCoordinatesDontResolve() { mavenCentral() } - def hibernateVersion = '6.0.2.Final' + def hibernateVersion = '5.6.15.Final' dependencies { - implementation "org.hibernate.orm:hibernate-core:${hibernateVersion}" + implementation "org.hibernate.orm:hibernate-core:6.0.2.Final" implementation "org.hibernate:hibernate-validator:${hibernateVersion}" } """ From 6ddc62db0e948a7a7897963acd5c85ca1caacc84 Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Wed, 8 Apr 2026 17:32:06 -0400 Subject: [PATCH 4/5] Only suppress resolution warnings when glob patterns are in use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a specific (non-glob) artifact fails to resolve under the new coordinates, that is a real problem the user should see — e.g. missing repository credentials or misconfiguration. Only suppress warnings and skip silently when the pattern contains wildcards, where a resolution failure is the expected signal that the glob matched an artifact that doesn't exist under the new group. --- .../openrewrite/gradle/ChangeDependency.java | 19 ++++++++++++++----- .../ChangeDependencyGroupIdAndArtifactId.java | 8 ++++++-- ...ManagedDependencyGroupIdAndArtifactId.java | 8 ++++++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index a029e60a8c8..753140090cb 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -162,6 +162,11 @@ public Validated validate() { )); } + private boolean isGlobPattern() { + return oldGroupId.contains("*") || oldGroupId.contains("?") || + oldArtifactId.contains("*") || oldArtifactId.contains("?"); + } + public static class Accumulator { Map versionVariableUpdates = new HashMap<>(); Map> versionVariableUsages = new HashMap<>(); @@ -233,10 +238,14 @@ private void resolveAndRecordVersion(String varName, J.MethodInvocation m, Gradl acc.versionVariableUpdates.put(varName, resolvedVersion); } } catch (MavenDownloadingException e) { - acc.failedResolutions.add(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); - // Don't overwrite a successful resolution with a failure from a different artifact - // sharing the same version variable (e.g. hibernate-core resolved but hibernate-validator didn't) - acc.versionVariableUpdates.putIfAbsent(varName, e); + if (isGlobPattern()) { + acc.failedResolutions.add(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); + // Don't overwrite a successful resolution with a failure from a different artifact + // sharing the same version variable (e.g. hibernate-core resolved but hibernate-validator didn't) + acc.versionVariableUpdates.putIfAbsent(varName, e); + } else { + acc.versionVariableUpdates.put(varName, e); + } } } }; @@ -368,7 +377,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, GradleDependen if (varName != null && !canUpdateVariable(varName)) { Object scanResult = acc.versionVariableUpdates.get(varName); if (scanResult instanceof Exception) { - return m; + return ((MavenDownloadingException) scanResult).warn(m); } if (scanResult instanceof String) { updated = updated.withDeclaredVersion((String) scanResult); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java index de4fb7b2499..3538b83e633 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java @@ -391,8 +391,12 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { deferUpdate = true; } } catch (MavenDownloadingException e) { - // New coordinates can't be resolved — leave this dependency unchanged - return tag; + if (oldGroupId.contains("*") || oldGroupId.contains("?") || + oldArtifactId.contains("*") || oldArtifactId.contains("?")) { + // Glob matched an artifact that doesn't exist under the new coordinates + return tag; + } + return e.warn(tag); } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java index c1cf15fa254..e2efe7e425f 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java @@ -177,8 +177,12 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { } } } catch (MavenDownloadingException e) { - // New coordinates can't be resolved — leave this dependency unchanged - return tag; + if (oldGroupId.contains("*") || oldGroupId.contains("?") || + oldArtifactId.contains("*") || oldArtifactId.contains("?")) { + // Glob matched an artifact that doesn't exist under the new coordinates + return tag; + } + return e.warn(t); } } if (t != tag) { From 4fdb80055e3e054d275a547bdcbbe8206aea7aaa Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Wed, 8 Apr 2026 17:40:31 -0400 Subject: [PATCH 5/5] Tighten instanceof checks and extract isGlobPattern helper consistently - Change instanceof Exception to instanceof MavenDownloadingException to match the actual cast, since the map only stores String or MavenDownloadingException values - Extract isGlobPattern() helper in all three classes instead of inlining the check only in the Maven variants --- .../java/org/openrewrite/gradle/ChangeDependency.java | 10 +++++----- .../maven/ChangeDependencyGroupIdAndArtifactId.java | 9 ++++++--- .../ChangeManagedDependencyGroupIdAndArtifactId.java | 9 ++++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index 753140090cb..9f5c752d643 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -342,8 +342,8 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations return v; } Object scanResult = acc.versionVariableUpdates.get(v.getSimpleName()); - if (scanResult instanceof Exception) { - return Markup.warn(v, (Exception) scanResult); + if (scanResult instanceof MavenDownloadingException) { + return Markup.warn(v, (MavenDownloadingException) scanResult); } if (scanResult instanceof String && v.getInitializer() instanceof J.Literal) { String resolvedVersion = (String) scanResult; @@ -376,7 +376,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, GradleDependen String varName = dep.getVersionVariable(); if (varName != null && !canUpdateVariable(varName)) { Object scanResult = acc.versionVariableUpdates.get(varName); - if (scanResult instanceof Exception) { + if (scanResult instanceof MavenDownloadingException) { return ((MavenDownloadingException) scanResult).warn(m); } if (scanResult instanceof String) { @@ -502,8 +502,8 @@ public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { return entry; } Object scanResult = acc.versionVariableUpdates.get(entry.getKey()); - if (scanResult instanceof Exception) { - return Markup.warn(entry, (Exception) scanResult); + if (scanResult instanceof MavenDownloadingException) { + return Markup.warn(entry, (MavenDownloadingException) scanResult); } if (scanResult instanceof String) { String resolvedVersion = (String) scanResult; diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java index 3538b83e633..0d036878a7e 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java @@ -114,6 +114,11 @@ public ChangeDependencyGroupIdAndArtifactId(String oldGroupId, String oldArtifac this.changeManagedDependency = changeManagedDependency; } + private boolean isGlobPattern() { + return oldGroupId.contains("*") || oldGroupId.contains("?") || + oldArtifactId.contains("*") || oldArtifactId.contains("?"); + } + String displayName = "Change Maven dependency"; @Override @@ -391,9 +396,7 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { deferUpdate = true; } } catch (MavenDownloadingException e) { - if (oldGroupId.contains("*") || oldGroupId.contains("?") || - oldArtifactId.contains("*") || oldArtifactId.contains("?")) { - // Glob matched an artifact that doesn't exist under the new coordinates + if (isGlobPattern()) { return tag; } return e.warn(tag); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java index e2efe7e425f..70e17e7a2c1 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeManagedDependencyGroupIdAndArtifactId.java @@ -101,6 +101,11 @@ public ChangeManagedDependencyGroupIdAndArtifactId(String oldGroupId, String old this.versionPattern = versionPattern; } + private boolean isGlobPattern() { + return oldGroupId.contains("*") || oldGroupId.contains("?") || + oldArtifactId.contains("*") || oldArtifactId.contains("?"); + } + @Override public Validated validate() { Validated validated = super.validate(); @@ -177,9 +182,7 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { } } } catch (MavenDownloadingException e) { - if (oldGroupId.contains("*") || oldGroupId.contains("?") || - oldArtifactId.contains("*") || oldArtifactId.contains("?")) { - // Glob matched an artifact that doesn't exist under the new coordinates + if (isGlobPattern()) { return tag; } return e.warn(t);