This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/maven-4.0.x by this push:
     new b8a0ae05a8 [maven-4.0.x] Backport bug fixes from master to maven-4.0.x 
(#12091)
b8a0ae05a8 is described below

commit b8a0ae05a834ca2de1fb1985884304baa83cfaaa
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue May 19 15:36:05 2026 +0200

    [maven-4.0.x] Backport bug fixes from master to maven-4.0.x (#12091)
    
    * Add Maven version to error message when rejecting model versions (#10921)
    
    Port https://github.com/apache/maven/pull/10899 to master, using 
Session.getMavenVersion() instead of reading properties file.
    
    - Modified validateModelVersion() to accept Session parameter and include 
Maven version in error messages
    - Updated error messages to be single-line and consistent with Maven's style
    - Enhanced error messages show which Maven version is rejecting the model 
version
    - Added test coverage to verify Maven version is included in error messages
    
    This helps users (especially IDE users like NetBeans) understand version 
compatibility issues more clearly.
    
    * Improve ProjectBuildingException error messages with detailed problem 
reporting (#10975)
    
    * Refactor ProjectBuildingException constructors for improved clarity
    
    * Add unit tests for ProjectBuildingException message generation
    
    * Refactor ProjectBuildingException for improved clarity and maintainability
    
    * Add unit tests for ProjectBuildingException to enhance message validation
    
    * style: Apply Spotless formatting
    
    * Refactor createMessage method to improve error counting logic in 
ProjectBuildingException
    
    * Refactor ProjectBuildingException for improved clarity and maintainability
    
    * Refactor createMessage method to streamline error message 
formattingRefactor createMessage method to streamline error message formatting
    
    * Refactor:Spotless formating
    
    * Improve dependency scope validation error messages for import scope 
(#10991)
    
    - Enhance error message when 'import' scope is used incorrectly in regular 
dependencies
    - Provide clear guidance that 'import' scope is only valid in 
<dependencyManagement> sections
    - Replace generic error message with context-aware validation
    - Update both Maven 3 (compat) and Maven 4 (impl) implementations for 
consistency
    - Update tests to verify the improved error messages
    - Fix grammar in comments (don't -> not)
    - Apply spotless formatting
    
    Before: 'dependencies.dependency.scope' must be one of [provided, compile, 
runtime, test, system] but is 'import'.
    After:  'dependencies.dependency.scope' has scope 'import'. The 'import' 
scope is only valid in <dependencyManagement> sections.
    
    This addresses the confusion reported in 
https://github.com/faktorips/faktorips.base/issues/70
    where users receive misleading error messages that suggest 'import' scope 
is never valid,
    when it's actually valid in dependency management sections with type=pom.
    
    * Fixes #10950: Throw ProjectBuildingException for reactor cycles; keep 
CycleDetectedException as cause. (#11091)
    
    Non-cycle model problems still use ProjectBuildingException(results).
    Remove misleading comment about projectId/pomFile.
    
    * Fix NullPointerException when clearing project properties
    
    Fix #11552
    
    ---------
    
    Co-authored-by: Pasan Kavinda Abeysekara 
<[email protected]>
    Co-authored-by: Arturo Bernal <[email protected]>
    Co-authored-by: copilot-swe-agent[bot] 
<[email protected]>
---
 .../model/validation/DefaultModelValidator.java    |  73 +++++++--
 .../validation/DefaultModelValidatorTest.java      |   4 +
 .../java/org/apache/maven/model/ModelTest.java     |  13 ++
 .../maven/project/DefaultProjectBuilder.java       |   9 +-
 .../maven/project/ProjectBuildingException.java    |  84 ++++++++++-
 .../project/ProjectBuildingExceptionTest.java      | 163 +++++++++++++++++++++
 .../maven/impl/model/DefaultModelValidator.java    |  97 ++++++++++--
 .../impl/model/DefaultModelValidatorTest.java      |  25 ++++
 src/mdo/java/WrapperProperties.java                |   6 +
 src/mdo/model-v3.vm                                |   6 +-
 10 files changed, 444 insertions(+), 36 deletions(-)

diff --git 
a/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
 
b/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
index 5e2d47329d..767054b9bd 100644
--- 
a/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
+++ 
b/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
@@ -785,10 +785,10 @@ private void validateEffectiveDependencies(
                             prefix, "version", problems, errOn30, Version.V20, 
d.getVersion(), d.getManagementKey(), d);
 
                     /*
-                     * TODO Extensions like Flex Mojos use custom scopes like 
"merged", "internal", "external", etc. In
-                     * order to don't break backward-compat with those, only 
warn but don't error out.
+                     * Extensions like Flex Mojos use custom scopes like 
"merged", "internal", "external", etc. In
+                     * order to not break backward-compat with those, only 
warn but don't error out.
                      */
-                    validateEnum(
+                    validateDependencyScope(
                             prefix,
                             "scope",
                             problems,
@@ -797,15 +797,11 @@ private void validateEffectiveDependencies(
                             d.getScope(),
                             d.getManagementKey(),
                             d,
-                            "provided",
-                            "compile",
-                            "runtime",
-                            "test",
-                            "system");
+                            false);
 
                     validateEffectiveModelAgainstDependency(prefix, problems, 
m, d, request);
                 } else {
-                    validateEnum(
+                    validateDependencyScope(
                             prefix,
                             "scope",
                             problems,
@@ -814,12 +810,7 @@ private void validateEffectiveDependencies(
                             d.getScope(),
                             d.getManagementKey(),
                             d,
-                            "provided",
-                            "compile",
-                            "runtime",
-                            "test",
-                            "system",
-                            "import");
+                            true);
                 }
             }
         }
@@ -1462,6 +1453,58 @@ private boolean validateEnum(
         return false;
     }
 
+    @SuppressWarnings("checkstyle:parameternumber")
+    private boolean validateDependencyScope(
+            String prefix,
+            String fieldName,
+            ModelProblemCollector problems,
+            Severity severity,
+            Version version,
+            String scope,
+            String sourceHint,
+            InputLocationTracker tracker,
+            boolean isDependencyManagement) {
+        if (scope == null || scope.length() <= 0) {
+            return true;
+        }
+
+        String[] validScopes;
+        if (isDependencyManagement) {
+            validScopes = new String[] {"provided", "compile", "runtime", 
"test", "system", "import"};
+        } else {
+            validScopes = new String[] {"provided", "compile", "runtime", 
"test", "system"};
+        }
+
+        List<String> values = Arrays.asList(validScopes);
+
+        if (values.contains(scope)) {
+            return true;
+        }
+
+        // Provide a more helpful error message for the 'import' scope
+        if ("import".equals(scope) && !isDependencyManagement) {
+            addViolation(
+                    problems,
+                    severity,
+                    version,
+                    prefix + fieldName,
+                    sourceHint,
+                    "has scope 'import'. The 'import' scope is only valid in 
<dependencyManagement> sections.",
+                    tracker);
+        } else {
+            addViolation(
+                    problems,
+                    severity,
+                    version,
+                    prefix + fieldName,
+                    sourceHint,
+                    "must be one of " + values + " but is '" + scope + "'.",
+                    tracker);
+        }
+
+        return false;
+    }
+
     @SuppressWarnings("checkstyle:parameternumber")
     private boolean validateModelVersion(
             ModelProblemCollector problems, String string, 
InputLocationTracker tracker, String... validVersions) {
diff --git 
a/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
 
b/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
index cdfa9caac1..d856b6ae94 100644
--- 
a/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
+++ 
b/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
@@ -355,6 +355,10 @@ void testBadDependencyScope() throws Exception {
         assertViolations(result, 0, 0, 2);
 
         assertTrue(result.getWarnings().get(0).contains("test:f"));
+        // Check that the import scope error message is more helpful
+        assertTrue(result.getWarnings()
+                .get(0)
+                .contains("has scope 'import'. The 'import' scope is only 
valid in <dependencyManagement> sections"));
 
         assertTrue(result.getWarnings().get(1).contains("test:g"));
     }
diff --git 
a/compat/maven-model/src/test/java/org/apache/maven/model/ModelTest.java 
b/compat/maven-model/src/test/java/org/apache/maven/model/ModelTest.java
index 2f872a7bf5..967af237c5 100644
--- a/compat/maven-model/src/test/java/org/apache/maven/model/ModelTest.java
+++ b/compat/maven-model/src/test/java/org/apache/maven/model/ModelTest.java
@@ -66,4 +66,17 @@ void testEqualsIdentity() {
     void testToStringNullSafe() {
         assertNotNull(new Model().toString());
     }
+
+    @Test
+    void testPropertiesClear() {
+        // Test for issue #11552: NullPointerException when clearing properties
+        Model model = new Model();
+        model.addProperty("key1", "value1");
+        model.addProperty("key2", "value2");
+        assertEquals(2, model.getProperties().size());
+
+        // This should not throw NullPointerException
+        model.getProperties().clear();
+        assertEquals(0, model.getProperties().size());
+    }
 }
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
 
b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
index f9c645be33..c3e4a73794 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
@@ -46,7 +46,6 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import org.apache.maven.ProjectCycleException;
 import org.apache.maven.RepositoryUtils;
 import org.apache.maven.api.ArtifactCoordinates;
 import org.apache.maven.api.Language;
@@ -504,10 +503,14 @@ List<ProjectBuildingResult> build(List<File> pomFiles, 
boolean recursive) throws
                         .findAny()
                         .orElse(null);
                 if (cycle != null) {
-                    throw new RuntimeException(new ProjectCycleException(
+                    final CycleDetectedException cde = 
(CycleDetectedException) cycle.getException();
+                    throw new ProjectBuildingException(
+                            null,
                             "The projects in the reactor contain a cyclic 
reference: " + cycle.getMessage(),
-                            (CycleDetectedException) cycle.getException()));
+                            null,
+                            cde);
                 }
+
                 throw new ProjectBuildingException(results);
             }
 
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingException.java
 
b/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingException.java
index d8b3ca5ae8..3aaee22c50 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingException.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingException.java
@@ -21,6 +21,8 @@
 import java.io.File;
 import java.util.List;
 
+import org.apache.maven.model.building.ModelProblem;
+
 /**
  */
 public class ProjectBuildingException extends Exception {
@@ -59,7 +61,7 @@ protected ProjectBuildingException(String projectId, String 
message, File pomFil
     }
 
     public ProjectBuildingException(List<ProjectBuildingResult> results) {
-        super("Some problems were encountered while processing the POMs");
+        super(createMessage(results));
         this.projectId = "";
         this.results = results;
     }
@@ -97,4 +99,84 @@ private static String createMessage(String message, String 
projectId, File pomFi
         }
         return buffer.toString();
     }
+
+    private static String createMessage(List<ProjectBuildingResult> results) {
+        if (results == null || results.isEmpty()) {
+            return "Some problems were encountered while processing the POMs";
+        }
+
+        long totalProblems = 0;
+        long errorProblems = 0;
+
+        for (ProjectBuildingResult result : results) {
+            List<ModelProblem> problems = result.getProblems();
+            totalProblems += problems.size();
+
+            for (ModelProblem problem : problems) {
+                if (problem.getSeverity() != ModelProblem.Severity.WARNING) {
+                    errorProblems++;
+                }
+            }
+        }
+
+        StringBuilder buffer = new StringBuilder(1024);
+        buffer.append(totalProblems);
+        buffer.append(totalProblems == 1 ? " problem was " : " problems were 
");
+        buffer.append("encountered while processing the POMs");
+
+        if (errorProblems > 0) {
+            buffer.append(" (")
+                    .append(errorProblems)
+                    .append(" ")
+                    .append(errorProblems > 1 ? "errors" : "error")
+                    .append(")");
+        }
+
+        buffer.append(":\n");
+
+        for (ProjectBuildingResult result : results) {
+            if (!result.getProblems().isEmpty()) {
+                String projectInfo = result.getProjectId();
+                if (projectInfo.trim().isEmpty()) {
+                    projectInfo =
+                            result.getPomFile() != null ? 
result.getPomFile().getName() : "unknown project";
+                }
+
+                buffer.append("\n[").append(projectInfo).append("]\n");
+
+                for (ModelProblem problem : result.getProblems()) {
+                    if (errorProblems > 0 && problem.getSeverity() == 
ModelProblem.Severity.WARNING) {
+                        continue;
+                    }
+
+                    buffer.append("  
[").append(problem.getSeverity()).append("] ");
+                    buffer.append(problem.getMessage());
+
+                    String location = "";
+                    if (!problem.getSource().trim().isEmpty()) {
+                        location = problem.getSource();
+                    }
+                    if (problem.getLineNumber() > 0) {
+                        if (!location.isEmpty()) {
+                            location += ", ";
+                        }
+                        location += "line " + problem.getLineNumber();
+                    }
+                    if (problem.getColumnNumber() > 0) {
+                        if (!location.isEmpty()) {
+                            location += ", ";
+                        }
+                        location += "column " + problem.getColumnNumber();
+                    }
+
+                    if (!location.isEmpty()) {
+                        buffer.append(" @ ").append(location);
+                    }
+                    buffer.append("\n");
+                }
+            }
+        }
+
+        return buffer.toString();
+    }
 }
diff --git 
a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuildingExceptionTest.java
 
b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuildingExceptionTest.java
new file mode 100644
index 0000000000..fae4355e0e
--- /dev/null
+++ 
b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuildingExceptionTest.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.maven.project;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.model.building.DefaultModelProblem;
+import org.apache.maven.model.building.ModelProblem;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test for {@link ProjectBuildingException} message generation.
+ */
+@SuppressWarnings("deprecation")
+class ProjectBuildingExceptionTest {
+
+    @Test
+    void testDetailedExceptionMessageWithMultipleProblems() {
+        List<ProjectBuildingResult> results = new ArrayList<>();
+
+        List<ModelProblem> problems1 = new ArrayList<>();
+        Collections.addAll(
+                problems1,
+                new DefaultModelProblem(
+                        "Missing required dependency",
+                        ModelProblem.Severity.ERROR,
+                        null,
+                        "pom.xml",
+                        25,
+                        10,
+                        null,
+                        null),
+                new DefaultModelProblem(
+                        "Invalid version format", ModelProblem.Severity.ERROR, 
null, "pom.xml", 30, 5, null, null));
+        DefaultProjectBuildingResult result1 =
+                new DefaultProjectBuildingResult("com.example:project1:1.0", 
new File("project1/pom.xml"), problems1);
+        results.add(result1);
+
+        List<ModelProblem> problems2 = new ArrayList<>();
+        Collections.addAll(
+                problems2,
+                new DefaultModelProblem(
+                        "Deprecated plugin usage", 
ModelProblem.Severity.WARNING, null, "pom.xml", 15, 3, null, null));
+        DefaultProjectBuildingResult result2 =
+                new DefaultProjectBuildingResult("com.example:project2:1.0", 
new File("project2/pom.xml"), problems2);
+        results.add(result2);
+
+        ProjectBuildingException exception = new 
ProjectBuildingException(results);
+        String message = exception.getMessage();
+
+        assertTrue(
+                message.contains("3 problems were encountered while processing 
the POMs (2 errors)"),
+                "Message should contain problem count and error count");
+
+        assertTrue(message.contains("[com.example:project1:1.0]"), "Message 
should contain project1 identifier");
+
+        assertTrue(message.contains("[com.example:project2:1.0]"), "Message 
should contain project2 identifier");
+
+        assertTrue(
+                message.contains("[ERROR] Missing required dependency @ 
pom.xml, line 25, column 10"),
+                "Message should contain error details with location");
+
+        assertTrue(
+                message.contains("[ERROR] Invalid version format @ pom.xml, 
line 30, column 5"),
+                "Message should contain second error details");
+
+        assertTrue(
+                !message.contains("[WARNING]") || message.contains("[WARNING] 
Deprecated plugin usage"),
+                "Warnings should be filtered when errors are present or shown 
if explicitly included");
+    }
+
+    @Test
+    void testExceptionMessageWithOnlyWarnings() {
+        List<ProjectBuildingResult> results = new ArrayList<>();
+
+        List<ModelProblem> problems = new ArrayList<>();
+        Collections.addAll(
+                problems,
+                new DefaultModelProblem(
+                        "Deprecated feature used", 
ModelProblem.Severity.WARNING, null, "pom.xml", 10, 1, null, null));
+        DefaultProjectBuildingResult result =
+                new DefaultProjectBuildingResult("com.example:project:1.0", 
new File("project/pom.xml"), problems);
+        results.add(result);
+
+        ProjectBuildingException exception = new 
ProjectBuildingException(results);
+        String message = exception.getMessage();
+
+        assertTrue(
+                message.contains("1 problem was encountered while processing 
the POMs"),
+                "Message should use singular form for single problem");
+
+        assertTrue(
+                message.contains("[WARNING] Deprecated feature used"),
+                "Message should contain warning when no errors are present");
+
+        assertTrue(
+                !message.contains("(") || !message.contains("error"),
+                "Message should not contain error count when there are no 
errors");
+    }
+
+    @Test
+    void testExceptionMessageWithEmptyResults() {
+        List<ProjectBuildingResult> results = Collections.emptyList();
+
+        ProjectBuildingException exception = new 
ProjectBuildingException(results);
+        String message = exception.getMessage();
+
+        assertEquals(
+                "Some problems were encountered while processing the POMs",
+                message,
+                "Empty results should fall back to generic message");
+    }
+
+    @Test
+    void testExceptionMessageWithNullResults() {
+        ProjectBuildingException exception = new 
ProjectBuildingException((List<ProjectBuildingResult>) null);
+        String message = exception.getMessage();
+
+        assertEquals(
+                "Some problems were encountered while processing the POMs",
+                message,
+                "Null results should fall back to generic message");
+    }
+
+    @Test
+    void testExceptionMessageWithUnknownProject() {
+        List<ProjectBuildingResult> results = new ArrayList<>();
+
+        List<ModelProblem> problems = new ArrayList<>();
+        Collections.addAll(
+                problems,
+                new DefaultModelProblem("Some error", 
ModelProblem.Severity.ERROR, null, "unknown", 1, 1, null, null));
+        DefaultProjectBuildingResult result = new 
DefaultProjectBuildingResult(null, null, problems);
+        results.add(result);
+
+        ProjectBuildingException exception = new 
ProjectBuildingException(results);
+        String message = exception.getMessage();
+
+        assertTrue(message.contains("[unknown project]"), "Message should 
handle unknown project gracefully");
+    }
+}
diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java
index 37db33b4e4..44b3353336 100644
--- 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java
@@ -372,7 +372,7 @@ public void validateFileModel(Session s, Model m, int 
validationLevel, ModelProb
         } else if (validationLevel >= 
ModelValidator.VALIDATION_LEVEL_MAVEN_2_0) {
             validateStringNotEmpty("modelVersion", problems, Severity.ERROR, 
Version.V20, m.getModelVersion(), m);
 
-            validateModelVersion(problems, m.getModelVersion(), m, 
ModelBuilder.KNOWN_MODEL_VERSIONS);
+            validateModelVersion(s, problems, m.getModelVersion(), m, 
ModelBuilder.KNOWN_MODEL_VERSIONS);
 
             Set<String> modules = new HashSet<>();
             for (int i = 0, n = m.getModules().size(); i < n; i++) {
@@ -1238,12 +1238,12 @@ private void validateEffectiveDependencies(
                             d);
 
                     /*
-                     * TODO Extensions like Flex Mojos use custom scopes like 
"merged", "internal", "external", etc. In
-                     * order to don't break backward-compat with those, only 
warn but don't error out.
+                     * Extensions like Flex Mojos use custom scopes like 
"merged", "internal", "external", etc. In
+                     * order to not break backward-compat with those, only 
warn but don't error out.
                      */
                     ScopeManager scopeManager =
                             
InternalSession.from(s).getSession().getScopeManager();
-                    validateEnum(
+                    validateDependencyScope(
                             prefix,
                             "scope",
                             problems,
@@ -1255,7 +1255,8 @@ private void validateEffectiveDependencies(
                             scopeManager.getDependencyScopeUniverse().stream()
                                     .map(DependencyScope::getId)
                                     .distinct()
-                                    .toArray(String[]::new));
+                                    .toArray(String[]::new),
+                            false);
 
                     validateEffectiveModelAgainstDependency(prefix, problems, 
m, d);
                 } else {
@@ -1265,7 +1266,7 @@ private void validateEffectiveDependencies(
                             .map(DependencyScope::getId)
                             .collect(Collectors.toCollection(HashSet::new));
                     scopes.add("import");
-                    validateEnum(
+                    validateDependencyScope(
                             prefix,
                             "scope",
                             problems,
@@ -1274,7 +1275,8 @@ private void validateEffectiveDependencies(
                             d.getScope(),
                             SourceHint.dependencyManagementKey(d),
                             d,
-                            scopes.toArray(new String[0]));
+                            scopes.toArray(new String[0]),
+                            true);
                 }
             }
         }
@@ -2023,21 +2025,71 @@ private boolean validateEnum(
         return false;
     }
 
+    @SuppressWarnings("checkstyle:parameternumber")
+    private boolean validateDependencyScope(
+            String prefix,
+            String fieldName,
+            ModelProblemCollector problems,
+            Severity severity,
+            Version version,
+            String scope,
+            @Nullable SourceHint sourceHint,
+            InputLocationTracker tracker,
+            String[] validScopes,
+            boolean isDependencyManagement) {
+        if (scope == null || scope.isEmpty()) {
+            return true;
+        }
+
+        List<String> values = Arrays.asList(validScopes);
+
+        if (values.contains(scope)) {
+            return true;
+        }
+
+        // Provide a more helpful error message for the 'import' scope
+        if ("import".equals(scope) && !isDependencyManagement) {
+            addViolation(
+                    problems,
+                    severity,
+                    version,
+                    prefix + fieldName,
+                    sourceHint,
+                    "has scope 'import'. The 'import' scope is only valid in 
<dependencyManagement> sections.",
+                    tracker);
+        } else {
+            addViolation(
+                    problems,
+                    severity,
+                    version,
+                    prefix + fieldName,
+                    sourceHint,
+                    "must be one of " + values + " but is '" + scope + "'.",
+                    tracker);
+        }
+
+        return false;
+    }
+
     @SuppressWarnings("checkstyle:parameternumber")
     private boolean validateModelVersion(
-            ModelProblemCollector problems, String string, 
InputLocationTracker tracker, List<String> validVersions) {
-        if (string == null || string.isEmpty()) {
+            Session session,
+            ModelProblemCollector problems,
+            String requestedModel,
+            InputLocationTracker tracker,
+            List<String> validVersions) {
+        if (requestedModel == null || requestedModel.isEmpty()) {
             return true;
         }
 
-        if (validVersions.contains(string)) {
+        if (validVersions.contains(requestedModel)) {
             return true;
         }
 
         boolean newerThanAll = true;
         boolean olderThanAll = true;
         for (String validValue : validVersions) {
-            final int comparison = compareModelVersions(validValue, string);
+            final int comparison = compareModelVersions(validValue, 
requestedModel);
             newerThanAll = newerThanAll && comparison < 0;
             olderThanAll = olderThanAll && comparison > 0;
         }
@@ -2049,8 +2101,10 @@ private boolean validateModelVersion(
                     Version.V20,
                     "modelVersion",
                     null,
-                    "of '" + string + "' is newer than the versions supported 
by this version of Maven: "
-                            + validVersions + ". Building this project 
requires a newer version of Maven.",
+                    "of '" + requestedModel + "' is newer than the versions 
supported by this Maven version ("
+                            + getMavenVersionString(session)
+                            + "). Supported modelVersions are: " + 
validVersions
+                            + ". Building this project requires a newer 
version of Maven.",
                     tracker);
 
         } else if (olderThanAll) {
@@ -2061,8 +2115,10 @@ private boolean validateModelVersion(
                     Version.V20,
                     "modelVersion",
                     null,
-                    "of '" + string + "' is older than the versions supported 
by this version of Maven: "
-                            + validVersions + ". Building this project 
requires an older version of Maven.",
+                    "of '" + requestedModel + "' is older than the versions 
supported by this Maven version ("
+                            + getMavenVersionString(session)
+                            + "). Supported modelVersions are: " + 
validVersions
+                            + ". Building this project requires an older 
version of Maven.",
                     tracker);
 
         } else {
@@ -2072,13 +2128,22 @@ private boolean validateModelVersion(
                     Version.V20,
                     "modelVersion",
                     null,
-                    "must be one of " + validVersions + " but is '" + string + 
"'.",
+                    "must be one of " + validVersions + " but is '" + 
requestedModel + "'.",
                     tracker);
         }
 
         return false;
     }
 
+    private String getMavenVersionString(Session session) {
+        try {
+            return session.getMavenVersion().toString();
+        } catch (Exception e) {
+            // Fallback for test contexts where RuntimeInformation might not 
be available
+            return "unknown";
+        }
+    }
+
     /**
      * Compares two model versions.
      *
diff --git 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java
 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java
index 6e6f1d7b49..36ba2745a0 100644
--- 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java
+++ 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java
@@ -23,6 +23,7 @@
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.maven.api.Version;
 import org.apache.maven.api.model.Model;
 import org.apache.maven.api.services.model.ModelValidator;
 import org.apache.maven.impl.InternalSession;
@@ -130,6 +131,11 @@ void setUp() throws Exception {
         when(repoSession.getScopeManager()).thenReturn(scopeManager);
         session = mock(InternalSession.class);
         when(session.getSession()).thenReturn(repoSession);
+
+        // Mock Maven version for error message testing
+        Version mavenVersion = mock(Version.class);
+        when(mavenVersion.toString()).thenReturn("4.0.0-test");
+        when(session.getMavenVersion()).thenReturn(mavenVersion);
     }
 
     @AfterEach
@@ -170,6 +176,21 @@ void testModelVersionMessage() throws Exception {
         assertTrue(result.getErrors().get(0).contains("'modelVersion' must be 
one of"));
     }
 
+    @Test
+    void testModelVersionMessageIncludesMavenVersion() throws Exception {
+        SimpleProblemCollector result = validateFile("bad-modelVersion.xml");
+
+        assertViolations(result, 1, 0, 0);
+
+        String errorMessage = result.getFatals().get(0);
+        assertTrue(errorMessage.contains("modelVersion"));
+        // Should include Maven version (either "4.0.0-test" from mock or 
"unknown" as fallback)
+        assertTrue(
+                errorMessage.contains("4.0.0-test") || 
errorMessage.contains("unknown"),
+                "Error message should include Maven version: " + errorMessage);
+        assertTrue(errorMessage.contains("newer than the versions supported by 
this Maven version"));
+    }
+
     @Test
     void testMissingArtifactId() throws Exception {
         SimpleProblemCollector result = validate("missing-artifactId-pom.xml");
@@ -391,6 +412,10 @@ void testBadDependencyScope() throws Exception {
         assertViolations(result, 0, 0, 2);
 
         assertTrue(result.getWarnings().get(0).contains("groupId='test', 
artifactId='f'"));
+        // Check that the import scope error message is more helpful
+        assertTrue(result.getWarnings()
+                .get(0)
+                .contains("has scope 'import'. The 'import' scope is only 
valid in <dependencyManagement> sections"));
 
         assertTrue(result.getWarnings().get(1).contains("groupId='test', 
artifactId='g'"));
     }
diff --git a/src/mdo/java/WrapperProperties.java 
b/src/mdo/java/WrapperProperties.java
index f285ec0666..2c1f26ebdd 100644
--- a/src/mdo/java/WrapperProperties.java
+++ b/src/mdo/java/WrapperProperties.java
@@ -378,6 +378,12 @@ public synchronized Object remove(Object key) {
             return super.remove(key);
         }
 
+        @Override
+        public synchronized void clear() {
+            keyOrder.clear();
+            super.clear();
+        }
+
         @Override
         public synchronized void forEach(BiConsumer<? super Object, ? super 
Object> action) {
             entrySet().forEach(e -> action.accept(e.getKey(), e.getValue()));
diff --git a/src/mdo/model-v3.vm b/src/mdo/model-v3.vm
index a31196bf8c..851a74e085 100644
--- a/src/mdo/model-v3.vm
+++ b/src/mdo/model-v3.vm
@@ -204,7 +204,11 @@ public class ${class.name}
         }
       #elseif( $field.type == "java.util.Properties" )
         LinkedHashMap<String, String> map = new LinkedHashMap<>();
-        ${field.name}.forEach((key, value) -> map.put(key.toString(), 
value.toString()));
+        ${field.name}.forEach((key, value) -> {
+            if (value != null) {
+                map.put(key.toString(), value.toString());
+            }
+        });
         if (!Objects.equals(map, getDelegate().get${cap}())) {
             update(getDelegate().with${cap}(map));
         }


Reply via email to