From 1ffbce9cfca8140714ea107b1e11f9b608aa7531 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Fri, 4 Sep 2026 13:43:06 -0400 Subject: [PATCH 1/4] Cover compiler levels inherited from a parent pom in UpgradeJavaVersion tests A module whose compiler level lives in the parent's pluginManagement, either literally or through a custom property, must be raised with the parent. --- .../java/migrate/UpgradeJavaVersionTest.java | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) diff --git a/src/test/java/org/openrewrite/java/migrate/UpgradeJavaVersionTest.java b/src/test/java/org/openrewrite/java/migrate/UpgradeJavaVersionTest.java index 3ff033069a..284b321195 100644 --- a/src/test/java/org/openrewrite/java/migrate/UpgradeJavaVersionTest.java +++ b/src/test/java/org/openrewrite/java/migrate/UpgradeJavaVersionTest.java @@ -26,8 +26,10 @@ import java.util.List; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.gradle.Assertions.buildGradle; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.Assertions.mavenProject; import static org.openrewrite.java.Assertions.version; import static org.openrewrite.maven.Assertions.pomXml; @@ -128,6 +130,242 @@ void mavenUpgradeFromJava8ToJava17ViaConfiguration() { ) ); } + + @Test + void mavenReleaseThroughCustomPropertyInParentPluginManagement() { + rewriteRun( + spec -> spec.recipe(new UpgradeJavaVersion(25)), + mavenProject("parent", + pomXml( + //language=xml + """ + + 4.0.0 + com.mycompany.app + parent + 1 + pom + + 8 + + + child + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${project.build.release.version} + + + + + + + """, + //language=xml + """ + + 4.0.0 + com.mycompany.app + parent + 1 + pom + + 25 + + + child + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${project.build.release.version} + + + + + + + """ + ), + mavenProject("child", + pomXml( + //language=xml + """ + + 4.0.0 + + com.mycompany.app + parent + 1 + + child + + """ + ) + ) + ) + ); + } + + @Test + void mavenSourceTargetInParentPluginManagement() { + rewriteRun( + spec -> spec.recipe(new UpgradeJavaVersion(25)), + mavenProject("parent", + pomXml( + //language=xml + """ + + 4.0.0 + com.mycompany.app + parent + 1 + pom + + child + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.6 + 1.6 + + + + + + + """, + //language=xml + """ + + 4.0.0 + com.mycompany.app + parent + 1 + pom + + child + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 25 + + + + + + + """ + ), + mavenProject("child", + pomXml( + //language=xml + """ + + 4.0.0 + + com.mycompany.app + parent + 1 + + child + + """ + ) + ) + ) + ); + } + + @Test + void mavenCompilerPropertiesInParentWithTestSourceTarget() { + rewriteRun( + spec -> spec.recipe(new UpgradeJavaVersion(25)), + mavenProject("parent", + pomXml( + //language=xml + """ + + 4.0.0 + com.mycompany.app + parent + 1 + pom + + 1.8 + 1.8 + 1.8 + 1.8 + + + child + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.testSource} + ${maven.compiler.testTarget} + + + + + + + """, + spec -> spec.after(actual -> assertThat(actual) + .doesNotContain("1.8") + .contains("25") + .actual()) + ), + mavenProject("child", + pomXml( + //language=xml + """ + + 4.0.0 + + com.mycompany.app + parent + 1 + + child + + """ + ) + ) + ) + ); + } } @Nested From c3b86d309497888b5c2389687538a5581e5943e0 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Fri, 4 Sep 2026 13:46:22 -0400 Subject: [PATCH 2/4] Raise build plugins whose older lines cannot load on JDK 25 maven-enforcer-plugin 1.x, maven-assembly-plugin 2.x and maven-plugin-plugin 3.6 fail at startup on JDK 25, and a checkstyle pinned on maven-checkstyle-plugin must parse the pattern-matching switch the Java 21 migration writes. --- .../META-INF/rewrite/java-version-25.yml | 23 +++++++ .../java/migrate/UpgradeToJava25Test.java | 62 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/src/main/resources/META-INF/rewrite/java-version-25.yml b/src/main/resources/META-INF/rewrite/java-version-25.yml index 32cb941f6f..23f19e5342 100644 --- a/src/main/resources/META-INF/rewrite/java-version-25.yml +++ b/src/main/resources/META-INF/rewrite/java-version-25.yml @@ -217,6 +217,29 @@ recipeList: groupId: org.apache.maven.plugins artifactId: maven-failsafe-plugin newVersion: 3.5.x + # Plugins whose older lines fail to load on JDK 25 (plexus-utils reflection, ASM without class file version 69). + - org.openrewrite.maven.UpgradePluginVersion: + groupId: org.apache.maven.plugins + artifactId: maven-enforcer-plugin + newVersion: 3.x + - org.openrewrite.maven.UpgradePluginVersion: + groupId: org.apache.maven.plugins + artifactId: maven-assembly-plugin + newVersion: 3.x + - org.openrewrite.maven.UpgradePluginVersion: + groupId: org.apache.maven.plugins + artifactId: maven-plugin-plugin + newVersion: 3.15.x + addVersionIfMissing: true + - org.openrewrite.maven.UpgradePluginVersion: + groupId: org.apache.maven.plugins + artifactId: maven-checkstyle-plugin + newVersion: 3.6.x + # A checkstyle pinned on the plugin must be able to parse the pattern-matching `switch` the Java 21 migration writes. + - org.openrewrite.java.dependencies.UpgradeDependencyVersion: + groupId: com.puppycrawl.tools + artifactId: checkstyle + newVersion: 10.x - org.openrewrite.java.dependencies.UpgradeDependencyVersion: groupId: net.bytebuddy artifactId: byte-buddy* diff --git a/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java b/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java index 6b7fd95cd0..824e76659b 100644 --- a/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java +++ b/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java @@ -156,6 +156,68 @@ void upgradesGradleWrapperForJava25() { ); } + @Test + void upgradesBuildPluginsThatCannotRunOnJava25() { + rewriteRun( + spec -> spec.recipeFromResources("org.openrewrite.java.migrate.UpgradePluginsForJava25"), + mavenProject("project", + pomXml( + //language=xml + """ + + com.mycompany.app + my-app + 1 + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4 + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.6 + + + org.apache.maven.plugins + maven-plugin-plugin + 3.6.1 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + + com.puppycrawl.tools + checkstyle + 8.45 + + + + + + + """, + spec -> spec.after(actual -> + assertThat(actual) + .containsPattern("maven-enforcer-plugin\\s*3\\.") + .containsPattern("maven-assembly-plugin\\s*3\\.") + .containsPattern("maven-plugin-plugin\\s*3\\.15\\.") + .containsPattern("maven-checkstyle-plugin\\s*3\\.6\\.") + .containsPattern("checkstyle\\s*10\\.") + .actual()) + ) + ) + ); + } + @Test void kotlin1xCapsJavaVersionAt24WithComment() { // Kotlin 1.x is left untouched: crossing the K2 compiler default introduced in Kotlin 2.0 is source-breaking, From 5ae55c3c90cd585e489c11dd7c8abea82e273775 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Fri, 4 Sep 2026 13:43:07 -0400 Subject: [PATCH 3/4] Move project-level sourceCompatibility into the java block alongside the Gradle 9 wrapper Gradle 9 removed the JavaPluginConvention, so a build that assigned sourceCompatibility on the project stopped configuring once the wrapper moved. --- .../META-INF/rewrite/java-version-25.yml | 2 ++ .../java/migrate/UpgradeToJava25Test.java | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/main/resources/META-INF/rewrite/java-version-25.yml b/src/main/resources/META-INF/rewrite/java-version-25.yml index 23f19e5342..9afed9717f 100644 --- a/src/main/resources/META-INF/rewrite/java-version-25.yml +++ b/src/main/resources/META-INF/rewrite/java-version-25.yml @@ -204,6 +204,8 @@ recipeList: - org.openrewrite.gradle.UpdateGradleWrapper: version: 9.1.0 addIfMissing: false + # Gradle 9 removed the JavaPluginConvention, so the wrapper bump above turns a project-level `sourceCompatibility` into an error. + - org.openrewrite.gradle.gradle9.UseJavaExtensionBlock - org.openrewrite.maven.UpgradePluginVersion: groupId: org.apache.maven.plugins artifactId: maven-compiler-plugin diff --git a/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java b/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java index 824e76659b..d1885a59bd 100644 --- a/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java +++ b/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java @@ -23,6 +23,7 @@ import org.openrewrite.test.RewriteTest; import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.gradle.Assertions.buildGradle; import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; import static org.openrewrite.java.Assertions.mavenProject; import static org.openrewrite.maven.Assertions.pomXml; @@ -218,6 +219,30 @@ void upgradesBuildPluginsThatCannotRunOnJava25() { ); } + @Test + void movesProjectLevelCompatibilityIntoJavaBlockForGradle9() { + rewriteRun( + spec -> spec.beforeRecipe(withToolingApi()), + buildGradle( + //language=groovy + """ + plugins { id 'java' } + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + """, + //language=groovy + """ + plugins { id 'java' } + + java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 + } + """ + ) + ); + } + @Test void kotlin1xCapsJavaVersionAt24WithComment() { // Kotlin 1.x is left untouched: crossing the K2 compiler default introduced in Kotlin 2.0 is source-breaking, From 76ecbb453f82fce4f77596b6944a037556c5dcdc Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Fri, 4 Sep 2026 14:00:46 -0400 Subject: [PATCH 4/4] Enable Lombok's annotation processor through the reactor parent JDK 23 stopped running annotation processors found on the classpath, so AddAnnotationProcessor writes the lombok path into the parent's pluginManagement. A per-module precondition never marked that parent pom, so multi-module builds kept compiling without Lombok on JDK 25. --- .../search/ModuleOrParentHasDependency.java | 125 ++++++++++++++++++ .../META-INF/rewrite/java-version-25.yml | 3 +- .../java/migrate/UpgradeToJava25Test.java | 66 +++++++++ 3 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/openrewrite/java/migrate/search/ModuleOrParentHasDependency.java diff --git a/src/main/java/org/openrewrite/java/migrate/search/ModuleOrParentHasDependency.java b/src/main/java/org/openrewrite/java/migrate/search/ModuleOrParentHasDependency.java new file mode 100644 index 0000000000..14c18538ea --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/search/ModuleOrParentHasDependency.java @@ -0,0 +1,125 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.migrate.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.ScanningRecipe; +import org.openrewrite.SourceFile; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.gradle.marker.GradleDependencyConfiguration; +import org.openrewrite.gradle.marker.GradleProject; +import org.openrewrite.java.marker.JavaProject; +import org.openrewrite.marker.Markers; +import org.openrewrite.marker.SearchResult; +import org.openrewrite.maven.tree.MavenResolutionResult; + +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +@EqualsAndHashCode(callSuper = false) +@Value +public class ModuleOrParentHasDependency extends ScanningRecipe { + + @Option(displayName = "Group pattern", + description = "Group glob pattern used to match dependencies.", + example = "org.projectlombok") + String groupIdPattern; + + @Option(displayName = "Artifact pattern", + description = "Artifact glob pattern used to match dependencies.", + example = "lombok") + String artifactIdPattern; + + String displayName = "Module or its reactor parent has dependency"; + + String description = "Marks all files in modules that have a matching dependency, and the in-reactor parent poms " + + "those modules inherit from. Intended as a precondition for recipes that configure a module through its " + + "parent's `pluginManagement`, which a per-module precondition would otherwise keep from being visited."; + + public static class Accumulator { + Set modules = new HashSet<>(); + Set parentPoms = new HashSet<>(); + } + + @Override + public Accumulator getInitialValue(ExecutionContext ctx) { + return new Accumulator(); + } + + @Override + public TreeVisitor getScanner(Accumulator acc) { + return new TreeVisitor() { + @Override + public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof SourceFile && hasDependency(tree.getMarkers())) { + tree.getMarkers().findFirst(JavaProject.class).ifPresent(acc.modules::add); + tree.getMarkers().findFirst(MavenResolutionResult.class).ifPresent(mrr -> { + for (MavenResolutionResult p = mrr; p.parentPomIsProjectPom() && p.getParent() != null; p = p.getParent()) { + Path parentPath = p.getParent().getPom().getRequested().getSourcePath(); + if (parentPath == null) { + break; + } + acc.parentPoms.add(parentPath); + } + }); + } + return tree; + } + }; + } + + private boolean hasDependency(Markers markers) { + Optional mrr = markers.findFirst(MavenResolutionResult.class); + if (mrr.isPresent()) { + return !mrr.get().findDependencies(groupIdPattern, artifactIdPattern, null).isEmpty(); + } + Optional gp = markers.findFirst(GradleProject.class); + if (gp.isPresent()) { + for (GradleDependencyConfiguration configuration : gp.get().getConfigurations()) { + if (configuration.findRequestedDependency(groupIdPattern, artifactIdPattern) != null || + configuration.findResolvedDependency(groupIdPattern, artifactIdPattern) != null) { + return true; + } + } + } + return false; + } + + @Override + public TreeVisitor getVisitor(Accumulator acc) { + return new TreeVisitor() { + @Override + public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + assert tree != null; + Optional jp = tree.getMarkers().findFirst(JavaProject.class); + if (jp.isPresent() && acc.modules.contains(jp.get())) { + return SearchResult.found(tree, "Module has dependency"); + } + if (tree instanceof SourceFile && acc.parentPoms.contains(((SourceFile) tree).getSourcePath())) { + return SearchResult.found(tree, "Parent of a module with the dependency"); + } + return tree; + } + }; + } +} diff --git a/src/main/resources/META-INF/rewrite/java-version-25.yml b/src/main/resources/META-INF/rewrite/java-version-25.yml index 9afed9717f..fca3a2b635 100644 --- a/src/main/resources/META-INF/rewrite/java-version-25.yml +++ b/src/main/resources/META-INF/rewrite/java-version-25.yml @@ -174,7 +174,8 @@ description: >- With Java 23 the encapsulation of JDK internals made it necessary to configure annotation processors like Lombok explicitly. The change is valid for older versions as well. preconditions: - - org.openrewrite.java.dependencies.search.ModuleHasDependency: + # Marks the reactor parent too: `AddAnnotationProcessor` configures a module through its parent's `pluginManagement`. + - org.openrewrite.java.migrate.search.ModuleOrParentHasDependency: groupIdPattern: org.projectlombok artifactIdPattern: lombok recipeList: diff --git a/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java b/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java index d1885a59bd..e561c40383 100644 --- a/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java +++ b/src/test/java/org/openrewrite/java/migrate/UpgradeToJava25Test.java @@ -562,6 +562,72 @@ void kotlinNewerThan2_3UpgradesToJava25() { ); } + @Test + void addsLombokAnnotationProcessorToReactorParent() { + rewriteRun( + spec -> spec.recipeFromResources("org.openrewrite.java.migrate.EnableLombokAnnotationProcessor"), + mavenProject("parent", + pomXml( + //language=xml + """ + + 4.0.0 + com.mycompany.app + parent + 1 + pom + + child + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + 25 + + + + + + + """, + spec -> spec.after(actual -> + assertThat(actual) + .containsPattern("(.|\\n)*(.|\\n)*lombok") + .actual() + ) + ), + mavenProject("child", + pomXml( + //language=xml + """ + + 4.0.0 + + com.mycompany.app + parent + 1 + + child + + + org.projectlombok + lombok + 1.18.40 + + + + """ + ) + ) + ) + ); + } + @Test void addsLombokAnnotationProcessor() { rewriteRun(