diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java
index cdcb385a7b5d..118ba1ac1dbd 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java
@@ -45,4 +45,21 @@ Model importManagement(
List extends DependencyManagement> sources,
ModelBuilderRequest request,
ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant of {@link #importManagement}.
+ *
+ * @since 4.0.0
+ */
+ default void importManagement(
+ Model.Builder builder,
+ List extends DependencyManagement> sources,
+ ModelBuilderRequest request,
+ ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = importManagement(built, sources, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java
index fb91b232cf60..fc29dc218e58 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java
@@ -38,4 +38,24 @@ public interface DependencyManagementInjector {
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant that operates on a {@link Model.Builder} directly,
+ * avoiding an intermediate {@code Model.build()} between pipeline stages.
+ *
+ * The default implementation bridges to {@link #injectManagement(Model, ModelBuilderRequest, ModelProblemCollector)}
+ * by building the model, processing it, and resetting the builder to the result.
+ *
+ * @param builder The model builder to modify in place, must not be {@code null}.
+ * @param request The model building request, must not be {@code null}.
+ * @param problems The container used to collect problems, must not be {@code null}.
+ * @since 4.0.0
+ */
+ default void injectManagement(Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = injectManagement(built, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java
index 59b24f370d04..3fdb1d3a6e81 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java
@@ -42,4 +42,18 @@ public interface InheritanceAssembler {
*/
Model assembleModelInheritance(
Model child, Model parent, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant that merges parent values into the child builder directly.
+ *
+ * @since 4.0.0
+ */
+ default void assembleModelInheritance(
+ Model.Builder childBuilder, Model parent, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = childBuilder.build();
+ Model result = assembleModelInheritance(built, parent, request, problems);
+ if (result != built) {
+ childBuilder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java
index c24a8a4d7f1e..4d678b21b4d3 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java
@@ -51,4 +51,21 @@ Model interpolateModel(
@Nullable Path projectDir,
@Nonnull ModelBuilderRequest request,
@Nonnull ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant of {@link #interpolateModel}.
+ *
+ * @since 4.0.0
+ */
+ default void interpolateModel(
+ @Nonnull Model.Builder builder,
+ @Nullable Path projectDir,
+ @Nonnull ModelBuilderRequest request,
+ @Nonnull ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = interpolateModel(built, projectDir, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java
index 50225fd417a5..a8b19a4cd9cb 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java
@@ -48,4 +48,31 @@ public interface ModelNormalizer {
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectDefaultValues(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant of {@link #mergeDuplicates}.
+ *
+ * @since 4.0.0
+ */
+ default void mergeDuplicates(Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = mergeDuplicates(built, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
+
+ /**
+ * Builder-accepting variant of {@link #injectDefaultValues}.
+ *
+ * @since 4.0.0
+ */
+ default void injectDefaultValues(
+ Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = injectDefaultValues(built, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java
index c2ec4ce522ac..eee49ecf486e 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java
@@ -40,4 +40,17 @@ public interface ModelPathTranslator {
* @since 4.0.0
*/
Model alignToBaseDirectory(Model model, Path basedir, ModelBuilderRequest request);
+
+ /**
+ * Builder-accepting variant of {@link #alignToBaseDirectory}.
+ *
+ * @since 4.0.0
+ */
+ default void alignToBaseDirectory(Model.Builder builder, Path basedir, ModelBuilderRequest request) {
+ Model built = builder.build();
+ Model result = alignToBaseDirectory(built, basedir, request);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java
index a216b99d7a86..f89cf80e9c1c 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java
@@ -36,4 +36,17 @@ public interface ModelUrlNormalizer {
* @param request The model building request that holds further settings, must not be {@code null}.
*/
Model normalize(Model model, ModelBuilderRequest request);
+
+ /**
+ * Builder-accepting variant of {@link #normalize}.
+ *
+ * @since 4.0.0
+ */
+ default void normalize(Model.Builder builder, ModelBuilderRequest request) {
+ Model built = builder.build();
+ Model result = normalize(built, request);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java
index bdd249489df0..a5f15cb111ea 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java
@@ -37,4 +37,18 @@ public interface PluginConfigurationExpander {
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model expandPluginConfiguration(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant of {@link #expandPluginConfiguration}.
+ *
+ * @since 4.0.0
+ */
+ default void expandPluginConfiguration(
+ Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = expandPluginConfiguration(built, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java
index 36d7f7e19f86..a2e39c63ca40 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java
@@ -38,4 +38,17 @@ public interface PluginManagementInjector {
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant of {@link #injectManagement}.
+ *
+ * @since 4.0.0
+ */
+ default void injectManagement(Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = injectManagement(built, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java
index 7361318f9801..256ecabdbf17 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java
@@ -57,4 +57,21 @@ default Model injectProfile(
*/
Model injectProfiles(
Model model, List profiles, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant that injects profile values into the model builder directly.
+ *
+ * @since 4.0.0
+ */
+ default void injectProfiles(
+ Model.Builder builder,
+ List profiles,
+ ModelBuilderRequest request,
+ ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = injectProfiles(built, profiles, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java b/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java
index 640579cb8871..b5b785c7cb7f 100644
--- a/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java
+++ b/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java
@@ -40,6 +40,10 @@ class FileToRawModelMergerTest {
void testOverriddenMergeMethods() {
List methodNames = Stream.of(MavenMerger.class.getDeclaredMethods())
.filter(m -> m.getName().startsWith("merge"))
+ // Exclude *ToBuilder variants and void methods whose first parameter
+ // is a Builder — only the object-returning merge methods need overriding
+ .filter(m -> !m.getName().endsWith("ToBuilder"))
+ .filter(m -> !m.getParameterTypes()[0].getSimpleName().equals("Builder"))
.filter(m -> {
String baseName = m.getName().substring(5 /* merge */);
String entity = baseName.substring(baseName.indexOf('_') + 1);
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultPluginConfigurationExpander.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultPluginConfigurationExpander.java
index fc159b8ccd2a..264d01d35642 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultPluginConfigurationExpander.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultPluginConfigurationExpander.java
@@ -44,6 +44,30 @@
@Singleton
public class DefaultPluginConfigurationExpander implements PluginConfigurationExpander {
+ @Override
+ public void expandPluginConfiguration(
+ Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+
+ // Use builder getters instead of builder.build() to avoid materializing
+ // all model-object lists (especially dependencies) just to read Build/Reporting
+ Build build = builder.getBuild();
+ if (build != null) {
+ Build newBuild = build.withPlugins(expandPlugin(build.getPlugins()));
+ PluginManagement pluginManagement = newBuild.getPluginManagement();
+ if (pluginManagement != null) {
+ newBuild = newBuild.withPluginManagement(
+ pluginManagement.withPlugins(expandPlugin(pluginManagement.getPlugins())));
+ }
+ if (newBuild != build) {
+ builder.build(newBuild);
+ }
+ }
+ Reporting reporting = builder.getReporting();
+ if (reporting != null) {
+ expandReport(reporting.getPlugins());
+ }
+ }
+
@Override
public Model expandPluginConfiguration(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
Build build = model.getBuild();
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java
index 45de07f83a3c..828f8568980c 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java
@@ -167,6 +167,8 @@ static Dependency updateWithImportedFrom(Dependency dependency, DependencyManage
if (dependencySource == null
|| bomSource == null
|| Objects.equals(dependencySource.getModelId(), bomSource.getModelId())) {
+ // Use forceCopy=true since we only set importedFrom (no field changes that would
+ // trigger copy-on-write), and build immediately as we need the immutable result.
return Dependency.newBuilder(dependency, true)
.importedFrom(bomLocation)
.build();
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementInjector.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementInjector.java
index d5c7d5d3e1b1..d7015c93ab36 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementInjector.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementInjector.java
@@ -45,6 +45,20 @@ public class DefaultDependencyManagementInjector implements DependencyManagement
private ManagementModelMerger merger = new ManagementModelMerger();
+ @Override
+ public void injectManagement(Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ // Use builder getters instead of builder.build() to avoid materializing
+ // all model-object lists just to read Dependencies and DependencyManagement
+ DependencyManagement depMgmt = builder.getDependencyManagement();
+ if (depMgmt != null) {
+ List deps = builder.getBuiltDependencies();
+ List merged = merger.computeMergedDependencies(deps, depMgmt);
+ if (merged != null) {
+ builder.dependencies(merged);
+ }
+ }
+ }
+
@Override
public Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
return merger.mergeManagedDependencies(model);
@@ -55,41 +69,69 @@ public Model injectManagement(Model model, ModelBuilderRequest request, ModelPro
*/
protected static class ManagementModelMerger extends MavenModelMerger {
- public Model mergeManagedDependencies(Model model) {
+ /**
+ * Computes the merged dependency list, or returns {@code null} if no dependencies were modified.
+ */
+ List computeMergedDependencies(Model model) {
DependencyManagement dependencyManagement = model.getDependencyManagement();
if (dependencyManagement != null) {
- Map