diff --git a/src/main/java/org/openrewrite/java/migrate/UpgradeStandaloneSpringCoreForJava25.java b/src/main/java/org/openrewrite/java/migrate/UpgradeStandaloneSpringCoreForJava25.java new file mode 100644 index 0000000000..1ef5d4f835 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/UpgradeStandaloneSpringCoreForJava25.java @@ -0,0 +1,341 @@ +/* + * 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; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.ScanningRecipe; +import org.openrewrite.SourceFile; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Statement; +import org.openrewrite.java.tree.TypeTree; +import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.maven.MavenIsoVisitor; +import org.openrewrite.maven.tree.Dependency; +import org.openrewrite.maven.tree.ManagedDependency; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.Parent; +import org.openrewrite.maven.tree.Plugin; +import org.openrewrite.maven.tree.Profile; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.ResolvedManagedDependency; +import org.openrewrite.maven.tree.ResolvedPom; +import org.openrewrite.xml.ChangeTagValueVisitor; +import org.openrewrite.xml.tree.Xml; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** A deliberately bounded Core-only major upgrade, not a Spring Framework migration. */ +public class UpgradeStandaloneSpringCoreForJava25 extends ScanningRecipe { + private static final String GROUP = "org.springframework"; + private static final String TARGET = "6.2.11"; + private static final Set TYPES = new HashSet<>(Arrays.asList( + "org.springframework.core.io.Resource", "org.springframework.core.io.ResourceLoader", + "org.springframework.core.io.support.ResourcePatternResolver", + "org.springframework.core.io.support.PathMatchingResourcePatternResolver", + "org.springframework.core.type.AnnotationMetadata", "org.springframework.core.type.AnnotatedTypeMetadata", + "org.springframework.core.type.ClassMetadata", "org.springframework.core.type.classreading.MetadataReader", + "org.springframework.core.type.classreading.MetadataReaderFactory", + "org.springframework.core.type.classreading.SimpleMetadataReaderFactory", "org.springframework.util.ClassUtils")); + private static final Set MEMBERS = new HashSet<>(Arrays.asList( + "Resource#exists", "Resource#getFilename", "ResourceLoader#getResource", "ResourcePatternResolver#getResources", + "PathMatchingResourcePatternResolver#", "PathMatchingResourcePatternResolver#getResource", + "PathMatchingResourcePatternResolver#getResources", + "SimpleMetadataReaderFactory#", "SimpleMetadataReaderFactory#getMetadataReader", + "MetadataReaderFactory#getMetadataReader", "MetadataReader#getClassMetadata", "MetadataReader#getAnnotationMetadata", + "ClassMetadata#hasSuperClass", "ClassMetadata#getSuperClassName", "ClassMetadata#isConcrete", + "ClassMetadata#getInterfaceNames", "ClassMetadata#getClassName", + "AnnotationMetadata#hasAnnotation", "AnnotatedTypeMetadata#getAnnotationAttributes", + "AnnotationMetadata#getAnnotationAttributes", "ClassUtils#convertClassNameToResourcePath")); + + @Override public String getDisplayName() { return "Upgrade standalone Spring Core metadata readers for Java 25"; } + @Override public String getDescription() { + return "Explicitly upgrade standalone Spring Core 5.3 OSS dependencies to 6.2.11 only when the complete Maven graph contains " + + "no other Spring family and typed Java usage is limited to private implementation metadata/resource APIs. " + + "Refuse framework management, unknown graphs, mixed versions, other APIs and exposed Spring signatures. Pin local dependencies without changing shared properties."; + } + public static class State { + final Map modules = new HashMap<>(); + final Map sources = new HashMap<>(); + } + private static class Usage { + final boolean safe; + final boolean usesSpring; + Usage(boolean safe, boolean usesSpring) { this.safe = safe; this.usesSpring = usesSpring; } + } + private static class Module { + boolean safe; + boolean usesSpring; + Module(boolean safe) { this.safe = safe; } + } + @Override public State getInitialValue(ExecutionContext ctx) { return new State(); } + @Override public TreeVisitor getScanner(State state) { + return new TreeVisitor() { + @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof SourceFile) { + SourceFile source = (SourceFile) tree; + if ("pom.xml".equals(source.getSourcePath().getFileName().toString())) { + state.modules.put(directory(source.getSourcePath()), new Module(source.getMarkers() + .findFirst(MavenResolutionResult.class).map(m -> source instanceof Xml.Document && standalone(m) && localVersions((Xml.Document) source, m.getPom())).orElse(false))); + } else { + Module facts = new Module(true); + String path = source.getSourcePath().toString(); + if (source instanceof J.CompilationUnit) inspect((J.CompilationUnit) source, facts); + else if (path.endsWith(".java") || path.endsWith(".kt") || path.endsWith(".groovy")) facts.safe = false; + state.sources.put(source.getSourcePath(), new Usage(facts.safe, facts.usesSpring)); + } + } + return tree; + } + }; + } + @Override public TreeVisitor getVisitor(State state) { + for (Map.Entry source : state.sources.entrySet()) { + Module module = owner(source.getKey(), state.modules); + if (module == null) continue; + module.safe &= source.getValue().safe; + module.usesSpring |= source.getValue().usesSpring; + } + return new MavenIsoVisitor() { + @Override public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + Module module = state.modules.get(directory(document.getSourcePath())); + return module != null && module.safe && module.usesSpring ? super.visitDocument(document, ctx) : document; + } + @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + if (!isDependencyTag(GROUP, "spring-core") && !isDependencyTag(GROUP, "spring-jcl")) return t; + // No profiles or dependency management are eligible. Only the declaration-local version changes. + Optional version = t.getChild("version"); + if (!version.isPresent()) return t; + Xml.Tag updated = (Xml.Tag) new ChangeTagValueVisitor<>(version.get(), TARGET) + .visitNonNull(t, ctx, getCursor().getParentOrThrow()); + if (updated != t) maybeUpdateModel(); + return updated; + } + }; + } + private static Path directory(Path path) { return path.getParent() == null ? Paths.get("") : path.getParent().normalize(); } + private static @Nullable Module owner(Path source, Map modules) { + Path best = null; + for (Path root : modules.keySet()) { + if ((root.toString().isEmpty() || source.startsWith(root)) && (best == null || root.toString().length() > best.toString().length())) best = root; + } + return best == null ? null : modules.get(best); + } + private static boolean spring(@Nullable String group) { return group != null && (group.startsWith("org.springframework") || group.startsWith("io.spring")); } + private static boolean old(@Nullable String version) { + if (version == null || !version.matches("5\\.3\\.[0-9]+")) return false; + try { return Integer.parseInt(version.substring(4)) <= 39; } catch (NumberFormatException e) { return false; } + } + private static boolean localVersions(Xml.Document document, ResolvedPom pom) { + Optional dependencies = document.getRoot().getChild("dependencies"); + if (!dependencies.isPresent()) return false; + for (Xml.Tag dependency : dependencies.get().getChildren()) { + String group = pom.getValue(dependency.getChildValue("groupId").orElse(null)); + if (group == null || group.contains("${")) return false; + if (spring(group) && (!old(pom.getValue(dependency.getChildValue("version").orElse(null))) || + dependency.getChild("systemPath").isPresent())) return false; + } + return true; + } + private static boolean standalone(MavenResolutionResult m) { + ResolvedPom pom = m.getPom(); + if (m.getDependencies().isEmpty() || !m.getModules().isEmpty() || !pom.getSubprojects().isEmpty() || + frameworkPlugin(pom.getPlugins(), pom) || frameworkPlugin(pom.getPluginManagement(), pom)) return false; + for (MavenResolutionResult p = m; p != null; p = p.getParent()) { + Parent parent = p.getPom().getRequested().getParent(); + if (spring(p.getPom().getGroupId()) || parent != null && + !("org.codehaus.mojo".equals(p.getPom().getValue(parent.getGroupId())) && + "mojo-parent".equals(p.getPom().getValue(parent.getArtifactId())) && + "87".equals(p.getPom().getValue(parent.getVersion())))) return false; + } + for (ManagedDependency d : pom.getRequested().getDependencyManagement()) { + if (spring(pom.getValue(d.getGroupId())) || d instanceof ManagedDependency.Imported && + !("org.junit".equals(pom.getValue(d.getGroupId())) && "junit-bom".equals(pom.getValue(d.getArtifactId())) && + "5.11.4".equals(pom.getValue(d.getVersion())))) return false; + } + for (ResolvedManagedDependency d : pom.getDependencyManagement()) { + if (spring(d.getGroupId()) || d.getBomGav() != null && + !("org.junit".equals(d.getBomGav().getGroupId()) && "junit-bom".equals(d.getBomGav().getArtifactId()) && + "5.11.4".equals(d.getBomGav().getVersion()))) return false; + } + List graph = new ArrayList<>(); + for (List scope : m.getDependencies().values()) for (ResolvedDependency d : scope) flatten(d, graph); + for (Profile profile : pom.getRequested().getProfiles()) { + if (!profile.getDependencies().isEmpty() || !profile.getDependencyManagement().isEmpty() || + frameworkPlugin(profile.getPlugins(), pom) || frameworkPlugin(profile.getPluginManagement(), pom)) return false; + for (String key : profile.getProperties().keySet()) { + for (ResolvedDependency dependency : graph) { + Dependency requested = dependency.getRequested(); + if (references(requested.getGroupId(), key, pom, new HashSet<>()) || references(requested.getArtifactId(), key, pom, new HashSet<>()) || + references(requested.getVersion(), key, pom, new HashSet<>())) return false; + } + } + } + String coreVersion = null; + boolean jcl = false; + for (ResolvedDependency d : graph) { + if (d.getGroupId() == null || d.getArtifactId() == null || d.getVersion() == null || + d.getGroupId().contains("${") || d.getArtifactId().contains("${") || d.getVersion().contains("${")) return false; + if (!spring(d.getGroupId())) continue; + if ("spring-jcl".equals(d.getArtifactId())) jcl = true; + if (!GROUP.equals(d.getGroupId()) || !("spring-core".equals(d.getArtifactId()) || "spring-jcl".equals(d.getArtifactId())) || !old(d.getVersion()) || + !"jar".equals(d.getType()) || d.getClassifier() != null || + !d.getVersion().equals(pom.getValue(d.getRequested().getVersion()))) return false; + if ("spring-core".equals(d.getArtifactId()) && d.getDepth() == 0) coreVersion = d.getVersion(); + } + if (coreVersion == null || !jcl) return false; + for (ResolvedDependency d : graph) if (spring(d.getGroupId()) && !coreVersion.equals(d.getVersion())) return false; + boolean localCore = false; + for (Dependency d : pom.getRequested().getDependencies()) { + String group = pom.getValue(d.getGroupId()); + if (group == null || group.contains("${")) return false; + if (spring(group)) { + String artifact = pom.getValue(d.getArtifactId()); + if (!GROUP.equals(group) || !("spring-core".equals(artifact) || "spring-jcl".equals(artifact)) || + !coreVersion.equals(pom.getValue(d.getVersion()))) return false; + if ("spring-core".equals(artifact)) localCore = true; + } + } + return localCore; + } + private static boolean frameworkPlugin(List plugins, ResolvedPom pom) { + for (Plugin plugin : plugins) { + String group = plugin.getGroupId(); + if (group == null) continue; // Maven's default plugin group is org.apache.maven.plugins. + group = pom.getValue(group); + if (group == null || group.contains("${") || spring(group)) return true; + } + return false; + } + private static final Pattern PROPERTY = Pattern.compile("\\$\\{([^}]+)}"); + private static boolean references(@Nullable String value, String key, ResolvedPom pom, Set visited) { + if (value == null) return false; + Matcher matcher = PROPERTY.matcher(value); + while (matcher.find()) { + String property = matcher.group(1); + if (key.equals(property)) return true; + if (visited.add(property)) { + String raw = pom.getRequested().getProperties().get(property); + if (raw == null) raw = pom.getProperties().get(property); + if (references(raw, key, pom, visited)) return true; + } + } + return false; + } + private static void flatten(ResolvedDependency dependency, List graph) { + graph.add(dependency); + for (ResolvedDependency child : dependency.getDependencies()) flatten(child, graph); + } + private static boolean springType(@Nullable JavaType type) { + return springType(type, Collections.newSetFromMap(new IdentityHashMap())); + } + private static boolean springType(@Nullable JavaType type, Set visited) { + if (type == null || !visited.add(type)) return false; + if (type instanceof JavaType.Array) return springType(((JavaType.Array) type).getElemType(), visited); + JavaType.FullyQualified fq = TypeUtils.asFullyQualified(type); + if (fq != null && spring(fq.getFullyQualifiedName())) return true; + if (type instanceof JavaType.Parameterized) for (JavaType t : ((JavaType.Parameterized) type).getTypeParameters()) if (springType(t, visited)) return true; + if (type instanceof JavaType.GenericTypeVariable) for (JavaType t : ((JavaType.GenericTypeVariable) type).getBounds()) if (springType(t, visited)) return true; + return false; + } + private static boolean springBounds(@Nullable List parameters) { + if (parameters != null) for (J.TypeParameter parameter : parameters) { + if (parameter.getBounds() != null) for (TypeTree bound : parameter.getBounds()) if (springType(bound.getType())) return true; + } + return false; + } + private static void inspect(J.CompilationUnit cu, Module module) { + for (JavaType type : cu.getTypesInUse().getTypesInUse()) { + JavaType.FullyQualified fq = TypeUtils.asFullyQualified(type); + if (fq != null && spring(fq.getFullyQualifiedName())) { + module.usesSpring = true; + if (!TYPES.contains(fq.getFullyQualifiedName())) module.safe = false; + } + } + for (J.Import imp : cu.getImports()) { + String name = imp.getTypeName(); + if (spring(name) && (!TYPES.contains(name) || imp.isStatic())) module.safe = false; + } + for (JavaType.Method method : cu.getTypesInUse().getUsedMethods()) { + String type = method.getDeclaringType().getFullyQualifiedName(); + if (spring(type) && (!TYPES.contains(type) || !MEMBERS.contains(type.substring(type.lastIndexOf('.') + 1) + "#" + method.getName()))) module.safe = false; + } + new JavaIsoVisitor() { + @Override public J.Literal visitLiteral(J.Literal literal, Integer ctx) { + if (literal.getValue() instanceof String && ((String) literal.getValue()).contains("org.springframework")) module.safe = false; + return super.visitLiteral(literal, ctx); + } + @Override public J.FieldAccess visitFieldAccess(J.FieldAccess field, Integer ctx) { + JavaType.Variable variable = field.getName().getFieldType(); + JavaType.FullyQualified owner = variable == null ? null : TypeUtils.asFullyQualified(variable.getOwner()); + if (owner != null && spring(owner.getFullyQualifiedName()) && + !("org.springframework.core.io.support.ResourcePatternResolver".equals(owner.getFullyQualifiedName()) && + "CLASSPATH_ALL_URL_PREFIX".equals(field.getSimpleName()))) module.safe = false; + return super.visitFieldAccess(field, ctx); + } + @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration c, Integer ctx) { + if (springBounds(c.getTypeParameters())) module.safe = false; + if (c.getPrimaryConstructor() != null) for (Statement parameter : c.getPrimaryConstructor()) { + if (parameter instanceof J.VariableDeclarations && springType(((J.VariableDeclarations) parameter).getType())) module.safe = false; + } + if (c.getExtends() != null && springType(c.getExtends().getType())) module.safe = false; + if (c.getImplements() != null) for (TypeTree t : c.getImplements()) if (springType(t.getType())) module.safe = false; + return super.visitClassDeclaration(c, ctx); + } + @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Integer ctx) { + JavaType.Method mt = method.getMethodType(); + if (mt == null) { module.safe = false; } + else if (!method.getLeadingAnnotations().isEmpty()) { + if (springType(mt.getReturnType()) || springBounds(method.getTypeParameters())) module.safe = false; + for (JavaType t : mt.getParameterTypes()) if (springType(t)) module.safe = false; + } + if (mt != null && !method.hasModifier(J.Modifier.Type.Private)) { + if (springType(mt.getReturnType()) || springBounds(method.getTypeParameters())) module.safe = false; + for (JavaType t : mt.getParameterTypes()) if (springType(t)) module.safe = false; + for (JavaType t : mt.getThrownExceptions()) if (springType(t)) module.safe = false; + } + return super.visitMethodDeclaration(method, ctx); + } + @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations v, Integer ctx) { + if (springType(v.getType()) && getCursor().getParentTreeCursor().getValue() instanceof J.Block && + getCursor().getParentTreeCursor().getParentTreeCursor().getValue() instanceof J.ClassDeclaration) module.safe = false; + return super.visitVariableDeclarations(v, ctx); + } + @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integer ctx) { + if (method.getMethodType() == null && method.getSelect() != null && springType(method.getSelect().getType())) module.safe = false; + return super.visitMethodInvocation(method, ctx); + } + }.visit(cu, 0); + } +} 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 af20ada3e1..893781daa7 100644 --- a/src/main/resources/META-INF/rewrite/java-version-25.yml +++ b/src/main/resources/META-INF/rewrite/java-version-25.yml @@ -243,6 +243,7 @@ recipeList: newVersion: 7.4.0 - org.openrewrite.github.SetupJavaAdoptOpenJDKToTemurin - org.openrewrite.java.migrate.UpgradeSpringBootParentForJava25 + - org.openrewrite.java.migrate.UpgradeStandaloneSpringCoreForJava25 - org.openrewrite.java.migrate.UpgradeLombokMavenPluginForJava25 - org.openrewrite.github.SetupJavaUpgradeJavaVersion: minimumJavaMajorVersion: 25 diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index b7fc9fd44b..2349cbae63 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -108,12 +108,12 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.U maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradePluginsForJava11,Upgrade plugins to Java 11 compatible versions,Updates plugins to versions compatible with Java 11.,6,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradePluginsForJava17,Upgrade plugins to Java 17 compatible versions,Updates plugins to versions compatible with Java 17.,10,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradePluginsForJava21,Upgrade plugins to Java 21 compatible versions,Updates plugins and dependencies to versions compatible with Java 21.,6,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradePluginsForJava25,Upgrade plugins to Java 25 compatible versions,Updates plugins and dependencies to versions compatible with Java 25.,29,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradePluginsForJava25,Upgrade plugins to Java 25 compatible versions,Updates plugins and dependencies to versions compatible with Java 25.,30,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradeSpringBootParentForJava25,Upgrade Spring Boot 3 Maven parents for Java 25,"Upgrade Spring Boot 3 Maven parents to the latest 3.5 release so that Spring's bundled ASM can read Java 25 class files. Stay within Boot 3: crossing from Boot 2 requires a separate Jakarta migration, and newer Boot major versions must not be downgraded.",2,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeGmavenGroovyForJava25,Upgrade the GMaven Groovy 3 compiler for Java 25,Raise explicit Groovy 3.0 compiler dependencies in GMaven plugins to at least 3.0.25. Preserve other release lines and newer patches; override inherited properties on the plugin dependency without changing the parent.,1,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradeToJava17,Migrate to Java 17,"This recipe will apply changes commonly needed when migrating to Java 17. Specifically, for those applications that are built on Java 8, this recipe will update and add dependencies on J2EE libraries that are no longer directly bundled with the JDK. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 17 as the target/source and plugins will also be upgraded to versions that are compatible with Java 17.",399,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradeToJava21,Migrate to Java 21,This recipe will apply changes commonly needed when migrating to Java 21. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 21 as the target/source and plugins will also be upgraded to versions that are compatible with Java 21.,445,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradeToJava25,Migrate to Java 25,This recipe will apply changes commonly needed when migrating to Java 25. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 25 as the target/source and plugins will also be upgraded to versions that are compatible with Java 25. Gradle builds with unsupported or opaque Kotlin compilers are preserved and reported with a manual prerequisite; editable Kotlin 2.x declarations can take the supported minor-upgrade path before the build migration.,555,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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.""}]},{""name"":""org.openrewrite.java.migrate.table.KotlinGradleJava25Prerequisites"",""displayName"":""Kotlin Gradle prerequisites for Java 25"",""instanceName"":""Kotlin Gradle prerequisites for Java 25"",""description"":""Gradle builds preserved because their Kotlin compiler requires a manual compatibility decision."",""columns"":[{""name"":""buildFile"",""type"":""String"",""displayName"":""Build file"",""description"":""The compiler declaration associated with the preserved Gradle build.""},{""name"":""compilerVersion"",""type"":""String"",""displayName"":""Compiler version"",""description"":""The declared compiler value or configured default, or unknown when unresolved.""},{""name"":""reason"",""type"":""String"",""displayName"":""Reason"",""description"":""Why this build is not automatically migrated.""},{""name"":""manualPrerequisite"",""type"":""String"",""displayName"":""Manual prerequisite"",""description"":""The explicit compatibility decision needed before Java 25 migration.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava25,Migrate to Java 25,This recipe will apply changes commonly needed when migrating to Java 25. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 25 as the target/source and plugins will also be upgraded to versions that are compatible with Java 25. Gradle builds with unsupported or opaque Kotlin compilers are preserved and reported with a manual prerequisite; editable Kotlin 2.x declarations can take the supported minor-upgrade path before the build migration.,556,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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.""}]},{""name"":""org.openrewrite.java.migrate.table.KotlinGradleJava25Prerequisites"",""displayName"":""Kotlin Gradle prerequisites for Java 25"",""instanceName"":""Kotlin Gradle prerequisites for Java 25"",""description"":""Gradle builds preserved because their Kotlin compiler requires a manual compatibility decision."",""columns"":[{""name"":""buildFile"",""type"":""String"",""displayName"":""Build file"",""description"":""The compiler declaration associated with the preserved Gradle build.""},{""name"":""compilerVersion"",""type"":""String"",""displayName"":""Compiler version"",""description"":""The declared compiler value or configured default, or unknown when unresolved.""},{""name"":""reason"",""type"":""String"",""displayName"":""Reason"",""description"":""Why this build is not automatically migrated.""},{""name"":""manualPrerequisite"",""type"":""String"",""displayName"":""Manual prerequisite"",""description"":""The explicit compatibility decision needed before Java 25 migration.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava6,Migrate to Java 6,This recipe will apply changes commonly needed when upgrading to Java 6. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy.,7,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradeToJava7,Migrate to Java 7,This recipe will apply changes commonly needed when upgrading to Java 7. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy.,54,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradeToJava8,Migrate to Java 8,This recipe will apply changes commonly needed when upgrading to Java 8. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy.,123,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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.""}]}]" @@ -592,6 +592,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.j maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.search.ModuleOrParentHasDependency,Module or its reactor parent has a direct dependency,"Marks all files in modules that have a matching direct 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.",1,,Search,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,"[{""name"":""groupIdPattern"",""type"":""String"",""displayName"":""Group pattern"",""description"":""Group glob pattern used to match dependencies."",""example"":""org.projectlombok"",""required"":true},{""name"":""artifactIdPattern"",""type"":""String"",""displayName"":""Artifact pattern"",""description"":""Artifact glob pattern used to match dependencies."",""example"":""lombok"",""required"":true}]", maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeImpsortForJava25,Use a Java 25 compatible parser for Impsort 1.13,"Use JavaParser 3.28.2 with Impsort 1.13.0 to parse Java 25 yield statements. Preserve newer parser versions, other Impsort versions, and unresolved declarations.",1,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradePlexusComponentMetadataForJava25,Use a Java 25 compatible bytecode reader for Plexus metadata,"Use ASM 9.8 with Plexus component metadata 2.2.0, including declarations managed by an external parent. Preserve newer ASM versions, other plugin versions, and unresolved declarations.",1,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava25WithCompatibleGradleCompiler,Migrate eligible builds to Java 25,Apply the Java 25 migration only after shared Gradle build compiler prerequisites are established. Unsupported Kotlin 1.x and opaque older compiler builds are preserved without partial compiler or wrapper changes.,544,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradeToJava25WithCompatibleGradleCompiler,Migrate eligible builds to Java 25,Apply the Java 25 migration only after shared Gradle build compiler prerequisites are established. Unsupported Kotlin 1.x and opaque older compiler builds are preserved without partial compiler or wrapper changes.,545,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.UpgradeOwnedGradleKotlinCompilerForJava25,Upgrade an editable Gradle Kotlin 2 compiler before Java 25,"Upgrade owned literal Kotlin JVM 2.x declarations, including settings plugin management, to the verified 2.3 compiler baseline. Runs only in eligible Gradle builds with actual compiled Kotlin; never crosses Kotlin 1.x to K2 or downgrades newer compilers.",2,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""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-migrate-java,org.openrewrite.java.migrate.search.CheckKotlinGradleJava25Compatibility,Check Kotlin compiler prerequisites for Java 25,"Preserve Gradle builds compiling Kotlin with an unsupported or unresolved compiler, and report a manual prerequisite. Java-only Kotlin DSL builds are not excluded. Independent nested build boundaries are retained.",1,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,"[{""name"":""minimumCompilerVersion"",""type"":""String"",""displayName"":""Minimum Kotlin compiler version"",""description"":""A compiler version verified to compile Kotlin while Gradle runs on Java 25."",""example"":""2.3.0"",""required"":true},{""name"":""markCompatible"",""type"":""Boolean"",""displayName"":""Mark compatible files"",""description"":""Mark eligible files when used as a precondition. Otherwise only report manual prerequisites without changing sources."",""required"":false},{""name"":""upgradeEditableCompiler"",""type"":""Boolean"",""displayName"":""Upgrade editable compiler declarations"",""description"":""Upgrade only owned Kotlin JVM 2.x compiler literals inside compatible shared builds."",""required"":false},{""name"":""markAlignmentCandidates"",""type"":""Boolean"",""displayName"":""Mark alignment candidates"",""description"":""Select modeled Kotlin modules and markerless root builds with conventional root Kotlin sources and a known compatible or safely upgradable owned JVM compiler."",""required"":false}]","[{""name"":""org.openrewrite.java.migrate.table.KotlinGradleJava25Prerequisites"",""displayName"":""Kotlin Gradle prerequisites for Java 25"",""instanceName"":""Kotlin Gradle prerequisites for Java 25"",""description"":""Gradle builds preserved because their Kotlin compiler requires a manual compatibility decision."",""columns"":[{""name"":""buildFile"",""type"":""String"",""displayName"":""Build file"",""description"":""The compiler declaration associated with the preserved Gradle build.""},{""name"":""compilerVersion"",""type"":""String"",""displayName"":""Compiler version"",""description"":""The declared compiler value or configured default, or unknown when unresolved.""},{""name"":""reason"",""type"":""String"",""displayName"":""Reason"",""description"":""Why this build is not automatically migrated.""},{""name"":""manualPrerequisite"",""type"":""String"",""displayName"":""Manual prerequisite"",""description"":""The explicit compatibility decision needed before Java 25 migration.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeStandaloneSpringCoreForJava25,Upgrade standalone Spring Core metadata readers for Java 25,"Explicitly upgrade standalone Spring Core 5.3 OSS dependencies to 6.2.11 only when the complete Maven graph contains no other Spring family and typed Java usage is limited to private implementation metadata/resource APIs. Refuse framework management, unknown graphs, mixed versions, other APIs and exposed Spring signatures. Pin local dependencies without changing shared properties.",1,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, diff --git a/src/test/java/org/openrewrite/java/migrate/UpgradeStandaloneSpringCoreForJava25Test.java b/src/test/java/org/openrewrite/java/migrate/UpgradeStandaloneSpringCoreForJava25Test.java new file mode 100644 index 0000000000..d17d54c9df --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/UpgradeStandaloneSpringCoreForJava25Test.java @@ -0,0 +1,276 @@ +/* + * 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; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.maven.Assertions.pomXml; +import static org.openrewrite.test.SourceSpecs.text; + +class UpgradeStandaloneSpringCoreForJava25Test implements RewriteTest { + @Override public void defaults(RecipeSpec spec) { + spec.recipe(new UpgradeStandaloneSpringCoreForJava25()) + .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "spring-core")); + } + private static final String USE = """ + import org.springframework.core.type.classreading.MetadataReader; + import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; + class Use { + private String read(String name) throws java.io.IOException { + MetadataReader reader = new SimpleMetadataReaderFactory().getMetadataReader(name); + return reader.getClassMetadata().getClassName(); + } + } + """; + private static String dependency(String artifact, String version) { + return "org.springframework" + artifact + + "" + version + ""; + } + private static String pom(String version, String extra) { + return "4.0.0examplereader" + + "1" + dependency("spring-core", version) + extra + ""; + } + @ParameterizedTest @ValueSource(strings = {"5.3.0", "5.3.19", "5.3.39"}) + void upgradesOnlyStandaloneOssCore(String version) { + rewriteRun(pomXml(pom(version, ""), spec -> spec.after(after -> { + assertThat(after).contains("6.2.11"); return after; + })), java(USE)); + } + @Test void alignsDirectJcl() { + rewriteRun(pomXml(pom("5.3.19", dependency("spring-jcl", "5.3.19")), spec -> spec.after(after -> { + assertThat(after.split("6.2.11", -1)).hasSize(3); return after; + })), java(USE)); + } + @Test void preservesSharedProperty() { + String before = pom("${spring.version}", "").replace("", "5.3.19"); + rewriteRun(pomXml(before, spec -> spec.after(after -> { + assertThat(after).contains("5.3.19", "6.2.11"); return after; + })), java(USE)); + } + @Test void allowsProvenMojoParent() { + String parent = "org.codehaus.mojomojo-parent87"; + rewriteRun(pomXml(pom("5.3.19", "").replace("example", parent + "example"), spec -> spec.after(after -> { + assertThat(after).contains("6.2.11"); return after; + })), java(USE)); + } + @ParameterizedTest @ValueSource(strings = {"5.2.25.RELEASE", "6.2.11", "6.2.12", "7.0.0"}) + void preservesOtherLinesAndNewerVersions(String version) { rewriteRun(pomXml(pom(version, "")), java(USE)); } + @ParameterizedTest @ValueSource(strings = {"spring-context", "spring-beans", "spring-web"}) + void refusesOtherFrameworkModules(String artifact) { rewriteRun(pomXml(pom("5.3.19", dependency(artifact, "5.3.19"))), java(USE)); } + @Test void refusesTransitiveFrameworkFamilyInTestScope() { + rewriteRun(pomXml(pom("5.3.19", dependency("spring-context", "5.3.19").replace("", "test"))), java(USE)); + } + @Test void refusesMixedVersions() { rewriteRun(pomXml(pom("5.3.19", dependency("spring-jcl", "5.3.39"))), java(USE)); } + @Test void refusesBom() { + String before = pom("5.3.19", "").replace("", "" + dependency("spring-framework-bom", "5.3.19").replace("", "pomimport") + ""); + rewriteRun(pomXml(before), java(USE)); + } + @Test void refusesManagedFramework() { + String before = pom("5.3.19", "").replace("", "" + dependency("spring-context", "5.3.19") + ""); + rewriteRun(pomXml(before), java(USE)); + } + @Test void refusesFrameworkParent() { + String before = pom("5.3.19", "").replace("example", "org.springframework.bootspring-boot-starter-parent2.7.18example"); + rewriteRun(pomXml(before), java(USE)); + } + @Test void refusesUnknownParent() { + String before = pom("5.3.19", "").replace("example", "org.apacheapache33example"); + rewriteRun(pomXml(before), java(USE)); + } + @Test void refusesProfileGraph() { + rewriteRun(pomXml(pom("5.3.19", "").replace("", "other" + dependency("spring-context", "5.3.19") + "")), java(USE)); + } + @Test void refusesPublicReturnType() { + rewriteRun(pomXml(pom("5.3.19", "")), java(USE.replace("private String read", "public MetadataReader read").replace("return reader.getClassMetadata().getClassName();", "return reader;"))); + } + @Test void refusesProtectedParameterType() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.core.type.classreading.MetadataReader; + class Use { protected void accept(MetadataReader reader) {} } + """)); + } + @Test void refusesPublicGenericExposure() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.core.type.classreading.MetadataReader; + class Use { public java.util.List readers() { return null; } } + """)); + } + @Test void refusesPublicField() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.core.type.classreading.MetadataReader; + class Use { public MetadataReader reader; } + """)); + } + @Test void refusesSpringInheritance() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; + class Use extends SimpleMetadataReaderFactory {} + """)); + } + @Test void refusesOtherCoreApi() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.core.SpringVersion; + class Use { private String version() { return SpringVersion.getVersion(); } } + """)); + } + @Test void refusesNoTypedUsage() { rewriteRun(pomXml(pom("5.3.19", ""))); } + @Test void refusesUnknownSourceKind() { rewriteRun(pomXml(pom("5.3.19", "")), java(USE), text("class Unknown", spec -> spec.path("src/main/kotlin/Unknown.kt"))); } + @Test void refusesUnknownGraph() { rewriteRun(text(pom("5.3.19", ""), spec -> spec.path("pom.xml")), java(USE)); } + @Test void doesNotChangeOtherModule() { + rewriteRun(pomXml(pom("5.3.19", ""), spec -> spec.path("first/pom.xml")), java(USE, spec -> spec.path("second/src/main/java/Use.java"))); + } + @Test void refusesTransitiveSpringFromNonSpringDependency() { + String extra = "org.apache.camelcamel-spring3.14.0test"; + rewriteRun(pomXml(pom("5.3.19", extra)), java(USE)); + } + @Test void refusesOtherMethodOnAllowedType() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.util.ClassUtils; + class Use { private Class load() throws ClassNotFoundException { + return ClassUtils.forName("example.Type", getClass().getClassLoader()); + } } + """)); + } + @Test void refusesOpaqueRequestedVersion() { + rewriteRun(pomXml(pom("5.3.19", ""), spec -> spec.mapBeforeRecipe(doc -> { + org.openrewrite.maven.tree.MavenResolutionResult model = doc.getMarkers() + .findFirst(org.openrewrite.maven.tree.MavenResolutionResult.class).orElseThrow(); + org.openrewrite.maven.tree.Pom requested = model.getPom().getRequested(); + return doc.withMarkers(doc.getMarkers().setByType(model.withPom(model.getPom().withRequested( + requested.withDependencies(requested.getDependencies().stream() + .map(dependency -> dependency.withGav(dependency.getGav().withVersion("${unknown}"))) + .collect(java.util.stream.Collectors.toList())))))); + })), java(USE)); + } + @Test void refusesUnparsedChildGraph() { + rewriteRun(pomXml(pom("5.3.19", "")), + text(pom("5.3.19", ""), spec -> spec.path("child/pom.xml")), + java(USE, spec -> spec.path("child/src/main/java/Use.java"))); + } + @Test void allowsBuildOnlyProfile() { + String before = pom("5.3.19", "").replace("", "release1maven-site-plugin${plugin.version}"); + rewriteRun(pomXml(before, spec -> spec.after(after -> { + assertThat(after).contains("6.2.11", "1"); return after; + })), java(USE)); + } + @Test void refusesProfileVersionOverride() { + String before = pom("${spring.version}", "").replace("", "5.3.19") + .replace("", "future6.2.12"); + rewriteRun(pomXml(before), java(USE)); + } + @Test void supportsObservedResourceMetadataCalls() { + rewriteRun(pomXml(pom("5.3.19", ""), spec -> spec.after(after -> { + assertThat(after).contains("6.2.11"); return after; + })), java(""" + import org.springframework.core.io.Resource; + import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; + class Use { + private void read(ClassLoader loader) throws java.io.IOException { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(loader); + Resource[] resources = resolver.getResources("classpath*:example/*.class"); + Resource single = resolver.getResource("example/One.class"); + if (single.exists()) { + String name = single.getFilename(); + new SimpleMetadataReaderFactory().getMetadataReader(single).getClassMetadata().getClassName(); + } + } + } + """)); + } + @Test void refusesReflectiveSpringUsage() { + rewriteRun(pomXml(pom("5.3.19", "")), java(USE.replace("return reader.getClassMetadata().getClassName();", "String implementation = \"org.springframework.core.CustomReader\"; return reader.getClassMetadata().getClassName();"))); + } + @Test void refusesUnprovenSpringField() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.util.ClassUtils; + class Use { private String suffix() { return ClassUtils.ARRAY_SUFFIX; } } + """)); + } + @Test void handlesRecursiveNonSpringGenericSignature() { + rewriteRun(pomXml(pom("5.3.19", ""), spec -> spec.after(after -> { + assertThat(after).contains("6.2.11"); return after; + })), java(USE.replace("class Use {", "class Use { public > T identity(T input) { return input; }"))); + } + @Test void refusesAggregatorGraph() { + String before = pom("5.3.19", "").replace("", "pomchild"); + rewriteRun(pomXml(before), java(USE)); + } + @Test void refusesIndirectProfileVersionOverride() { + String before = pom("${spring.version}", "") + .replace("", "5.3.19${base.version}") + .replace("", "future6.2.12"); + rewriteRun(pomXml(before), java(USE)); + } + @Test void refusesBootBuildPlugin() { + String plugin = "org.springframework.bootspring-boot-maven-plugin2.7.18"; + rewriteRun(pomXml(pom("5.3.19", "").replace("", plugin + "")), java(USE)); + } + @Test void refusesBootPluginInInactiveProfile() { + String profile = "bootorg.springframework.bootspring-boot-maven-plugin2.7.18"; + rewriteRun(pomXml(pom("5.3.19", "").replace("", profile + "")), java(USE)); + } + @Test void refusesPublicMethodTypeParameterBound() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.core.type.classreading.MetadataReader; + class Use { public void accept() {} } + """)); + } + @Test void refusesClassTypeParameterBound() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.core.type.classreading.MetadataReader; + class Use {} + """)); + } + @Test void refusesRecordComponentApi() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.core.type.classreading.MetadataReader; + record Use(MetadataReader reader) {} + """)); + } + @Test void refusesSpringFieldWithPotentialGeneratedAccessors() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.core.type.classreading.MetadataReader; + class Use { private MetadataReader reader; } + """)); + } + @Test void refusesAnnotatedPrivateSpringSignature() { + rewriteRun(pomXml(pom("5.3.19", "")), java(""" + import org.springframework.core.type.classreading.MetadataReader; + class Use { @Deprecated private MetadataReader reader() { return null; } } + """)); + } + @Test void refreshesDependencyModelAfterLocalPin() { + rewriteRun(pomXml(pom("5.3.19", ""), spec -> spec.after(after -> { + assertThat(after).contains("6.2.11"); return after; + }).afterRecipe(doc -> { + org.openrewrite.maven.tree.MavenResolutionResult model = doc.getMarkers() + .findFirst(org.openrewrite.maven.tree.MavenResolutionResult.class).orElseThrow(); + assertThat(model.findDependencies("org.springframework", "spring-core", null)) + .isNotEmpty().allSatisfy(dependency -> assertThat(dependency.getVersion()).isEqualTo("6.2.11")); + assertThat(model.findDependencies("org.springframework", "spring-jcl", null)) + .isNotEmpty().allSatisfy(dependency -> assertThat(dependency.getVersion()).isEqualTo("6.2.11")); + })), java(USE)); + } +}