+ * The behavior differs based on project structure:
+ *
+ *
Single module: Adds to build/plugins
+ *
Multi-module with parent in reactor: Adds to parent's build/pluginManagement/plugins
+ *
Orphan module (no parent in reactor): Adds to build/plugins
+ *
+ */
@Value
@EqualsAndHashCode(callSuper = false)
-public class AddAnnotationProcessor extends Recipe {
+public class AddAnnotationProcessor extends ScanningRecipe {
private static final String MAVEN_COMPILER_PLUGIN_GROUP_ID = "org.apache.maven.plugins";
private static final String MAVEN_COMPILER_PLUGIN_ARTIFACT_ID = "maven-compiler-plugin";
@@ -56,68 +67,190 @@ public class AddAnnotationProcessor extends Recipe {
String displayName = "Add an annotation processor to `maven-compiler-plugin`";
- String description = "Add an annotation processor to the maven compiler plugin. Will not do anything if it already exists. " +
- "Also doesn't add anything when no other annotation processors are defined yet. " +
- "(Perhaps `ChangePluginConfiguration` can be used).";
+ String description = "Add an annotation processor path to the `maven-compiler-plugin` configuration. " +
+ "For modules with an in-reactor parent, adds to the parent's `build/pluginManagement/plugins` section. " +
+ "For modules without a parent or with a parent outside the reactor, adds directly to `build/plugins`. " +
+ "Updates the annotation processor version if a newer version is specified.";
+
+ /**
+ * Accumulator to track which POMs need modifications and how.
+ */
+ public static class Scanned {
+ /**
+ * Source paths of POMs that are referenced as parents by at least one child within the reactor.
+ * These should get pluginManagement updates.
+ */
+ Set parentPomPaths = new HashSet<>();
+
+ /**
+ * Source paths of POMs that have no parent within the reactor.
+ * After scanning, aggregator-only POMs will be filtered out.
+ */
+ Set candidateOrphanPaths = new HashSet<>();
+
+ /**
+ * Source paths of POMs that have a <modules> section (aggregators).
+ * Used to identify aggregator-only POMs that should not be modified.
+ */
+ Set aggregatorPaths = new HashSet<>();
+
+ /**
+ * Get the actual orphan paths (candidates minus aggregator-only POMs).
+ * A true orphan has no parent in reactor and is not an aggregator-only POM.
+ */
+ Set getOrphanPomPaths() {
+ Set result = new HashSet<>(candidateOrphanPaths);
+ // Remove aggregator-only POMs (aggregators that are not also parents)
+ for (Path aggregatorPath : aggregatorPaths) {
+ if (!parentPomPaths.contains(aggregatorPath)) {
+ result.remove(aggregatorPath);
+ }
+ }
+ return result;
+ }
+ }
+
+ @Override
+ public Scanned getInitialValue(ExecutionContext ctx) {
+ return new Scanned();
+ }
@Override
- public TreeVisitor, ExecutionContext> getVisitor() {
- return new MavenVisitor() {
+ public TreeVisitor, ExecutionContext> getScanner(Scanned acc) {
+ return new MavenIsoVisitor() {
@Override
- public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
- Xml.Tag plugins = (Xml.Tag) super.visitTag(tag, ctx);
- plugins = (Xml.Tag) new MavenPlugin.Matcher().asVisitor(plugin -> {
- if (MAVEN_COMPILER_PLUGIN_GROUP_ID.equals(plugin.getGroupId()) &&
- MAVEN_COMPILER_PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId())) {
- MavenResolutionResult mrr = getResolutionResult();
- AtomicReference> afterVisitor = new AtomicReference<>();
- Xml.Tag modifiedPlugin = new XmlIsoVisitor() {
- @Override
- public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
- Xml.Tag tg = super.visitTag(tag, ctx);
- if ("annotationProcessorPaths".equals(tg.getName())) {
+ public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
+ MavenResolutionResult mrr = getResolutionResult();
+ Path sourcePath = mrr.getPom().getRequested().getSourcePath();
+
+ if (mrr.parentPomIsProjectPom()) {
+ // This module has a parent within the reactor
+ // Mark the parent for pluginManagement update
+ MavenResolutionResult parent = mrr.getParent();
+ if (parent != null) {
+ Path parentPath = parent.getPom().getRequested().getSourcePath();
+ if (parentPath != null) {
+ acc.parentPomPaths.add(parentPath);
+ }
+ }
+ } else {
+ // This module has no parent within the reactor
+ // Mark as candidate orphan (will be filtered later if it's aggregator-only)
+ if (sourcePath != null) {
+ acc.candidateOrphanPaths.add(sourcePath);
+ }
+ }
+
+ // Track aggregator POMs (those with section)
+ List subprojects = mrr.getPom().getSubprojects();
+ if (sourcePath != null && subprojects != null && !subprojects.isEmpty()) {
+ acc.aggregatorPaths.add(sourcePath);
+ }
+
+ return document;
+ }
+ };
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor(Scanned acc) {
+ return new TreeVisitor() {
+ @Override
+ public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
+ if (tree == null) {
+ return null;
+ }
+
+ MavenResolutionResult mrr = tree.getMarkers().findFirst(MavenResolutionResult.class).orElse(null);
+ if (mrr == null) {
+ return tree;
+ }
+
+ Path sourcePath = mrr.getPom().getRequested().getSourcePath();
+ if (sourcePath == null) {
+ return tree;
+ }
+
+ boolean isParent = acc.parentPomPaths.contains(sourcePath);
+ // Skip POMs that are neither parents nor orphans (children with parents in reactor)
+ if (!isParent && !acc.getOrphanPomPaths().contains(sourcePath)) {
+ return tree;
+ }
+
+ // First, ensure the plugin exists - use the source path as file pattern
+ tree = new AddPluginVisitor(isParent,
+ MAVEN_COMPILER_PLUGIN_GROUP_ID, MAVEN_COMPILER_PLUGIN_ARTIFACT_ID, null,
+ "", null, null, null
+ ).visit(tree, ctx);
+
+ // Then, configure the annotation processor path
+ return new MavenIsoVisitor() {
+ @Override
+ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
+ Xml.Tag plugins = super.visitTag(tag, ctx);
+ plugins = (Xml.Tag) new MavenPlugin.Matcher(isParent, MAVEN_COMPILER_PLUGIN_GROUP_ID, MAVEN_COMPILER_PLUGIN_ARTIFACT_ID).asVisitor(plugin -> {
+ MavenResolutionResult currentMrr = getResolutionResult();
+ AtomicReference> maybePropertyUpdate = new AtomicReference<>();
+
+ Xml.Tag modifiedPlugin = new XmlIsoVisitor() {
+ @Override
+ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
+ Xml.Tag tg = super.visitTag(tag, ctx);
+
+ if (!"annotationProcessorPaths".equals(tg.getName())) {
+ return tg;
+ }
+
+ // Iterate the children (annotation processor paths) and try to update the version
for (int i = 0; i < tg.getChildren().size(); i++) {
Xml.Tag child = tg.getChildren().get(i);
- if (groupId.equals(child.getChildValue("groupId").orElse(null)) &&
- artifactId.equals(child.getChildValue("artifactId").orElse(null))) {
- if (!version.equals(child.getChildValue("version").orElse(null))) {
- String oldVersion = child.getChildValue("version").orElse("");
- boolean oldVersionUsesProperty = oldVersion.startsWith("${");
- String lookupVersion = oldVersionUsesProperty ?
- mrr.getPom().getValue(oldVersion.trim()) :
- oldVersion;
- VersionComparator comparator = Semver.validate(lookupVersion, null).getValue();
- if (comparator.compare(version, lookupVersion) > 0) {
- if (oldVersionUsesProperty) {
- afterVisitor.set(new ChangePropertyValue(oldVersion, version, null, null).getVisitor());
- } else {
- List tags = tg.getChildren();
- tags.set(i, child.withChildValue("version", version));
- return tg.withContent(tags);
- }
+ if (!groupId.equals(child.getChildValue("groupId").orElse(null)) ||
+ !artifactId.equals(child.getChildValue("artifactId").orElse(null))) {
+ continue;
+ }
+
+ if (!version.equals(child.getChildValue("version").orElse(null))) {
+ String oldVersion = child.getChildValue("version").orElse("");
+ boolean oldVersionUsesProperty = oldVersion.startsWith("${");
+ String lookupVersion = oldVersionUsesProperty ?
+ currentMrr.getPom().getValue(oldVersion.trim()) : oldVersion;
+ VersionComparator comparator = Semver.validate(lookupVersion, null).getValue();
+ if (comparator.compare(version, lookupVersion) > 0) {
+ if (oldVersionUsesProperty) {
+ // A maven property is used here, update in properties section later
+ maybePropertyUpdate.set(new ChangePropertyValue(oldVersion, version, null, null).getVisitor());
+ } else {
+ // Update the path's version directly
+ List tags = tg.getChildren();
+ tags.set(i, child.withChildValue("version", version));
+ return tg.withContent(tags);
}
}
- return tg;
}
+
+ return tg;
}
+
+ // Not found, so we add it
return tg.withContent(ListUtils.concat(tg.getChildren(), Xml.Tag.build(String.format(
"\n%s\n%s\n%s\n",
groupId, artifactId, version))));
}
- return tg;
+ }.visitTag(plugin.getTree(), ctx);
+
+ if (maybePropertyUpdate.get() != null) {
+ doAfterVisit(maybePropertyUpdate.get());
}
- }.visitTag(plugin.getTree(), ctx);
- if (afterVisitor.get() != null) {
- doAfterVisit(afterVisitor.get());
+
+ return modifiedPlugin;
+ }).visitNonNull(plugins, 0);
+
+ if (plugins != tag) {
+ plugins = autoFormat(plugins, ctx);
}
- return modifiedPlugin;
+ return plugins;
}
- return plugin.getTree();
- }).visitNonNull(plugins, 0);
- if (plugins != tag) {
- plugins = autoFormat(plugins, ctx);
- }
- return plugins;
+ }.visit(tree, ctx);
}
};
}
diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddManagedPlugin.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddManagedPlugin.java
new file mode 100644
index 00000000000..34ce20cdb3f
--- /dev/null
+++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddManagedPlugin.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.maven;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.intellij.lang.annotations.Language;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Option;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.xml.XPathMatcher;
+
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class AddManagedPlugin extends Recipe {
+ @Option(displayName = "Group",
+ description = "The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'.",
+ example = "org.openrewrite.maven")
+ String groupId;
+
+ @Option(displayName = "Artifact",
+ description = "The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'.",
+ example = "rewrite-maven-plugin")
+ String artifactId;
+
+ @Option(displayName = "Version",
+ description = "A fixed version of the plugin to add.",
+ example = "1.0.0",
+ required = false)
+ @Nullable
+ String version;
+
+ @Language("xml")
+ @Option(displayName = "Configuration",
+ description = "Optional plugin configuration provided as raw XML",
+ example = "foo",
+ required = false)
+ @Nullable
+ String configuration;
+
+ @Language("xml")
+ @Option(displayName = "Dependencies",
+ description = "Optional plugin dependencies provided as raw XML.",
+ example = "com.yourorgcore-lib1.0.0",
+ required = false)
+ @Nullable
+ String dependencies;
+
+ @Language("xml")
+ @Option(displayName = "Executions",
+ description = "Optional executions provided as raw XML.",
+ example = "generate-sourcesadd-source",
+ required = false)
+ @Nullable
+ String executions;
+
+ @Option(displayName = "File pattern",
+ description = "A glob expression that can be used to constrain which directories or source files should be searched. " +
+ "Multiple patterns may be specified, separated by a semicolon `;`. " +
+ "If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " +
+ "When not set, all source files are searched. ",
+ required = false,
+ example = "**/*-parent/grpc-*/pom.xml")
+ @Nullable
+ String filePattern;
+
+ String displayName = "Add Managed Maven plugin";
+
+ @Override
+ public String getInstanceNameSuffix() {
+ return String.format("`%s:%s:%s`", groupId, artifactId, version);
+ }
+
+ String description = "Add the specified Maven plugin to the Plugin Managed of the pom.xml.";
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return new AddPluginVisitor(true, groupId, artifactId, version, configuration, dependencies, executions, filePattern);
+ }
+}
diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddPlugin.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddPlugin.java
index 57b2db86cbc..0713045ec24 100644
--- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddPlugin.java
+++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddPlugin.java
@@ -31,9 +31,6 @@
@Value
@EqualsAndHashCode(callSuper = false)
public class AddPlugin extends Recipe {
-
- private static final XPathMatcher BUILD_MATCHER = new XPathMatcher("/project/build");
-
@Option(displayName = "Group",
description = "The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'.",
example = "org.openrewrite.maven")
@@ -59,6 +56,7 @@ public class AddPlugin extends Recipe {
@Nullable
String configuration;
+ @Language("xml")
@Option(displayName = "Dependencies",
description = "Optional plugin dependencies provided as raw XML.",
example = "com.yourorgcore-lib1.0.0",
@@ -66,6 +64,7 @@ public class AddPlugin extends Recipe {
@Nullable
String dependencies;
+ @Language("xml")
@Option(displayName = "Executions",
description = "Optional executions provided as raw XML.",
example = "generate-sourcesadd-source",
@@ -94,80 +93,6 @@ public String getInstanceNameSuffix() {
@Override
public TreeVisitor, ExecutionContext> getVisitor() {
- return new AddPluginVisitor();
- }
-
- private class AddPluginVisitor extends MavenIsoVisitor {
-
- @Override
- public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) {
- if (filePattern != null) {
- return PathUtils.matchesGlob(sourceFile.getSourcePath(), filePattern) && super.isAcceptable(sourceFile, ctx);
- }
-
- MavenResolutionResult mrr = sourceFile.getMarkers().findFirst(MavenResolutionResult.class).orElse(null);
- if (mrr == null || mrr.parentPomIsProjectPom()) {
- return false;
- }
-
- return super.isAcceptable(sourceFile, ctx);
- }
-
- @Override
- public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
- Xml.Tag root = document.getRoot();
- if (!root.getChild("build").isPresent()) {
- document = (Xml.Document) new AddToTagVisitor<>(root, Xml.Tag.build(""))
- .visitNonNull(document, ctx, getCursor().getParentOrThrow());
- }
- return super.visitDocument(document, ctx);
- }
-
- @Override
- public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
- Xml.Tag t = super.visitTag(tag, ctx);
-
- if (BUILD_MATCHER.matches(getCursor())) {
- Optional maybePlugins = t.getChild("plugins");
- Xml.Tag plugins;
- if (maybePlugins.isPresent()) {
- plugins = maybePlugins.get();
- } else {
- t = (Xml.Tag) new AddToTagVisitor<>(t, Xml.Tag.build("")).visitNonNull(t, ctx, getCursor().getParentOrThrow());
- //noinspection OptionalGetWithoutIsPresent
- plugins = t.getChild("plugins").get();
- }
-
- Optional maybePlugin = plugins.getChildren().stream()
- .filter(plugin ->
- "plugin".equals(plugin.getName()) &&
- groupId.equals(plugin.getChildValue("groupId").orElse(null)) &&
- artifactId.equals(plugin.getChildValue("artifactId").orElse(null))
- )
- .findAny();
-
- if (maybePlugin.isPresent()) {
- Xml.Tag plugin = maybePlugin.get();
- if (version != null && !version.equals(plugin.getChildValue("version").orElse(null))) {
- if (plugin.getChild("version").isPresent()) {
- t = (Xml.Tag) new ChangeTagValueVisitor<>(plugin.getChild("version").get(), version).visitNonNull(t, ctx, getCursor().getParentOrThrow());
- }
- }
- } else {
- Xml.Tag pluginTag = Xml.Tag.build(
- "\n" +
- "" + groupId + "\n" +
- "" + artifactId + "\n" +
- (version != null ? "" + version + "\n" : "") +
- (executions != null ? executions.trim() + "\n" : "") +
- (configuration != null ? configuration.trim() + "\n" : "") +
- (dependencies != null ? dependencies.trim() + "\n" : "") +
- "");
- t = (Xml.Tag) new AddToTagVisitor<>(plugins, pluginTag).visitNonNull(t, ctx, getCursor().getParentOrThrow());
- }
- }
-
- return t;
- }
+ return new AddPluginVisitor(false, groupId, artifactId, version, configuration, dependencies, executions, filePattern);
}
}
diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddPluginVisitor.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddPluginVisitor.java
new file mode 100644
index 00000000000..fee0e130a7c
--- /dev/null
+++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddPluginVisitor.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.maven;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.intellij.lang.annotations.Language;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.PathUtils;
+import org.openrewrite.SourceFile;
+import org.openrewrite.maven.tree.MavenResolutionResult;
+import org.openrewrite.xml.AddToTagVisitor;
+import org.openrewrite.xml.ChangeTagValueVisitor;
+import org.openrewrite.xml.XPathMatcher;
+import org.openrewrite.xml.tree.Xml;
+
+import java.util.Optional;
+
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class AddPluginVisitor extends MavenIsoVisitor {
+ private static final XPathMatcher BUILD_MATCHER = new XPathMatcher("/project/build");
+ private static final XPathMatcher MANAGEMENT_MATCHER = new XPathMatcher("/project/build/pluginManagement");
+
+ boolean asManagedPlugin;
+
+ String groupId;
+
+ String artifactId;
+
+ @Nullable
+ String version;
+
+ @Language("xml")
+ @Nullable
+ String configuration;
+
+ @Language("xml")
+ @Nullable
+ String dependencies;
+
+ @Language("xml")
+ @Nullable
+ String executions;
+
+ @Nullable
+ String filePattern;
+
+ @Override
+ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) {
+ if (filePattern != null) {
+ return PathUtils.matchesGlob(sourceFile.getSourcePath(), filePattern) && super.isAcceptable(sourceFile, ctx);
+ }
+
+ MavenResolutionResult mrr = sourceFile.getMarkers().findFirst(MavenResolutionResult.class).orElse(null);
+ if (mrr == null || mrr.parentPomIsProjectPom()) {
+ return false;
+ }
+
+ return super.isAcceptable(sourceFile, ctx);
+ }
+
+ @Override
+ public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
+ Xml.Tag root = document.getRoot();
+ if (!root.getChild("build").isPresent()) {
+ document = (Xml.Document) new AddToTagVisitor<>(root, Xml.Tag.build(""))
+ .visitNonNull(document, ctx, getCursor().getParentOrThrow());
+ }
+ Xml.Tag build = document.getRoot().getChild("build").get(); // we know its there we add it one line above!
+ if (asManagedPlugin && !build.getChild("pluginManagement").isPresent()) {
+ document = (Xml.Document) new AddToTagVisitor<>(build, Xml.Tag.build(""))
+ .visitNonNull(document, ctx, getCursor().getParentOrThrow());
+ }
+
+ return super.visitDocument(document, ctx);
+ }
+
+ @Override
+ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
+ Xml.Tag t = super.visitTag(tag, ctx);
+
+ if ( (!asManagedPlugin && BUILD_MATCHER.matches(getCursor())) ||
+ (asManagedPlugin && MANAGEMENT_MATCHER.matches(getCursor())) ) {
+ Optional maybePlugins = t.getChild("plugins");
+ Xml.Tag plugins;
+ if (maybePlugins.isPresent()) {
+ plugins = maybePlugins.get();
+ } else {
+ t = (Xml.Tag) new AddToTagVisitor<>(t, Xml.Tag.build("")).visitNonNull(t, ctx, getCursor().getParentOrThrow());
+ //noinspection OptionalGetWithoutIsPresent
+ plugins = t.getChild("plugins").get();
+ }
+
+ Optional maybePlugin = plugins.getChildren().stream()
+ .filter(plugin ->
+ "plugin".equals(plugin.getName()) &&
+ groupId.equals(plugin.getChildValue("groupId").orElse("org.apache.maven.plugins")) &&
+ artifactId.equals(plugin.getChildValue("artifactId").orElse(null))
+ )
+ .findAny();
+
+ if (maybePlugin.isPresent()) {
+ Xml.Tag plugin = maybePlugin.get();
+ if (version != null && !version.equals(plugin.getChildValue("version").orElse(null))) {
+ if (plugin.getChild("version").isPresent()) {
+ t = (Xml.Tag) new ChangeTagValueVisitor<>(plugin.getChild("version").get(), version).visitNonNull(t, ctx, getCursor().getParentOrThrow());
+ }
+ }
+ } else {
+ Xml.Tag pluginTag = Xml.Tag.build(
+ "\n" +
+ "" + groupId + "\n" +
+ "" + artifactId + "\n" +
+ (version != null ? "" + version + "\n" : "") +
+ (executions != null ? executions.trim() + "\n" : "") +
+ (configuration != null ? configuration.trim() + "\n" : "") +
+ (dependencies != null ? dependencies.trim() + "\n" : "") +
+ "");
+ t = (Xml.Tag) new AddToTagVisitor<>(plugins, pluginTag).visitNonNull(t, ctx, getCursor().getParentOrThrow());
+ }
+ }
+
+ return t;
+ }
+}
diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenPlugin.java b/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenPlugin.java
index 7b618cd750a..b3c9467210a 100644
--- a/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenPlugin.java
+++ b/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenPlugin.java
@@ -28,6 +28,8 @@
@Value
public class MavenPlugin implements Trait {
static final XPathMatcher PLUGIN_MATCHER = new XPathMatcher("//plugins/plugin");
+ static final XPathMatcher BUILD_PLUGIN_MATCHER = new XPathMatcher("//build/plugins/plugin");
+ static final XPathMatcher MANGED_PLUGIN_MATCHER = new XPathMatcher("//build/pluginManagement/plugins/plugin");
Cursor cursor;
@@ -38,6 +40,23 @@ public class MavenPlugin implements Trait {
public static class Matcher extends MavenTraitMatcher {
+ @Nullable
+ Boolean isManaged;
+ @Nullable
+ String groupId;
+ @Nullable
+ String artifactId;
+
+ public Matcher() {
+ this(null, null, null);
+ }
+
+ public Matcher(@Nullable Boolean isManaged, @Nullable String groupId, @Nullable String artifactId) {
+ this.isManaged = isManaged;
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ }
+
@Override
protected @Nullable MavenPlugin test(Cursor cursor) {
Object value = cursor.getValue();
@@ -46,15 +65,23 @@ public static class Matcher extends MavenTraitMatcher {
// `XPathMatcher` is still a bit expensive
if (!"plugin".equals(tag.getName()) ||
- (!PLUGIN_MATCHER.matches(cursor))) {
+ (isManaged == null && !PLUGIN_MATCHER.matches(cursor)) ||
+ (Boolean.FALSE.equals(isManaged) && !BUILD_PLUGIN_MATCHER.matches(cursor)) ||
+ (Boolean.TRUE.equals(isManaged) && !MANGED_PLUGIN_MATCHER.matches(cursor))) {
+ return null;
+ }
+
+ String currentGroupId = getProperty(cursor, "groupId").orElse("org.apache.maven.plugins");
+ if (groupId != null && !groupId.equals(currentGroupId)) {
+ return null;
+ }
+
+ String currentArtifactId = getProperty(cursor, "artifactId").orElse(null);
+ if (artifactId != null && currentArtifactId != null && !artifactId.equals(currentArtifactId)) {
return null;
}
- return new MavenPlugin(
- cursor,
- getProperty(cursor, "groupId").orElse("org.apache.maven.plugins"),
- getProperty(cursor, "artifactId").orElse(null)
- );
+ return new MavenPlugin(cursor, currentGroupId, currentArtifactId);
}
return null;
}
diff --git a/rewrite-maven/src/main/resources/META-INF/rewrite/recipes.csv b/rewrite-maven/src/main/resources/META-INF/rewrite/recipes.csv
index fa09b6c74fe..c3cde1d8c0f 100644
--- a/rewrite-maven/src/main/resources/META-INF/rewrite/recipes.csv
+++ b/rewrite-maven/src/main/resources/META-INF/rewrite/recipes.csv
@@ -95,3 +95,100 @@ maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.ModuleHasDepend
maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.ModuleHasPlugin,Module has plugin,"Searches for Maven modules that have a plugin matching the specified groupId and artifactId. Places a `SearchResult` marker on all sources within a module with a matching plugin. This recipe is intended to be used as a precondition for other recipes. For example this could be used to limit the application of a spring boot migration to only projects that apply the spring boot plugin, limiting unnecessary upgrading. If the search result you want is instead just the build.gradle(.kts) file applying the plugin, use the `FindPlugins` recipe instead.",1,Search,Maven,Openrewrite,Org,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""rewrite-maven-plugin"",""required"":true}]",
maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.security.UseHttpsForRepositories,Use HTTPS for repositories,Use HTTPS for repository URLs.,1,Security,Maven,Openrewrite,Org,,
maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.utilities.PrintMavenAsDot,Print Maven dependency hierarchy in DOT format,The DOT language format is specified [here](https://graphviz.org/doc/info/lang.html).,1,Utilities,Maven,Openrewrite,Org,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddCommentToMavenDependency,Add a comment to a `Maven` dependency or plugin,Adds a comment as the first element in a `Maven` dependency or plugin.,1,,,,Maven,"[{""name"":""xPath"",""type"":""String"",""displayName"":""XPath"",""description"":""An XPath expression used to find matching tags."",""example"":""/project/dependencies/dependency"",""required"":true},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""guava"",""required"":true},{""name"":""commentText"",""type"":""String"",""displayName"":""Comment text"",""description"":""The text to add as a comment.."",""example"":""This is excluded due to CVE and will be removed when we upgrade the next version is available."",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddManagedPlugin,Add Managed Maven plugin,Add the specified Maven plugin to the Plugin Managed of the pom.xml.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""rewrite-maven-plugin"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""A fixed version of the plugin to add."",""example"":""1.0.0""},{""name"":""configuration"",""type"":""String"",""displayName"":""Configuration"",""description"":""Optional plugin configuration provided as raw XML"",""example"":""foo""},{""name"":""dependencies"",""type"":""String"",""displayName"":""Dependencies"",""description"":""Optional plugin dependencies provided as raw XML."",""example"":""com.yourorgcore-lib1.0.0""},{""name"":""executions"",""type"":""String"",""displayName"":""Executions"",""description"":""Optional executions provided as raw XML."",""example"":""generate-sourcesadd-source""},{""name"":""filePattern"",""type"":""String"",""displayName"":""File pattern"",""description"":""A glob expression that can be used to constrain which directories or source files should be searched. Multiple patterns may be specified, separated by a semicolon `;`. If multiple patterns are supplied any of the patterns matching will be interpreted as a match. When not set, all source files are searched. "",""example"":""**/*-parent/grpc-*/pom.xml""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddParentPom,Add Maven parent,Add a parent pom to a Maven pom.xml. Does nothing if a parent pom is already present.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The group ID of the maven parent pom to be adopted."",""example"":""org.springframework.boot"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The artifact ID of the maven parent pom to be adopted."",""example"":""spring-boot-starter-parent"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""29.X"",""required"":true},{""name"":""relativePath"",""type"":""String"",""displayName"":""Relative path"",""description"":""New relative path attribute for parent lookup."",""example"":""../pom.xml"",""required"":true},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddPlugin,Add Maven plugin,Add the specified Maven plugin to the pom.xml.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""rewrite-maven-plugin"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""A fixed version of the plugin to add."",""example"":""1.0.0""},{""name"":""configuration"",""type"":""String"",""displayName"":""Configuration"",""description"":""Optional plugin configuration provided as raw XML"",""example"":""foo""},{""name"":""dependencies"",""type"":""String"",""displayName"":""Dependencies"",""description"":""Optional plugin dependencies provided as raw XML."",""example"":""com.yourorgcore-lib1.0.0""},{""name"":""executions"",""type"":""String"",""displayName"":""Executions"",""description"":""Optional executions provided as raw XML."",""example"":""generate-sourcesadd-source""},{""name"":""filePattern"",""type"":""String"",""displayName"":""File pattern"",""description"":""A glob expression that can be used to constrain which directories or source files should be searched. Multiple patterns may be specified, separated by a semicolon `;`. If multiple patterns are supplied any of the patterns matching will be interpreted as a match. When not set, all source files are searched. "",""example"":""**/*-parent/grpc-*/pom.xml""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddPluginDependency,Add Maven plugin dependencies,Adds the specified dependencies to a Maven plugin. Will not add the plugin if it does not already exist in the pom.,1,,,,Maven,"[{""name"":""pluginGroupId"",""type"":""String"",""displayName"":""Plugin group"",""description"":""Group ID of the plugin to which the dependency will be added. A group ID is the first part of a dependency coordinate `org.openrewrite.maven:rewrite-maven-plugin:VERSION`."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""pluginArtifactId"",""type"":""String"",""displayName"":""Plugin artifact"",""description"":""Artifact ID of the plugin to which the dependency will be added.The second part of a dependency coordinate `org.openrewrite.maven:rewrite-maven-plugin:VERSION`."",""example"":""rewrite-maven-plugin"",""required"":true},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The group ID of the dependency to add."",""example"":""org.openrewrite.recipe"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The artifact ID of the dependency to add."",""example"":""org.openrewrite.recipe"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the dependency to add."",""example"":""org.openrewrite.recipe"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddProfile,Add Maven profile,Add a maven profile to a `pom.xml` file.,1,,,,Maven,"[{""name"":""id"",""type"":""String"",""displayName"":""id"",""description"":""The profile id."",""example"":""default"",""required"":true},{""name"":""activation"",""type"":""String"",""displayName"":""Activation"",""description"":""activation details of a maven profile, provided as raw XML."",""example"":""foo""},{""name"":""properties"",""type"":""String"",""displayName"":""Properties"",""description"":""properties of a maven profile, provided as raw XML."",""example"":""foobar""},{""name"":""build"",""type"":""String"",""displayName"":""build"",""description"":""build details of a maven profile, provided as raw XML."",""example"":""foo""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddProperty,Add Maven project property,Add a new property to the Maven project property. Prefers to add the property to the parent if the project has multiple modules.,1,,,,Maven,"[{""name"":""key"",""type"":""String"",""displayName"":""Key"",""description"":""The name of the property key to be added."",""example"":""junit.version"",""required"":true},{""name"":""value"",""type"":""String"",""displayName"":""Value"",""description"":""The value of property to be added."",""example"":""4.13"",""required"":true},{""name"":""preserveExistingValue"",""type"":""Boolean"",""displayName"":""Preserve existing value"",""description"":""Preserve previous value if the property already exists in the pom file.""},{""name"":""trustParent"",""type"":""Boolean"",""displayName"":""Trust parent POM"",""description"":""If the parent defines a property with the same key, trust it even if the value isn't the same. Useful when you want to wait for the parent to have its value changed first. The parent is not trusted by default.""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddRepository,Add repository,Adds a new Maven Repository or updates a matching repository.,1,,,,Maven,"[{""name"":""id"",""type"":""String"",""displayName"":""Repository ID"",""description"":""A unique name to describe the repository."",""example"":""repo-id"",""required"":true},{""name"":""url"",""type"":""String"",""displayName"":""Repository URL"",""description"":""The URL of the repository."",""example"":""http://myrepo.maven.com/repo"",""required"":true},{""name"":""repoName"",""type"":""String"",""displayName"":""Repository name"",""description"":""A display name for the repository."",""example"":""My Great Repo Name""},{""name"":""layout"",""type"":""String"",""displayName"":""Repository layout"",""description"":""The Maven layout of the repository."",""example"":""default""},{""name"":""snapshotsEnabled"",""type"":""Boolean"",""displayName"":""Enable snapshots"",""description"":""Snapshots from the repository are available.""},{""name"":""snapshotsChecksumPolicy"",""type"":""String"",""displayName"":""Snapshots checksum policy"",""description"":""Governs whether snapshots require checksums."",""example"":""warn""},{""name"":""snapshotsUpdatePolicy"",""type"":""String"",""displayName"":""Snapshots update policy"",""description"":""The policy governing snapshot updating interval."",""example"":""always""},{""name"":""releasesEnabled"",""type"":""Boolean"",""displayName"":""Releases enabled"",""description"":""Releases from the repository are available""},{""name"":""releasesChecksumPolicy"",""type"":""String"",""displayName"":""Releases checksum policy"",""description"":""Governs whether releases require checksums."",""example"":""fail""},{""name"":""releasesUpdatePolicy"",""type"":""String"",""displayName"":""Releases update policy"",""description"":""The policy governing release updating interval."",""example"":""never""},{""name"":""type"",""type"":""Type"",""displayName"":""Repository type"",""description"":""The type of repository to add."",""example"":""Repository""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangeDependencyClassifier,Change Maven dependency classifier,Add or alter the classifier of the specified dependency.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""guava"",""required"":true},{""name"":""newClassifier"",""type"":""String"",""displayName"":""New classifier"",""description"":""Classifier to apply to specified Maven dependency. May be omitted, which indicates that no classifier should be added and any existing scope be removed from the dependency."",""example"":""jar""},{""name"":""changeManagedDependency"",""type"":""Boolean"",""displayName"":""Change Maven managed dependency"",""description"":""This flag can be set to explicitly change the classifier in Maven management dependency section. Default `false`."",""example"":""true""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId,Change Maven dependency,Change a Maven dependency coordinates. The `newGroupId` or `newArtifactId` **MUST** be different from before. Matching `` coordinates are also updated if a `newVersion` or `versionPattern` is provided. Exclusions that reference the old dependency coordinates will also be updated to match the new coordinates.,1,,,,Maven,"[{""name"":""oldGroupId"",""type"":""String"",""displayName"":""Old groupId"",""description"":""The old groupId to replace. The groupId is the first part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob expressions."",""example"":""org.openrewrite.recipe"",""required"":true},{""name"":""oldArtifactId"",""type"":""String"",""displayName"":""Old artifactId"",""description"":""The old artifactId to replace. The artifactId is the second part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob expressions."",""example"":""rewrite-testing-frameworks"",""required"":true},{""name"":""newGroupId"",""type"":""String"",""displayName"":""New groupId"",""description"":""The new groupId to use. Defaults to the existing group id."",""example"":""corp.internal.openrewrite.recipe""},{""name"":""newArtifactId"",""type"":""String"",""displayName"":""New artifactId"",""description"":""The new artifactId to use. Defaults to the existing artifact id."",""example"":""rewrite-testing-frameworks""},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""29.X""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""overrideManagedVersion"",""type"":""Boolean"",""displayName"":""Override managed version"",""description"":""If the new dependency has a managed version, this flag can be used to explicitly set the version on the dependency. The default for this flag is `false`.""},{""name"":""changeManagedDependency"",""type"":""Boolean"",""displayName"":""Update dependency management"",""description"":""Also update the dependency management section. The default for this flag is `true`.""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangeDependencyScope,Change Maven dependency scope,Add or alter the scope of the specified dependency.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""guava"",""required"":true},{""name"":""newScope"",""type"":""String"",""displayName"":""New scope"",""description"":""Scope to apply to specified Maven dependency. May be omitted, which indicates that no scope should be added and any existing scope be removed from the dependency."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""]}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangeExclusion,Change Maven dependency exclusion,"Modify Maven dependency exclusions, changing the group ID, artifact Id, or both. Useful when an excluded dependency has been renamed and references to it must be updated.",1,,,,Maven,"[{""name"":""oldGroupId"",""type"":""String"",""displayName"":""Old groupId"",""description"":""The old groupId to replace. Supports glob expressions."",""example"":""org.springframework"",""required"":true},{""name"":""oldArtifactId"",""type"":""String"",""displayName"":""Old artifactId"",""description"":""The old artifactId to replace. Supports glob expressions."",""example"":""spring-web*"",""required"":true},{""name"":""newGroupId"",""type"":""String"",""displayName"":""New groupId"",""description"":""The new groupId to use. Defaults to the existing group id."",""example"":""org.springframework.boot""},{""name"":""newArtifactId"",""type"":""String"",""displayName"":""New artifactId"",""description"":""The new artifactId to use. Defaults to the existing artifact id."",""example"":""spring-boot-starter-web""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangeManagedDependencyGroupIdAndArtifactId,"Change Maven managed dependency groupId, artifactId and optionally the version","Change the groupId, artifactId and optionally the version of a specified Maven managed dependency.",1,,,,Maven,"[{""name"":""oldGroupId"",""type"":""String"",""displayName"":""Old groupId"",""description"":""The old groupId to replace. The groupId is the first part of a managed dependency coordinate `com.google.guava:guava:VERSION`. Supports glob expressions."",""example"":""org.openrewrite.recipe"",""required"":true},{""name"":""oldArtifactId"",""type"":""String"",""displayName"":""Old artifactId"",""description"":""The old artifactId to replace. The artifactId is the second part of a managed dependency coordinate `com.google.guava:guava:VERSION`. Supports glob expressions."",""example"":""rewrite-testing-frameworks"",""required"":true},{""name"":""newGroupId"",""type"":""String"",""displayName"":""New groupId"",""description"":""The new groupId to use."",""example"":""corp.internal.openrewrite.recipe"",""required"":true},{""name"":""newArtifactId"",""type"":""String"",""displayName"":""New artifactId"",""description"":""The new artifactId to use."",""example"":""rewrite-testing-frameworks"",""required"":true},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""The new version to use."",""example"":""2.0.0""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangePackaging,Set Maven project packaging,Sets the packaging type of Maven projects. Either adds the packaging tag if it is missing or changes its context if present.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The groupId of the project whose packaging should be changed. Accepts glob patterns."",""example"":""org.openrewrite.*"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Group"",""description"":""The artifactId of the project whose packaging should be changed. Accepts glob patterns."",""example"":""rewrite-*"",""required"":true},{""name"":""packaging"",""type"":""String"",""displayName"":""Packaging"",""description"":""The type of packaging to set. If `null` specified the packaging tag will be removed"",""example"":""jar"",""required"":true},{""name"":""oldPackaging"",""type"":""String"",""displayName"":""Old Packaging"",""description"":""The old packaging type. If provided, will only change if the current packaging matches"",""example"":""jar""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangePluginConfiguration,Change Maven plugin configuration,Apply the specified configuration to a Maven plugin. Will not add the plugin if it does not already exist in the pom.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of the coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION' of the plugin to modify."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION' of the plugin to modify."",""example"":""rewrite-maven-plugin"",""required"":true},{""name"":""configuration"",""type"":""String"",""displayName"":""Configuration"",""description"":""Plugin configuration provided as raw XML overriding any existing configuration. Configuration inside `` blocks will not be altered. Supplying `null` will remove any existing configuration."",""example"":""bar""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangePluginDependencies,Change Maven plugin dependencies,Applies the specified dependencies to a Maven plugin. Will not add the plugin if it does not already exist in the pom.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""rewrite-maven-plugin"",""required"":true},{""name"":""dependencies"",""type"":""String"",""displayName"":""Dependencies"",""description"":""Plugin dependencies provided as dependency coordinates of format \""groupId:artifactId:version\"". When supplying multiple coordinates separate them with \"",\"". Supplying `null` will remove any existing plugin dependencies."",""example"":""org.openrewrite.recipe:rewrite-spring:1.0.0, org.openrewrite.recipe:rewrite-testing-frameworks:1.0.0""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangePluginExecutions,Change Maven plugin executions,Apply the specified executions to a Maven plugin. Will not add the plugin if it does not already exist in the pom.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""rewrite-maven-plugin"",""required"":true},{""name"":""executions"",""type"":""String"",""displayName"":""Executions"",""description"":""Plugin goal executions provided as raw XML. Supplying `null` will remove any existing executions."",""example"":""validatedryRun""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangePluginGroupIdAndArtifactId,Change Maven plugin group and artifact ID,Change the groupId and/or the artifactId of a specified Maven plugin. Optionally update the plugin version. This recipe does not perform any validation and assumes all values passed are valid.,1,,,,Maven,"[{""name"":""oldGroupId"",""type"":""String"",""displayName"":""Old group ID"",""description"":""The old group ID to replace. The group ID is the first part of a plugin coordinate 'com.google.guava:guava:VERSION'. Supports glob expressions."",""example"":""org.openrewrite.recipe"",""required"":true},{""name"":""oldArtifactId"",""type"":""String"",""displayName"":""Old artifact ID"",""description"":""The old artifactId to replace. The artifact ID is the second part of a plugin coordinate 'com.google.guava:guava:VERSION'. Supports glob expressions."",""example"":""my-deprecated-maven-plugin"",""required"":true},{""name"":""newGroupId"",""type"":""String"",""displayName"":""New group ID"",""description"":""The new group ID to use."",""example"":""corp.internal.openrewrite.recipe""},{""name"":""newArtifactId"",""type"":""String"",""displayName"":""New artifact ID"",""description"":""The new artifact ID to use."",""example"":""my-new-maven-plugin""},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""An exact version number."",""example"":""29.0""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangeProjectVersion,Change Maven Project Version,"Change the project version of a Maven pom.xml. Identifies the project to be changed by its groupId and artifactId. If the version is defined as a property, this recipe will only change the property value if the property exists within the same pom.",1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The group ID of the maven project to change its version. This can be a glob expression."",""example"":""org.openrewrite"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The artifact ID of the maven project to change its version. This can be a glob expression."",""example"":""*"",""required"":true},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""The new version to replace the maven project version."",""example"":""8.4.2"",""required"":true},{""name"":""overrideParentVersion"",""type"":""Boolean"",""displayName"":""Override Parent Version"",""description"":""This flag can be set to explicitly override the inherited parent version. Default `false`.""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangePropertyValue,Change Maven project property value,Changes the specified Maven project property value leaving the key intact.,1,,,,Maven,"[{""name"":""key"",""type"":""String"",""displayName"":""Key"",""description"":""The name of the property key whose value is to be changed."",""example"":""junit.version"",""required"":true},{""name"":""newValue"",""type"":""String"",""displayName"":""Value"",""description"":""Value to apply to the matching property."",""example"":""4.13"",""required"":true},{""name"":""addIfMissing"",""type"":""Boolean"",""displayName"":""Add if missing"",""description"":""Add the property if it is missing from the pom file.""},{""name"":""trustParent"",""type"":""Boolean"",""displayName"":""Trust parent POM"",""description"":""Even if the parent defines a property with the same key, trust it even if the value isn't the same. Useful when you want to wait for the parent to have its value changed first. The parent is not trusted by default.""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.EnableDevelocityBuildCache,Enable Develocity build cache,Add Develocity build cache configuration to any `.mvn/` Develocity configuration file that lack existing configuration.,1,,,,Maven,"[{""name"":""localEnabled"",""type"":""String"",""displayName"":""Enable local build cache"",""description"":""Value for `//develocity/buildCache/local/enabled`."",""example"":""true""},{""name"":""remoteEnabled"",""type"":""String"",""displayName"":""Enable remote build cache"",""description"":""Value for `//develocity/buildCache/remote/enabled`."",""example"":""true""},{""name"":""remoteStoreEnabled"",""type"":""String"",""displayName"":""Enable remote build cache store"",""description"":""Value for `//develocity/buildCache/remote/storeEnabled`."",""example"":""#{isTrue(env['CI'])}""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ExcludeDependency,Exclude Maven dependency,Exclude specified dependency from any dependency that transitively includes it.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""guava"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Match dependencies with the specified scope. If you specify `compile`, this will NOT match dependencies in `runtime`. The purpose of this is to be able to exclude dependencies that should be in a higher scope, e.g. a compile dependency that should be a test dependency."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""]}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ManagedToRuntimeDependencies,Convert managed dependencies to runtime dependencies,"This recipe processes Maven POMs, converting all `` entries into runtime scoped `` entries. Import scoped BOMs (like jackson-bom) are left unmodified in ``. Some style guidelines prefer that `` be used only for BOMs. This maintain that style while avoiding introducing new symbols onto the compile classpath unintentionally.",1,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ModernizeObsoletePoms,Modernize obsolete Maven poms,Very old Maven poms are no longer supported by current versions of Maven. This recipe updates poms with `3` to `4.0.0` of the Maven pom schema. This does not attempt to upgrade old dependencies or plugins and is best regarded as the starting point of a migration rather than an end-point.,1,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.OrderPomElements,Order POM elements,Order POM elements according to the [recommended](https://maven.apache.org/developers/conventions/code.html#pom-code-convention) order.,1,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveDependency,Remove Maven dependency,Removes a single dependency from the section of the pom.xml.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""guava"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Only remove dependencies if they are in this scope. If 'runtime', this willalso remove dependencies in the 'compile' scope because 'compile' dependencies are part of the runtime dependency set"",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""]}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveDuplicateDependencies,Remove duplicate Maven dependencies,Removes duplicated dependencies in the `` and `` sections of the `pom.xml`.,1,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveDuplicatePluginDeclarations,Remove duplicate plugin declarations,"Maven 4 rejects duplicate plugin declarations (same groupId and artifactId) with an error. This recipe removes duplicate plugin declarations, keeping only the first occurrence.",1,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveExclusion,Remove exclusion,Remove any matching exclusion from any matching dependency.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""guava"",""required"":true},{""name"":""exclusionGroupId"",""type"":""String"",""displayName"":""Exclusion group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""com.google.guava"",""required"":true},{""name"":""exclusionArtifactId"",""type"":""String"",""displayName"":""Exclusion artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""guava"",""required"":true},{""name"":""onlyIneffective"",""type"":""Boolean"",""displayName"":""Only ineffective"",""description"":""Default false. If enabled, matching exclusions will only be removed if they are ineffective (if the excluded dependency was not actually a transitive dependency of the target dependency).""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveManagedDependency,Remove Maven managed dependency,Removes a single managed dependency from the section of the pom.xml.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a managed dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a managed dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""guava"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Only remove managed dependencies if they are in this scope. If `runtime`, this will also remove managed dependencies in the 'compile' scope because `compile` dependencies are part of the runtime dependency set."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""]}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemovePlugin,Remove Maven plugin,Remove the specified Maven plugin from the POM.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""rewrite-maven-plugin"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemovePluginDependency,Remove Maven plugin dependency,Removes a dependency from the section of a plugin in the pom.xml.,1,,,,Maven,"[{""name"":""pluginGroupId"",""type"":""String"",""displayName"":""Plugin group ID"",""description"":""Group ID of the plugin from which the dependency will be removed. Supports glob.A Group ID is the first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""pluginArtifactId"",""type"":""String"",""displayName"":""Plugin artifact ID"",""description"":""Artifact ID of the plugin from which the dependency will be removed. Supports glob.The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""rewrite-maven-plugin"",""required"":true},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a plugin dependency coordinate. Supports glob."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a plugin dependency coordinate. Supports glob."",""example"":""guava"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveProperty,Remove Maven project property,Removes the specified Maven project property from the pom.xml.,1,,,,Maven,"[{""name"":""propertyName"",""type"":""String"",""displayName"":""Property name"",""description"":""Key name of the property to remove."",""example"":""junit.version"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveRedundantDependencyVersions,Remove redundant explicit dependency and plugin versions,Remove explicitly-specified dependency/plugin versions when a parent POM's `dependencyManagement`/`pluginManagement` specifies the version.,1,,,,Maven,"[{""name"":""groupPattern"",""type"":""String"",""displayName"":""Group"",""description"":""Group glob expression pattern used to match dependencies that should be managed.Group is the first part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.*""},{""name"":""artifactPattern"",""type"":""String"",""displayName"":""Artifact"",""description"":""Artifact glob expression pattern used to match dependencies that should be managed.Artifact is the second part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""guava*""},{""name"":""onlyIfVersionsMatch"",""type"":""Boolean"",""displayName"":""Only if versions match"",""description"":""Deprecated; use `onlyIfManagedVersionIs` instead. Only remove the explicit version if it exactly matches the managed dependency version. When `false` explicit versions will be removed if they are older than or equal to the managed dependency version. Default `true`.""},{""name"":""onlyIfManagedVersionIs"",""type"":""Comparator"",""displayName"":""Only if managed version is ..."",""description"":""Only remove the explicit version if the managed version has the specified comparative relationship to the explicit version. For example, `gte` will only remove the explicit version if the managed version is the same or newer. Default `eq`."",""valid"":[""ANY"",""EQ"",""LT"",""LTE"",""GT"",""GTE""]},{""name"":""except"",""type"":""List"",""displayName"":""Except"",""description"":""Accepts a list of GAVs. Dependencies matching a GAV will be ignored by this recipe. GAV versions are ignored if provided."",""example"":""com.jcraft:jsch""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveRedundantProperties,Remove redundant properties,Remove properties when a parent POM specifies the same property.,1,,,,Maven,"[{""name"":""namePattern"",""type"":""String"",""displayName"":""Property name"",""description"":""Property name glob expression pattern used to match properties that should be checked."",""example"":""*.version""},{""name"":""onlyIfValuesMatch"",""type"":""Boolean"",""displayName"":""Only if values match"",""description"":""Only remove the property if its value exactly matches the property value in the parent pom. Default `false`.""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveRepository,Remove repository,Removes a matching Maven repository.,1,,,,Maven,"[{""name"":""id"",""type"":""String"",""displayName"":""Repository ID"",""description"":""A unique repository ID."",""example"":""repo-id""},{""name"":""url"",""type"":""String"",""displayName"":""Repository URL"",""description"":""The URL of the repository."",""example"":""http://myrepo.maven.com/repo"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RenamePropertyKey,Rename Maven property key,Rename the specified Maven project property key leaving the value unchanged.,1,,,,Maven,"[{""name"":""oldKey"",""type"":""String"",""displayName"":""Old key"",""description"":""The old name of the property key to be replaced."",""example"":""junit.version"",""required"":true},{""name"":""newKey"",""type"":""String"",""displayName"":""New key"",""description"":""The new property name to use."",""example"":""version.org.junit"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UpdateMavenProjectPropertyJavaVersion,Update Maven Java project properties,"The Java version is determined by several project properties, including:
+
+ * `java.version`
+ * `jdk.version`
+ * `javaVersion`
+ * `jdkVersion`
+ * `maven.compiler.source`
+ * `maven.compiler.target`
+ * `maven.compiler.release`
+ * `release.version`
+
+If none of these properties are in use and the maven compiler plugin is not otherwise configured, adds the `maven.compiler.release` property.",1,,,,Maven,"[{""name"":""version"",""type"":""Integer"",""displayName"":""Java version"",""description"":""The Java version to upgrade to."",""example"":""11"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UpdateScmFromGitOrigin,Update SCM with Git origin,"Updates or adds the Maven `` tag based on the Git remote origin. By default, only existing Source Control Management (SCM) sections are updated. Set `addIfMissing` to `true` to also add missing SCM sections.",1,,,,Maven,"[{""name"":""addIfMissing"",""type"":""Boolean"",""displayName"":""Add if missing"",""description"":""If set to `true`, the recipe will add a `` section if it is missing. If set to `false` (default), the recipe will only update existing `` sections.""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UpgradePluginVersion,Upgrade Maven plugin version,"Upgrade the version of a plugin using Node Semver advanced range selectors, allowing more precise control over version updates to patch or minor releases.",1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'. Supports globs."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'. Supports globs."",""example"":""rewrite-maven-plugin"",""required"":true},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""An exact version number or node-style semver selector used to select the version number. You can also use `latest.release` for the latest available version and `latest.patch` if the current version is a valid semantic version. For more details, you can look at the documentation page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors)"",""example"":""29.X"",""required"":true},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""trustParent"",""type"":""Boolean"",""displayName"":""Trust parent POM"",""description"":""Even if the parent suggests a version that is older than what we are trying to upgrade to, trust it anyway. Useful when you want to wait for the parent to catch up before upgrading. The parent is not trusted by default.""},{""name"":""addVersionIfMissing"",""type"":""Boolean"",""displayName"":""Add version if missing"",""description"":""If the plugin is missing a version, add the latest release. Defaults to false.""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UseMavenCompilerPluginReleaseConfiguration,Use Maven compiler plugin release configuration,"Replaces any explicit `source` or `target` configuration (if present) on the `maven-compiler-plugin` with `release`, and updates the `release` value if needed. Will not downgrade the Java version if the current version is higher.",1,,,,Maven,"[{""name"":""releaseVersion"",""type"":""Integer"",""displayName"":""Release version"",""description"":""The new value for the release configuration. This recipe prefers ${java.version} if defined."",""example"":""11"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UseParentInference,Use Maven 4 parent inference,"Maven 4.1.0 supports automatic parent version inference when using a relative path. This recipe simplifies parent declarations by using the shorthand `` form when the parent is in the default location (`..`), removing the explicit ``, ``, ``, and `` elements. Maven automatically infers these values from the parent POM.",1,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddAnnotationProcessor,Add an annotation processor to `maven-compiler-plugin`,"Add an annotation processor path to the `maven-compiler-plugin` configuration. For modules with an in-reactor parent, adds to the parent's `build/pluginManagement/plugins` section. For modules without a parent or with a parent outside the reactor, adds directly to `build/plugins`. Updates the annotation processor version if a newer version is specified.",1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of the coordinate 'org.projectlombok:lombok-mapstruct-binding:0.2.0' of the processor to add."",""example"":""org.projectlombok"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a coordinate 'org.projectlombok:lombok-mapstruct-binding:0.2.0' of the processor to add."",""example"":""lombok-mapstruct-binding"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The third part of a coordinate 'org.projectlombok:lombok-mapstruct-binding:0.2.0' of the processor to add. Note that an exact version is expected"",""example"":""0.2.0"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddDependency,Add Maven dependency,Add a Maven dependency to a `pom.xml` file in the correct scope based on where it is used.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""29.X"",""required"":true},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""A scope to use when it is not what can be inferred from usage. Most of the time this will be left empty, but is used when adding a runtime, provided, or test dependency."",""example"":""runtime"",""valid"":[""compile"",""runtime"",""provided"",""test""]},{""name"":""releasesOnly"",""type"":""Boolean"",""displayName"":""Releases only"",""description"":""Whether to exclude snapshots from consideration when using a semver selector""},{""name"":""onlyIfUsing"",""type"":""String"",""displayName"":""Only if using"",""description"":""Used to determine if the dependency will be added and in which scope it should be placed. Required for multi-module projects to avoid adding dependencies unnecessarily."",""example"":""org.junit.jupiter.api.*""},{""name"":""type"",""type"":""String"",""displayName"":""Type"",""description"":""The type of dependency to add. If omitted Maven defaults to assuming the type is \""jar\""."",""example"":""jar"",""valid"":[""jar"",""pom"",""war""]},{""name"":""classifier"",""type"":""String"",""displayName"":""Classifier"",""description"":""A Maven classifier to add. Most commonly used to select shaded or test variants of a library"",""example"":""test""},{""name"":""optional"",""type"":""Boolean"",""displayName"":""Optional"",""description"":""Set the value of the `` tag. No `` tag will be added when this is `null`.""},{""name"":""familyPattern"",""type"":""String"",""displayName"":""Family pattern"",""description"":""A pattern, applied to groupIds, used to determine which other dependencies should have aligned version numbers. Accepts '*' as a wildcard character."",""example"":""com.fasterxml.jackson*""},{""name"":""acceptTransitive"",""type"":""Boolean"",""displayName"":""Accept transitive"",""description"":""Default false. If enabled, the dependency will not be added if it is already on the classpath as a transitive dependency."",""example"":""true""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddDevelocityMavenExtension,Add the Develocity Maven extension,"To integrate the Develocity Maven extension into Maven projects, ensure that the `develocity-maven-extension` is added to the `.mvn/extensions.xml` file if not already present. Additionally, configure the extension by adding the `.mvn/develocity.xml` configuration file.",1,,,,Maven,"[{""name"":""version"",""type"":""String"",""displayName"":""Extension version"",""description"":""A maven-compatible version number to select the gradle-enterprise-maven-extension version."",""example"":""1.17.4""},{""name"":""server"",""type"":""String"",""displayName"":""Server URL"",""description"":""The URL of the Develocity server."",""example"":""https://scans.gradle.com/"",""required"":true},{""name"":""allowUntrustedServer"",""type"":""Boolean"",""displayName"":""Allow untrusted server"",""description"":""When set to `true` the extension will be configured to allow unencrypted http connections with the server. If set to `false` or omitted, the extension will refuse to communicate without transport layer security enabled."",""example"":""true""},{""name"":""fileFingerprints"",""type"":""Boolean"",""displayName"":""Capture file fingerprints"",""description"":""When set to `true` the extension will capture additional information about the inputs to Maven goals. This increases the size of build scans, but is useful for diagnosing issues with goal caching. "",""example"":""true""},{""name"":""uploadInBackground"",""type"":""Boolean"",""displayName"":""Upload in background"",""description"":""When set to `false` the extension will not upload build scan in the background. By default, build scans are uploaded in the background after the build has finished to avoid blocking the build process."",""example"":""false""},{""name"":""publishCriteria"",""type"":""PublishCriteria"",""displayName"":""Publish Criteria"",""description"":""When set to `Always` the extension will publish build scans of every single build. This is the default behavior when omitted.When set to `Failure` the extension will only publish build scans when the build fails. When set to `Demand` the extension will only publish build scans when explicitly requested."",""example"":""Always"",""valid"":[""Always"",""Failure"",""Demand""]}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddManagedDependency,Add managed Maven dependency,Add a managed Maven dependency to a `pom.xml` file.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.apache.logging.log4j:ARTIFACT_ID:VERSION'."",""example"":""org.apache.logging.log4j"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.apache.logging.log4j:log4j-bom:VERSION'."",""example"":""log4j-bom"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""latest.release"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""An optional scope to use for the dependency management tag."",""example"":""import"",""valid"":[""import"",""runtime"",""provided"",""test""]},{""name"":""type"",""type"":""String"",""displayName"":""Type"",""description"":""An optional type to use for the dependency management tag."",""example"":""pom"",""valid"":[""jar"",""pom"",""war""]},{""name"":""classifier"",""type"":""String"",""displayName"":""Classifier"",""description"":""An optional classifier to use for the dependency management tag"",""example"":""test""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select 29.0-jre"",""example"":""-jre""},{""name"":""releasesOnly"",""type"":""Boolean"",""displayName"":""Releases only"",""description"":""Whether to exclude snapshots from consideration when using a semver selector""},{""name"":""onlyIfUsing"",""type"":""String"",""displayName"":""Only if using glob expression for group:artifact"",""description"":""Only add managed dependencies to projects having a dependency matching the expression."",""example"":""org.apache.logging.log4j:log4j*""},{""name"":""addToRootPom"",""type"":""Boolean"",""displayName"":""Add to the root pom"",""description"":""Add to the root pom where root is the eldest parent of the pom within the source set.""},{""name"":""because"",""type"":""String"",""displayName"":""Because"",""description"":""The reason for adding the managed dependency. This will be added as an XML comment preceding the managed dependency."",""example"":""CVE-2021-1234""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.AddRuntimeConfig,Add a configuration option for the Maven runtime,Add a new configuration option for the Maven runtime if not already present.,1,,,,Maven,"[{""name"":""relativeConfigFileName"",""type"":""String"",""displayName"":""Config file"",""description"":""The file name for setting the runtime configuration."",""example"":""maven.config"",""valid"":[""maven.config"",""jvm.config""],""required"":true},{""name"":""flag"",""type"":""String"",""displayName"":""Runtime flag"",""description"":""The runtime flag name to be set."",""example"":""-T"",""required"":true},{""name"":""argument"",""type"":""String"",""displayName"":""Runtime flag argument"",""description"":""The argument to set for the runtime flag. Some flags do not need to provide a value."",""example"":""3""},{""name"":""separator"",""type"":""Separator"",""displayName"":""Separator between runtime flag and argument"",""description"":""The separator to use if flag and argument have been provided."",""example"":""="",""valid"":["""","" "",""=""],""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ChangeParentPom,Change Maven parent,"Change the parent pom of a Maven pom.xml by matching the existing parent via groupId and artifactId, and updating it to a new groupId, artifactId, version, and optional relativePath. Also updates the project to retain dependency management and properties previously inherited from the old parent that are no longer provided by the new parent. Removes redundant dependency versions already managed by the new parent.",1,,,,Maven,"[{""name"":""oldGroupId"",""type"":""String"",""displayName"":""Old group ID"",""description"":""The group ID of the Maven parent pom to be changed away from."",""example"":""org.springframework.boot"",""required"":true},{""name"":""newGroupId"",""type"":""String"",""displayName"":""New group ID"",""description"":""The group ID of the new maven parent pom to be adopted. If this argument is omitted it defaults to the value of `oldGroupId`."",""example"":""org.springframework.boot""},{""name"":""oldArtifactId"",""type"":""String"",""displayName"":""Old artifact ID"",""description"":""The artifact ID of the maven parent pom to be changed away from."",""example"":""spring-boot-starter-parent"",""required"":true},{""name"":""newArtifactId"",""type"":""String"",""displayName"":""New artifact ID"",""description"":""The artifact ID of the new maven parent pom to be adopted. If this argument is omitted it defaults to the value of `oldArtifactId`."",""example"":""spring-boot-starter-parent""},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""29.X"",""required"":true},{""name"":""oldRelativePath"",""type"":""String"",""displayName"":""Old relative path"",""description"":""The relativePath of the maven parent pom to be changed away from. Use an empty String to match ``, use `../pom.xml` to match the default value."",""example"":""../../pom.xml""},{""name"":""newRelativePath"",""type"":""String"",""displayName"":""New relative path"",""description"":""New relative path attribute for parent lookup."",""example"":""../pom.xml""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""allowVersionDowngrades"",""type"":""Boolean"",""displayName"":""Allow version downgrades"",""description"":""If the new parent has the same group/artifact, this flag can be used to only upgrade the version if the target version is newer than the current.""},{""name"":""except"",""type"":""List"",""displayName"":""Except"",""description"":""Accepts a list of GAVs that should be retained when calling `RemoveRedundantDependencyVersions`."",""example"":""com.jcraft:jsch""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.IncrementProjectVersion,Increment Maven project version,"Increase Maven project version by incrementing either the major, minor, or patch version as defined by [semver](https://semver.org/). Other versioning schemes are not supported.",1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The group ID of the Maven project to change its version. This can be a glob expression."",""example"":""org.openrewrite"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The artifact ID of the Maven project to change its version. This can be a glob expression."",""example"":""*"",""required"":true},{""name"":""digit"",""type"":""SemverDigit"",""displayName"":""Semver digit"",""description"":""`MAJOR` increments the first digit, `MINOR` increments the second digit, and `PATCH` increments the third digit."",""example"":""PATCH"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ManageDependencies,Manage dependencies,Make existing dependencies managed by moving their version to be specified in the dependencyManagement section of the POM.,1,,,,Maven,"[{""name"":""groupPattern"",""type"":""String"",""displayName"":""Group"",""description"":""Group glob expression pattern used to match dependencies that should be managed.Group is the first part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""com.google.*"",""required"":true},{""name"":""artifactPattern"",""type"":""String"",""displayName"":""Artifact"",""description"":""Artifact glob expression pattern used to match dependencies that should be managed.Artifact is the second part of a dependency coordinate `com.google.guava:guava:VERSION`."",""example"":""guava*""},{""name"":""addToRootPom"",""type"":""Boolean"",""displayName"":""Add to the root POM"",""description"":""Add to the root POM where root is the eldest parent of the pom within the source set.""},{""name"":""skipModelUpdate"",""type"":""Boolean"",""displayName"":""Skip model updates"",""description"":""Optionally skip updating the dependency model after managing dependencies. Updating the model does not affect the source code of the POM,but will cause the resolved dependency model to reflect the changes made to the POM. If this recipe is ran standalone, it is not necessary to update the model.""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveUnusedProperties,Remove unused properties,Detect and remove Maven property declarations which do not have any usage within the project.,1,,,,Maven,"[{""name"":""propertyPattern"",""type"":""String"",""displayName"":""Property pattern"",""description"":""A pattern to filter properties to remove. Defaults to `.+?` to match anything"",""example"":"".+\\.version""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UpdateMavenWrapper,Update Maven wrapper,Update the version of Maven used in an existing Maven wrapper.,1,,,,Maven,"[{""name"":""wrapperVersion"",""type"":""String"",""displayName"":""New wrapper version"",""description"":""An exact version number or node-style semver selector used to select the wrapper version number."",""example"":""3.x""},{""name"":""wrapperDistribution"",""type"":""String"",""displayName"":""Wrapper Distribution type"",""description"":""The distribution of the Maven wrapper to use.\n\n* \""bin\"" uses a `maven-wrapper.jar` compiled binary.\n* \""only-script\"" uses a lite version of `mvnw`/`mvnw.cmd` using wget/curl or powershell. (required wrapper 3.2.0 or newer)\n* \""script\"" downloads `maven-wrapper.jar` or `MavenWrapperDownloader.java` to then download a full distribution.\n* \""source\"" uses `MavenWrapperDownloader.java` source file.\n\nDefaults to \""bin\""."",""valid"":[""bin"",""only-script"",""script"",""source""]},{""name"":""distributionVersion"",""type"":""String"",""displayName"":""New distribution version"",""description"":""An exact version number or node-style semver selector used to select the Maven version number."",""example"":""3.x""},{""name"":""repositoryUrl"",""type"":""String"",""displayName"":""Repository URL"",""description"":""The URL of the repository to download the Maven wrapper and distribution from. Supports repositories with a Maven layout. Defaults to `https://repo.maven.apache.org/maven2`."",""example"":""https://repo.maven.apache.org/maven2""},{""name"":""addIfMissing"",""type"":""Boolean"",""displayName"":""Add if missing"",""description"":""Add a Maven wrapper, if it's missing. Defaults to `true`.""},{""name"":""enforceWrapperChecksumVerification"",""type"":""Boolean"",""displayName"":""Enforce checksum verification for maven-wrapper.jar"",""description"":""Enforce checksum verification for the maven-wrapper.jar. Enabling this feature may sporadically result in build failures, such as [MWRAPPER-103](https://issues.apache.org/jira/browse/MWRAPPER-103). Defaults to `false`.""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UpgradeDependencyVersion,Upgrade Maven dependency version,"Upgrade the version of a dependency by specifying a group and (optionally) an artifact using Node Semver advanced range selectors, allowing more precise control over version updates to patch or minor releases.",1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""com.fasterxml.jackson*"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""jackson-module*"",""required"":true},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""An exact version number or node-style semver selector used to select the version number. You can also use `latest.release` for the latest available version and `latest.patch` if the current version is a valid semantic version. For more details, you can look at the documentation page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors)"",""example"":""29.X"",""required"":true},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'newVersion' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""overrideManagedVersion"",""type"":""Boolean"",""displayName"":""Override managed version"",""description"":""This flag can be set to explicitly override a managed dependency's version. If the dependency has its version managed by a Bill of Materials (BOM), enabling this flag will attempt to upgrade the BOM. The default for this flag is `false`.""},{""name"":""retainVersions"",""type"":""List"",""displayName"":""Retain versions"",""description"":""Accepts a list of GAVs. For each GAV, if it is a project direct dependency, and it is removed from dependency management after the changes from this recipe, then it will be retained with an explicit version. The version can be omitted from the GAV to use the old value from dependency management"",""example"":""com.jcraft:jsch""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UpgradeParentVersion,Upgrade Maven parent project version,Set the parent pom version number according to a [version selector](https://docs.openrewrite.org/reference/dependency-version-selectors) or to a specific version number.,1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.springframework.boot:spring-boot-parent:VERSION'."",""example"":""org.springframework.boot"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.springframework.boot:spring-boot-parent:VERSION'."",""example"":""spring-boot-parent"",""required"":true},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""29.X"",""required"":true},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""},{""name"":""onlyExternal"",""type"":""Boolean"",""displayName"":""Only external"",""description"":""Only upgrade `` if external to the project, i.e. it has an empty ``. Defaults to `false`.""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UpgradeTransitiveDependencyVersion,Upgrade transitive Maven dependencies,"Upgrades the version of a transitive dependency in a Maven pom file. Leaves direct dependencies unmodified. Can be paired with the regular Upgrade Dependency Version recipe to upgrade a dependency everywhere, regardless of whether it is direct or transitive.",1,,,,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.apache.logging.log4j:ARTIFACT_ID:VERSION'."",""example"":""org.apache.logging.log4j"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.apache.logging.log4j:log4j-bom:VERSION'."",""example"":""log4j-bom"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""latest.release"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""An optional scope to use for the dependency management tag."",""example"":""import"",""valid"":[""import"",""runtime"",""provided"",""test""]},{""name"":""type"",""type"":""String"",""displayName"":""Type"",""description"":""An optional type to use for the dependency management tag."",""example"":""pom"",""valid"":[""jar"",""pom"",""war""]},{""name"":""classifier"",""type"":""String"",""displayName"":""Classifier"",""description"":""An optional classifier to use for the dependency management tag"",""example"":""test""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select 29.0-jre"",""example"":""-jre""},{""name"":""releasesOnly"",""type"":""Boolean"",""displayName"":""Releases only"",""description"":""Whether to exclude snapshots from consideration when using a semver selector""},{""name"":""onlyIfUsing"",""type"":""String"",""displayName"":""Only if using glob expression for group:artifact"",""description"":""Only add managed dependencies to projects having a dependency matching the expression."",""example"":""org.apache.logging.log4j:log4j*""},{""name"":""addToRootPom"",""type"":""Boolean"",""displayName"":""Add to the root pom"",""description"":""Add to the root pom where root is the eldest parent of the pom within the source set.""},{""name"":""because"",""type"":""String"",""displayName"":""Because"",""description"":""The reason for upgrading the transitive dependency. This will be added as an XML comment preceding the managed dependency."",""example"":""CVE-2021-1234""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.BestPractices,Apache Maven best practices,Applies best practices to Maven POMs.,33,,,,Maven,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.MigrateToMaven4,Migrate to Maven 4,"Migrates Maven POMs from Maven 3 to Maven 4, addressing breaking changes and deprecations. This recipe updates property expressions, lifecycle phases, removes duplicate plugin declarations, and replaces removed properties to ensure compatibility with Maven 4.",53,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ReplaceRemovedRootDirectoryProperties,Replace removed root directory properties,Maven 4 removed support for deprecated root directory properties. This recipe replaces `${executionRootDirectory}` with `${session.rootDirectory}` and `${multiModuleProjectDirectory}` with `${project.rootDirectory}`.,5,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ReplaceDeprecatedLifecyclePhases,Replace deprecated lifecycle phases,"Maven 4 deprecated all `pre-*` and `post-*` lifecycle phases in favor of the `before:` and `after:` syntax. This recipe updates plugin phase declarations to use the new syntax, including `pre-clean` → `before:clean`, `pre-site` → `before:site`, `pre-integration-test` → `before:integration-test`, and their `post-*` equivalents.",13,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.UpgradeToModelVersion410,Upgrade to Maven model version 4.1.0,"Upgrades Maven POMs from model version 4.0.0 to 4.1.0, enabling new Maven 4 features like ``, `bom` packaging, and automatic version inference. This recipe updates the `` element, `xmlns` namespace, and `xsi:schemaLocation` from 4.0.0 to 4.1.0.",7,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.ReplaceModulesWithSubprojects,Replace modules with subprojects,Maven 4 model version 4.1.0 deprecates the `` element in favor of `` to eliminate confusion with Java's Platform Module System (JPMS). This recipe renames `` to `` and `` children to ``.,5,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.RemoveMavenWrapper,Remove Maven wrapper,"Remove Maven wrapper files from a project. This includes the `mvnw` and `mvnw.cmd` scripts, and the `.mvn/wrapper` directory.",7,,,,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.cleanup.DependencyManagementDependencyRequiresVersion,Dependency management dependencies should have a version,"If they don't have a version, they can't possibly affect dependency resolution anywhere, and can be safely removed.",1,,,Cleanup,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.cleanup.ExplicitDependencyVersion,Add explicit dependency versions,"Add explicit dependency versions to POMs for reproducibility, as the `LATEST` and `RELEASE` version keywords are deprecated.",1,,,Cleanup,Maven,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.cleanup.ExplicitPluginGroupId,Add explicit `groupId` to Maven plugins,Add the default `org.apache.maven.plugins` to plugins for clarity.,1,,,Cleanup,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.cleanup.ExplicitPluginVersion,Add explicit plugin versions,"Add explicit plugin versions to POMs for reproducibility, as [MNG-4173](https://issues.apache.org/jira/browse/MNG-4173) removes automatic version resolution for POM plugins.",1,,,Cleanup,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.cleanup.PrefixlessExpressions,Drop prefixless expressions in POM,MNG-7404 drops support for prefixless in POMs. This recipe will add the `project.` prefix where missing.,13,,,Cleanup,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.plugin.DependencyPluginGoalResolveSources,Migrate to `maven-dependency-plugin` goal `resolve-sources`,Migrate from `sources` to `resolve-sources` for the `maven-dependency-plugin`.,1,,,Plugin,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.DependencyInsight,Maven dependency insight,"Find direct and transitive dependencies matching a group, artifact, and scope. Results include dependencies that either directly match or transitively include a matching dependency.",1,,,Search,Maven,"[{""name"":""groupIdPattern"",""type"":""String"",""displayName"":""Group pattern"",""description"":""Group glob pattern used to match dependencies."",""example"":""com.fasterxml.jackson.module"",""required"":true},{""name"":""artifactIdPattern"",""type"":""String"",""displayName"":""Artifact pattern"",""description"":""Artifact glob pattern used to match dependencies."",""example"":""jackson-module-*"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Match dependencies with the specified scope. All scopes are searched by default."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided"",""system""]},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used.All versions are searched by default."",""example"":""1.x""},{""name"":""onlyDirect"",""type"":""Boolean"",""displayName"":""Only direct"",""description"":""If enabled, transitive dependencies will not be considered. All dependencies are searched by default."",""example"":""true""}]","[{""name"":""org.openrewrite.maven.table.DependenciesInUse"",""displayName"":""Dependencies in use"",""description"":""Direct and transitive dependencies in use."",""columns"":[{""name"":""projectName"",""type"":""String"",""displayName"":""Project name"",""description"":""The name of the project that contains the dependency.""},{""name"":""sourceSet"",""type"":""String"",""displayName"":""Source set"",""description"":""The source set that contains the dependency.""},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The resolved version.""},{""name"":""datedSnapshotVersion"",""type"":""String"",""displayName"":""Dated snapshot version"",""description"":""The resolved dated snapshot version or `null` if this dependency is not a snapshot.""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Dependency scope. This will be `compile` if the dependency is direct and a scope is not explicitly specified in the POM.""},{""name"":""count"",""type"":""Integer"",""displayName"":""Count"",""description"":""How many times does this dependency appear.""}]},{""name"":""org.openrewrite.maven.table.ExplainDependenciesInUse"",""displayName"":""Explain dependencies in use"",""description"":""A dependency graph explainer similar to that shown by `gradle dependencyInsight` for each matching dependency. This table will contain a row per matching dependency per configuration per (sub)project."",""columns"":[{""name"":""projectName"",""type"":""String"",""displayName"":""Project name"",""description"":""The name of the project that contains the dependency.""},{""name"":""sourceSet"",""type"":""String"",""displayName"":""Source set"",""description"":""The source set that contains the dependency.""},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The resolved version.""},{""name"":""datedSnapshotVersion"",""type"":""String"",""displayName"":""Dated snapshot version"",""description"":""The resolved dated snapshot version or `null` if this dependency is not a snapshot.""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Dependency scope. This will be `compile` if the dependency is direct and a scope is not explicitly specified in the POM.""},{""name"":""count"",""type"":""Integer"",""displayName"":""Count"",""description"":""How many times does this dependency appear.""},{""name"":""dependencyGraph"",""type"":""String"",""displayName"":""Dependency graph"",""description"":""The dependency paths that requested the dependency.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.DoesNotIncludeDependency,Does not include Maven dependency,"A precondition which returns false if visiting a Maven pom which includes the specified dependency in the classpath of some scope. For compatibility with multimodule projects, this should most often be applied as a precondition.",1,,,Search,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used. All versions are searched by default."",""example"":""1.x""},{""name"":""onlyDirect"",""type"":""Boolean"",""displayName"":""Only direct dependencies"",""description"":""Default false. If enabled, transitive dependencies will not be considered."",""example"":""true""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Default any. If specified, only the requested scope's classpaths will be checked."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""]}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.EffectiveDependencies,Effective dependencies,Emit the data of binary dependency relationships.,1,,,Search,Maven,"[{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Match dependencies with the specified scope"",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""],""required"":true}]","[{""name"":""org.openrewrite.maven.table.DependencyGraph"",""displayName"":""Dependency graph"",""description"":""Relationships between dependencies."",""columns"":[{""name"":""projectName"",""type"":""String"",""displayName"":""Project name"",""description"":""The name of the project that contains the dependency.""},{""name"":""sourceSet"",""type"":""String"",""displayName"":""Source set"",""description"":""The source set that contains the dependency.""},{""name"":""from"",""type"":""String"",""displayName"":""From dependency"",""description"":""What depends on the 'to' dependency.""},{""name"":""to"",""type"":""String"",""displayName"":""From dependency"",""description"":""A dependency.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.EffectiveManagedDependencies,Effective managed dependencies,Emit the data of binary dependency relationships.,1,,,Search,Maven,,"[{""name"":""org.openrewrite.maven.table.ManagedDependencyGraph"",""displayName"":""Managed dependency graph"",""description"":""Relationships between POMs and their ancestors that define managed dependencies."",""columns"":[{""name"":""from"",""type"":""String"",""displayName"":""From dependency"",""description"":""What depends on the 'to' dependency.""},{""name"":""to"",""type"":""String"",""displayName"":""From dependency"",""description"":""A dependency.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.EffectiveMavenRepositories,List effective Maven repositories,"Lists the Maven repositories that would be used for dependency resolution, in order of precedence. This includes Maven repositories defined in the Maven settings file (and those contributed by active profiles) as determined when the LST was produced.",1,,,Search,Maven,"[{""name"":""useMarkers"",""type"":""Boolean"",""displayName"":""Use markers"",""description"":""Whether to add markers for each effective Maven repository to the POM. Default `false`.""}]","[{""name"":""org.openrewrite.maven.search.EffectiveMavenRepositoriesTable"",""displayName"":""Effective Maven repositories"",""description"":""Table showing which Maven repositories were used in dependency resolution for this POM."",""columns"":[{""name"":""pomPath"",""type"":""String"",""displayName"":""POM path"",""description"":""The path to the POM file.""},{""name"":""repositoryUri"",""type"":""String"",""displayName"":""Repository URI"",""description"":""The URI of the Maven repository.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.FindDependency,Find Maven dependency,"Finds first-order dependency uses, i.e. dependencies that are defined directly in a project.",1,,,Search,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""3.0.0""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.FindManagedDependency,Find Maven dependency management entry,"Finds first-order dependency management entries, i.e. dependencies that are defined directly in a project.",1,,,Search,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""com.google.guava"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. Supports glob."",""example"":""guava"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""An exact version number or node-style semver selector used to select the version number."",""example"":""3.0.0""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.FindMavenProject,Find Maven projects,Maven projects are `pom.xml` files with a `MavenResolutionResult` marker.,1,,,Search,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.FindMavenSettings,Find effective maven settings,List the effective maven settings file for the current project.,1,,,Search,Maven,"[{""name"":""existenceCheckOnly"",""type"":""Boolean"",""displayName"":""Existence check only"",""description"":""Only record that a maven settings file exists; do not include its contents.""}]","[{""name"":""org.openrewrite.maven.table.EffectiveMavenSettings"",""displayName"":""Effective maven settings"",""description"":""The maven settings file used by each pom."",""columns"":[{""name"":""pom"",""type"":""String"",""displayName"":""POM"",""description"":""The location of the `pom.xml`.""},{""name"":""mavenSettings"",""type"":""String"",""displayName"":""Maven settings"",""description"":""Effective maven settings.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.FindPlugin,Find Maven plugin,Finds a Maven plugin within a pom.xml.,1,,,Search,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""rewrite-maven-plugin"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.FindProperties,Find Maven project properties,Finds the specified Maven project properties within a pom.xml.,1,,,Search,Maven,"[{""name"":""propertyPattern"",""type"":""String"",""displayName"":""Property pattern"",""description"":""Regular expression pattern used to match property tag names."",""example"":""guava.*"",""required"":true},{""name"":""valuePattern"",""type"":""String"",""displayName"":""Value pattern"",""description"":""Regular expression pattern used to match property values."",""example"":""28.*""}]","[{""name"":""org.openrewrite.maven.table.MavenProperties"",""displayName"":""Maven properties"",""description"":""Property and value."",""columns"":[{""name"":""property"",""type"":""String"",""displayName"":""Property"",""description"":""The Maven property that was found.""},{""name"":""value"",""type"":""String"",""displayName"":""Value"",""description"":""The value associated with the property.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.FindRepositoryOrder,Maven repository order,Determine the order in which dependencies will be resolved for each `pom.xml` based on its defined repositories and effective `settings.xml`.,1,,,Search,Maven,,"[{""name"":""org.openrewrite.maven.table.MavenRepositoryOrder"",""displayName"":""Maven repository order"",""description"":""The order in which dependencies will be resolved for each `pom.xml` based on its defined repositories and effective `settings.xml`."",""columns"":[{""name"":""id"",""type"":""String"",""displayName"":""Repository ID"",""description"":""The ID of the repository. Note that projects may define the same physical repository with different IDs.""},{""name"":""uri"",""type"":""String"",""displayName"":""Repository URI"",""description"":""The URI of the repository.""},{""name"":""knownToExist"",""type"":""boolean"",""displayName"":""Known to exist"",""description"":""If the repository is provably reachable. If false, does not guarantee that the repository does not exist.""},{""name"":""rank"",""type"":""int"",""displayName"":""Rank"",""description"":""The index order of this repository in the repository list.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.FindScm,Find SCM tag,Finds any `` tag directly inside the `` root of a Maven pom.xml file.,1,,,Search,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.ParentPomInsight,Maven parent insight,Find Maven parents matching a `groupId` and `artifactId`.,1,,,Search,Maven,"[{""name"":""groupIdPattern"",""type"":""String"",""displayName"":""Group pattern"",""description"":""Group glob pattern used to match dependencies."",""example"":""org.springframework.boot"",""required"":true},{""name"":""artifactIdPattern"",""type"":""String"",""displayName"":""Artifact pattern"",""description"":""Artifact glob pattern used to match dependencies."",""example"":""spring-boot-starter-*"",""required"":true},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used.All versions are searched by default."",""example"":""1.x""},{""name"":""recursive"",""type"":""Boolean"",""displayName"":""Recursive"",""description"":""Whether to search recursively through the parents. True by default.""}]","[{""name"":""org.openrewrite.maven.table.ParentPomsInUse"",""displayName"":""Maven parent POMs in use"",""description"":""Projects, GAVs and relativePaths for Maven parent POMs in use."",""columns"":[{""name"":""projectArtifactId"",""type"":""String"",""displayName"":""Project artifactId"",""description"":""The artifactId of the project that contains the parent.""},{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a parent coordinate `org.springframework.boot`.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a parent coordinate `spring-boot-starter-*`.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The resolved version.""},{""name"":""relativePath"",""type"":""String"",""displayName"":""Relative path"",""description"":""The relative path to the parent.""}]}]"
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.ModuleHasDependency,Module has dependency,"Searches for Maven modules that have a dependency matching the specified groupId and artifactId. Places a `SearchResult` marker on all sources within a module with a matching dependency. This recipe is intended to be used as a precondition for other recipes. For example this could be used to limit the application of a spring boot migration to only projects that use spring-boot-starter, limiting unnecessary upgrading. If the search result you want is instead just the build.gradle(.kts) file applying the plugin, use the `FindDependency` recipe instead.",1,,,Search,Maven,"[{""name"":""groupIdPattern"",""type"":""String"",""displayName"":""Group pattern"",""description"":""Group glob pattern used to match dependencies."",""example"":""com.fasterxml.jackson.module"",""required"":true},{""name"":""artifactIdPattern"",""type"":""String"",""displayName"":""Artifact pattern"",""description"":""Artifact glob pattern used to match dependencies."",""example"":""jackson-module-*"",""required"":true},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Match dependencies with the specified scope. All scopes are searched by default."",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided"",""system""]},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""Match only dependencies with the specified version. Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used.All versions are searched by default."",""example"":""1.x""},{""name"":""onlyDirect"",""type"":""Boolean"",""displayName"":""Only direct"",""description"":""If enabled, transitive dependencies will not be considered. All dependencies are searched by default."",""example"":""true""}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.search.ModuleHasPlugin,Module has plugin,"Searches for Maven modules that have a plugin matching the specified groupId and artifactId. Places a `SearchResult` marker on all sources within a module with a matching plugin. This recipe is intended to be used as a precondition for other recipes. For example this could be used to limit the application of a spring boot migration to only projects that apply the spring boot plugin, limiting unnecessary upgrading. If the search result you want is instead just the build.gradle(.kts) file applying the plugin, use the `FindPlugins` recipe instead.",1,,,Search,Maven,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group"",""description"":""The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""org.openrewrite.maven"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact"",""description"":""The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'."",""example"":""rewrite-maven-plugin"",""required"":true}]",
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.security.UseHttpsForRepositories,Use HTTPS for repositories,Use HTTPS for repository URLs.,1,,,Security,Maven,,
+maven,org.openrewrite:rewrite-maven,org.openrewrite.maven.utilities.PrintMavenAsDot,Print Maven dependency hierarchy in DOT format,The DOT language format is specified [here](https://graphviz.org/doc/info/lang.html).,1,,,Utilities,Maven,,
diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java
index 75311263628..9c247e7a717 100644
--- a/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java
+++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java
@@ -15,11 +15,13 @@
*/
package org.openrewrite.maven;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;
+import static org.openrewrite.java.Assertions.mavenProject;
import static org.openrewrite.maven.Assertions.pomXml;
class AddAnnotationProcessorTest implements RewriteTest {
@@ -44,7 +46,7 @@ void addAnnotationProcessor() {
com.mycompany.appmy-app1
-
+
@@ -72,7 +74,7 @@ void addAnnotationProcessor() {
com.mycompany.appmy-app1
-
+
@@ -113,7 +115,7 @@ void shouldUpdateProcessorVersionAlreadyPresent() {
com.mycompany.appmy-app1
-
+
@@ -147,7 +149,7 @@ void shouldUpdateProcessorVersionAlreadyPresent() {
com.mycompany.appmy-app1
-
+
@@ -275,4 +277,1601 @@ void addAnnotationWithSameVersionAsMavenProperty() {
)
);
}
+
+ @Nested
+ class SingleModuleProject {
+
+ @Test
+ void addToExistingPluginInBuildPlugins() {
+ // Single-module: plugin exists in build/plugins with annotationProcessorPaths
+ // Expected: adds new path to build/plugins
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ my-app
+ 1
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+
+
+
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ my-app
+ 1
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+ """
+ )
+ );
+ }
+
+ @Test
+ void createPluginWhenOnlyInPluginManagement() {
+ // Single-module: plugin only exists in pluginManagement
+ // Expected: creates new plugin in build/plugins (pluginManagement unchanged)
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ my-app
+ 1
+
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ my-app
+ 1
+
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+ """
+ )
+ );
+ }
+
+ @Test
+ void createPluginWhenMissing() {
+ // Single-module: no maven-compiler-plugin anywhere
+ // Expected: creates plugin in build/plugins with only annotationProcessorPaths
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ my-app
+ 1
+
+
+
+
+ maven-surefire-plugin
+
+
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ my-app
+ 1
+
+
+
+
+ maven-surefire-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+ """
+ )
+ );
+ }
+
+ @Test
+ void createBuildSectionWhenMissing() {
+ // Single-module: no build section at all
+ // Expected: creates entire build/plugins/plugin structure
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ my-app
+ 1
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ my-app
+ 1
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+ """
+ )
+ );
+ }
+
+ @Test
+ void addToBuildPluginsWhenInBothLocations() {
+ // Single-module: plugin exists in both build/plugins AND pluginManagement
+ // Expected: adds to build/plugins only (consistent with single-module behavior)
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ my-app
+ 1
+
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+
+
+
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ my-app
+ 1
+
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+ """
+ )
+ );
+ }
+ }
+
+ @Nested
+ class CombinedAggregatorAndParent {
+
+ @Test
+ void addToExistingPluginInPluginManagement() {
+ // Multi-module: root pom has plugin in pluginManagement with annotationProcessorPaths
+ // Expected: adds path to pluginManagement, child unchanged
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+
+
+
+
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ ),
+ mavenProject("child",
+ pomXml(
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+
+ child
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void createInPluginManagementWhenOnlyInBuildPlugins() {
+ // Multi-module: root has plugin only in build/plugins
+ // Expected: creates in pluginManagement, leaves build/plugins alone
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ ),
+ mavenProject("child",
+ pomXml(
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+
+ child
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void createPluginManagementWhenMissing() {
+ // Multi-module: root has no maven-compiler-plugin anywhere
+ // Expected: creates pluginManagement with the plugin
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ ),
+ mavenProject("child",
+ pomXml(
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+
+ child
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void leaveChildPluginsUntouched() {
+ // Multi-module: child module has its own maven-compiler-plugin
+ // Expected: child remains untouched, only root gets pluginManagement entry
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ ),
+ mavenProject("child",
+ pomXml(
+ // Child has its own plugin config - should NOT be modified
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+
+ child
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+
+
+
+
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void childIsUntouched() {
+ // Multi-module: child module has no maven-compiler-plugin
+ // Expected: child remains untouched, only root gets pluginManagement entry
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ ),
+ mavenProject("child",
+ pomXml(
+ // Child has nothing - should NOT be modified
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+
+ child
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void addOnlyToPluginManagementWhenInBothLocations() {
+ // Multi-module: root has plugin in both build/plugins and pluginManagement
+ // Expected: adds only to pluginManagement
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+
+ """
+ ),
+ mavenProject("child",
+ pomXml(
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+
+ child
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void upgradeVersionInPluginManagement() {
+ // Multi-module: root has processor with older version in pluginManagement
+ // Expected: upgrades version in pluginManagement
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.1.0
+
+
+
+
+
+
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ ),
+ mavenProject("child",
+ pomXml(
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+
+ child
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void orphanModuleGetsOwnPluginConfiguration() {
+ // Combined aggregator/parent scenario with an orphan module
+ // Root is aggregator+parent for child module
+ // module-orphan has NO parent in reactor -> should get build/plugins directly
+ rewriteRun(
+ pomXml(
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+ module-orphan
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ child
+ module-orphan
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ ),
+ mavenProject("child",
+ pomXml(
+ // Child has parent in reactor - should NOT be modified
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+
+ child
+
+ """
+ )
+ ),
+ mavenProject("module-orphan",
+ pomXml(
+ // Orphan module has NO parent in reactor -> gets build/plugins directly
+ """
+
+ 4.0.0
+ com.mycompany.app
+ module-orphan
+ 1
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ module-orphan
+ 1
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+ """
+ )
+ )
+ );
+ }
+ }
+
+ @Nested
+ class SeparatedAggregatorAndParent {
+
+ @Test
+ void addToParentNotAggregator() {
+ // Aggregator POM should NOT be modified
+ // Parent POM (separate from aggregator) should get pluginManagement entry
+ // Child modules remain unchanged
+ rewriteRun(
+ pomXml(
+ // Aggregator - should NOT be modified (no parent references to it)
+ """
+
+ 4.0.0
+ com.mycompany.app
+ aggregator
+ 1
+ pom
+
+ parent
+ module-a
+
+
+ """
+ ),
+ mavenProject("parent",
+ pomXml(
+ // Parent POM - should get pluginManagement
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ )
+ ),
+ mavenProject("module-a",
+ pomXml(
+ // Child with parent - should NOT be modified
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+ ../parent
+
+ module-a
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void aggregatorUnchangedWhenSeparateParentExists() {
+ // Explicit test that aggregator is NOT modified when children use a separate parent
+ // Even if aggregator has maven-compiler-plugin, it should NOT be modified
+ rewriteRun(
+ pomXml(
+ // Aggregator with existing compiler plugin - should NOT be modified
+ """
+
+ 4.0.0
+ com.mycompany.app
+ aggregator
+ 1
+ pom
+
+ parent
+ module-a
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+
+
+ """
+ ),
+ mavenProject("parent",
+ pomXml(
+ // Parent POM - should get pluginManagement
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ )
+ ),
+ mavenProject("module-a",
+ pomXml(
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+ ../parent
+
+ module-a
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void orphanModuleInSeparatedStructure() {
+ // Orphan module (no parent in reactor) gets build/plugins directly
+ // Parent POM also gets pluginManagement for its children
+ rewriteRun(
+ pomXml(
+ // Aggregator - should NOT be modified
+ """
+
+ 4.0.0
+ com.mycompany.app
+ aggregator
+ 1
+ pom
+
+ parent
+ module-a
+ module-orphan
+
+
+ """
+ ),
+ mavenProject("parent",
+ pomXml(
+ // Parent POM - should get pluginManagement
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ )
+ ),
+ mavenProject("module-a",
+ pomXml(
+ // Child with parent - should NOT be modified
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+ ../parent
+
+ module-a
+
+ """
+ )
+ ),
+ mavenProject("module-orphan",
+ pomXml(
+ // Orphan module (no parent in reactor) -> gets build/plugins directly
+ """
+
+ 4.0.0
+ com.mycompany.app
+ module-orphan
+ 1
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ module-orphan
+ 1
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void addToExistingPluginInSeparatedParent() {
+ // Parent POM already has maven-compiler-plugin in pluginManagement
+ // Expected: adds annotation processor path to existing config
+ rewriteRun(
+ pomXml(
+ // Aggregator - should NOT be modified
+ """
+
+ 4.0.0
+ com.mycompany.app
+ aggregator
+ 1
+ pom
+
+ parent
+ module-a
+
+
+ """
+ ),
+ mavenProject("parent",
+ pomXml(
+ // Parent POM with existing compiler plugin - should get annotation processor added
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+
+
+
+
+
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent
+ 1
+ pom
+
+
+
+
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ )
+ ),
+ mavenProject("module-a",
+ pomXml(
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent
+ 1
+ ../parent
+
+ module-a
+
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ void multipleParentsWithSharedChildren() {
+ // Complex scenario: multiple parent POMs in reactor
+ // Each parent should get pluginManagement (deduplicated)
+ rewriteRun(
+ pomXml(
+ // Aggregator - should NOT be modified
+ """
+
+ 4.0.0
+ com.mycompany.app
+ aggregator
+ 1
+ pom
+
+ parent-a
+ parent-b
+ module-a1
+ module-a2
+ module-b1
+
+
+ """
+ ),
+ mavenProject("parent-a",
+ pomXml(
+ // Parent A - should get pluginManagement
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent-a
+ 1
+ pom
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent-a
+ 1
+ pom
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ )
+ ),
+ mavenProject("parent-b",
+ pomXml(
+ // Parent B - should also get pluginManagement
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent-b
+ 1
+ pom
+
+ """,
+ """
+
+ 4.0.0
+ com.mycompany.app
+ parent-b
+ 1
+ pom
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
+
+ """
+ )
+ ),
+ mavenProject("module-a1",
+ pomXml(
+ // Child of parent-a - should NOT be modified
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent-a
+ 1
+ ../parent-a
+
+ module-a1
+
+ """
+ )
+ ),
+ mavenProject("module-a2",
+ pomXml(
+ // Child of parent-a - should NOT be modified
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent-a
+ 1
+ ../parent-a
+
+ module-a2
+
+ """
+ )
+ ),
+ mavenProject("module-b1",
+ pomXml(
+ // Child of parent-b - should NOT be modified
+ """
+
+ 4.0.0
+
+ com.mycompany.app
+ parent-b
+ 1
+ ../parent-b
+
+ module-b1
+
+ """
+ )
+ )
+ );
+ }
+ }
}
diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddManagedPluginTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddManagedPluginTest.java
new file mode 100644
index 00000000000..ebe61651e40
--- /dev/null
+++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddManagedPluginTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *