This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push:
new 71bd10f28c Fail on legacy config in modular projects (#11702)
71bd10f28c is described below
commit 71bd10f28c50a915a68cf04a18fe083655a6a833
Author: Gerd Aschemann <[email protected]>
AuthorDate: Fri Feb 20 14:03:13 2026 +0100
Fail on legacy config in modular projects (#11702)
In modular projects, legacy directories and resources that would be
silently ignored now trigger an ERROR instead of WARNING:
- Explicit <sourceDirectory>/<testSourceDirectory> differing from defaults
- Default src/main/java or src/test/java existing on filesystem
- Explicit <resources>/<testResources> differing from Super POM defaults
This prevents silent loss of user-configured sources/resources.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
---
.../maven/project/DefaultProjectBuilder.java | 188 ++++++----
.../maven/project/SourceHandlingContext.java | 50 ++-
.../apache/maven/project/ProjectBuilderTest.java | 381 +++++++++++++++++----
.../classic-sources-with-explicit-legacy/pom.xml | 30 ++
.../projects/project-builder/mixed-sources/pom.xml | 41 ---
.../modular-java-with-explicit-source-dir/pom.xml | 33 ++
.../pom.xml | 34 ++
.../pom.xml | 42 +++
.../src/main/java/.gitkeep | 0
.../src/test/java/.gitkeep | 0
.../modular-with-physical-legacy/pom.xml | 41 +++
.../src/main/java/.gitkeep | 0
.../src/test/java/.gitkeep | 0
.../pom.xml | 42 +++
.../non-modular-resources-only/pom.xml | 38 ++
15 files changed, 746 insertions(+), 174 deletions(-)
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 9de2ca1348..7b13a9c12a 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
@@ -692,49 +692,105 @@ private void initProject(MavenProject project,
ModelBuilderResult result) {
}
/*
- * `sourceDirectory`, `testSourceDirectory` and
`scriptSourceDirectory`
- * are ignored if the POM file contains at least one enabled
<source> element
- * for the corresponding scope and language. This rule exists
because
- * Maven provides default values for those elements which may
conflict
- * with user's configuration.
- *
- * Additionally, for modular projects, legacy directories are
unconditionally
- * ignored because it is not clear how to dispatch their
content between
- * different modules. A warning is emitted if these properties
are explicitly set.
- */
- if (!sourceContext.hasSources(Language.SCRIPT,
ProjectScope.MAIN)) {
+ Source directory handling depends on project type and
<sources> configuration:
+
+ 1. CLASSIC projects (no <sources>):
+ - All legacy directories are used
+
+ 2. MODULAR projects (have <module> in <sources>):
+ - ALL legacy directories cause the build to fail (cannot
dispatch
+ between modules)
+ - The build also fails if default directories
(src/main/java)
+ physically exist on the filesystem
+
+ 3. NON-MODULAR projects with <sources>:
+ - Explicit legacy directories (differ from default)
always cause
+ the build to fail
+ - Legacy directories for scopes where <sources> defines
Java are ignored
+ - Legacy directories for scopes where <sources> has no
Java serve as
+ implicit fallback (only if they match the default,
e.g., inherited)
+ - This allows incremental adoption (e.g., custom
resources + default Java)
+ */
+ if (sources.isEmpty()) {
+ // Classic fallback: no <sources> configured, use legacy
directories
project.addScriptSourceRoot(build.getScriptSourceDirectory());
- }
- if (isModularProject) {
- // Modular projects: unconditionally ignore legacy
directories, warn if explicitly set
- warnIfExplicitLegacyDirectory(
- build.getSourceDirectory(),
- baseDir.resolve("src/main/java"),
- "<sourceDirectory>",
- project.getId(),
- result);
- warnIfExplicitLegacyDirectory(
- build.getTestSourceDirectory(),
- baseDir.resolve("src/test/java"),
- "<testSourceDirectory>",
- project.getId(),
- result);
+ project.addCompileSourceRoot(build.getSourceDirectory());
+
project.addTestCompileSourceRoot(build.getTestSourceDirectory());
+ // Handle resources using legacy configuration
+
sourceContext.handleResourceConfiguration(ProjectScope.MAIN);
+
sourceContext.handleResourceConfiguration(ProjectScope.TEST);
} else {
- // Classic projects: use legacy directories if no sources
defined in <sources>
- if (!sourceContext.hasSources(Language.JAVA_FAMILY,
ProjectScope.MAIN)) {
-
project.addCompileSourceRoot(build.getSourceDirectory());
+ // Add script source root if no <sources lang="script">
configured
+ if (!sourceContext.hasSources(Language.SCRIPT,
ProjectScope.MAIN)) {
+
project.addScriptSourceRoot(build.getScriptSourceDirectory());
}
- if (!sourceContext.hasSources(Language.JAVA_FAMILY,
ProjectScope.TEST)) {
-
project.addTestCompileSourceRoot(build.getTestSourceDirectory());
+
+ if (isModularProject) {
+ // Modular: reject ALL legacy directory configurations
+ failIfLegacyDirectoryPresent(
+ build.getSourceDirectory(),
+ baseDir.resolve("src/main/java"),
+ "<sourceDirectory>",
+ project.getId(),
+ result,
+ true); // check physical presence
+ failIfLegacyDirectoryPresent(
+ build.getTestSourceDirectory(),
+ baseDir.resolve("src/test/java"),
+ "<testSourceDirectory>",
+ project.getId(),
+ result,
+ true); // check physical presence
+ } else {
+ // Non-modular: always validate legacy directories
(error if differs from default)
+ Path mainDefault = baseDir.resolve("src/main/java");
+ Path testDefault = baseDir.resolve("src/test/java");
+
+ failIfLegacyDirectoryPresent(
+ build.getSourceDirectory(),
+ mainDefault,
+ "<sourceDirectory>",
+ project.getId(),
+ result,
+ false); // no physical presence check
+ failIfLegacyDirectoryPresent(
+ build.getTestSourceDirectory(),
+ testDefault,
+ "<testSourceDirectory>",
+ project.getId(),
+ result,
+ false); // no physical presence check
+
+ // Use legacy as fallback only if:
+ // 1. <sources> doesn't have Java for this scope
+ // 2. Legacy matches default (otherwise error was
reported above)
+ if (!sourceContext.hasSources(Language.JAVA_FAMILY,
ProjectScope.MAIN)) {
+ Path configuredMain =
Path.of(build.getSourceDirectory())
+ .toAbsolutePath()
+ .normalize();
+ if (configuredMain.equals(
+ mainDefault.toAbsolutePath().normalize()))
{
+
project.addCompileSourceRoot(build.getSourceDirectory());
+ }
+ }
+ if (!sourceContext.hasSources(Language.JAVA_FAMILY,
ProjectScope.TEST)) {
+ Path configuredTest =
Path.of(build.getTestSourceDirectory())
+ .toAbsolutePath()
+ .normalize();
+ if (configuredTest.equals(
+ testDefault.toAbsolutePath().normalize()))
{
+
project.addTestCompileSourceRoot(build.getTestSourceDirectory());
+ }
+ }
}
- }
- // Validate that modular and classic sources are not mixed
within <sources>
- sourceContext.validateNoMixedModularAndClassicSources();
+ // Fail if modular and classic sources are mixed within
<sources>
+ sourceContext.failIfMixedModularAndClassicSources();
- // Handle main and test resources using unified source handling
- sourceContext.handleResourceConfiguration(ProjectScope.MAIN);
- sourceContext.handleResourceConfiguration(ProjectScope.TEST);
+ // Handle main and test resources using unified source
handling
+
sourceContext.handleResourceConfiguration(ProjectScope.MAIN);
+
sourceContext.handleResourceConfiguration(ProjectScope.TEST);
+ }
}
project.setActiveProfiles(
@@ -906,44 +962,60 @@ private void initProject(MavenProject project,
ModelBuilderResult result) {
}
/**
- * Warns about legacy directory usage in a modular project. Two cases
are handled:
+ * Validates that legacy directory configuration does not conflict
with {@code <sources>}.
+ * <p>
+ * When {@code <sources>} is configured, the build fails if:
* <ul>
- * <li>Case 1: The default legacy directory exists on the filesystem
(e.g., src/main/java exists)</li>
- * <li>Case 2: An explicit legacy directory is configured that
differs from the default</li>
+ * <li><strong>Configuration presence</strong>: an explicit legacy
configuration differs from the default</li>
+ * <li><strong>Physical presence</strong>: the default directory
exists on the filesystem (only checked
+ * when {@code checkPhysicalPresence} is true, typically for
modular projects where
+ * {@code <source>} elements use different paths like {@code
src/<module>/main/java})</li>
* </ul>
- * Legacy directories are unconditionally ignored in modular projects
because it is not clear
- * how to dispatch their content between different modules.
+ * <p>
+ * The presence of {@code <sources>} is the trigger for this
validation, not whether the
+ * project is modular or non-modular.
+ * <p>
+ * This ensures consistency with resource handling.
+ *
+ * @param configuredDir the configured legacy directory value
+ * @param defaultDir the default legacy directory path
+ * @param elementName the XML element name for error messages
+ * @param projectId the project ID for error messages
+ * @param result the model builder result for reporting problems
+ * @param checkPhysicalPresence whether to check for physical presence
of the default directory
+ * @see SourceHandlingContext#handleResourceConfiguration(ProjectScope)
*/
- private void warnIfExplicitLegacyDirectory(
+ private void failIfLegacyDirectoryPresent(
String configuredDir,
Path defaultDir,
String elementName,
String projectId,
- ModelBuilderResult result) {
+ ModelBuilderResult result,
+ boolean checkPhysicalPresence) {
if (configuredDir != null) {
Path configuredPath =
Path.of(configuredDir).toAbsolutePath().normalize();
Path defaultPath = defaultDir.toAbsolutePath().normalize();
if (!configuredPath.equals(defaultPath)) {
- // Case 2: Explicit configuration differs from default -
always warn
+ // Configuration presence: explicit config differs from
default
String message = String.format(
- "Legacy %s is ignored in modular project %s. "
- + "In modular projects, source directories
must be defined via <sources> "
- + "with a module element for each module.",
- elementName, projectId);
- logger.warn(message);
+ "Legacy %s cannot be used in project %s because
sources are configured via <sources>. "
+ + "Remove the %s configuration.",
+ elementName, projectId, elementName);
+ logger.error(message);
result.getProblemCollector()
.reportProblem(new
org.apache.maven.impl.model.DefaultModelProblem(
- message, Severity.WARNING, Version.V41,
null, -1, -1, null));
- } else if (Files.isDirectory(defaultPath)) {
- // Case 1: Default configuration, but the default
directory exists on filesystem
+ message, Severity.ERROR, Version.V41,
null, -1, -1, null));
+ } else if (checkPhysicalPresence &&
Files.isDirectory(defaultPath)) {
+ // Physical presence: default directory exists but would
be ignored
String message = String.format(
- "Legacy %s '%s' exists but is ignored in modular
project %s. "
- + "In modular projects, source directories
must be defined via <sources>.",
- elementName, defaultPath, projectId);
- logger.warn(message);
+ "Legacy directory '%s' exists but cannot be used
in project %s "
+ + "because sources are configured via
<sources>. "
+ + "Remove or rename the directory.",
+ defaultPath, projectId);
+ logger.error(message);
result.getProblemCollector()
.reportProblem(new
org.apache.maven.impl.model.DefaultModelProblem(
- message, Severity.WARNING, Version.V41,
null, -1, -1, null));
+ message, Severity.ERROR, Version.V41,
null, -1, -1, null));
}
}
}
diff --git
a/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java
b/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java
index e7691eb86b..400f9f5dc0 100644
---
a/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java
+++
b/impl/maven-core/src/main/java/org/apache/maven/project/SourceHandlingContext.java
@@ -152,7 +152,7 @@ boolean hasSources(Language language, ProjectScope scope) {
}
/**
- * Validates that a project does not mix modular and classic (non-modular)
sources.
+ * Fails the build if modular and classic (non-modular) sources are mixed
within {@code <sources>}.
* <p>
* A project must be either fully modular (all sources have a module) or
fully classic
* (no sources have a module). Mixing modular and non-modular sources
within the same
@@ -161,7 +161,7 @@ boolean hasSources(Language language, ProjectScope scope) {
* This validation checks each (language, scope) combination and reports
an ERROR if
* both modular and non-modular sources are found.
*/
- void validateNoMixedModularAndClassicSources() {
+ void failIfMixedModularAndClassicSources() {
for (ProjectScope scope : List.of(ProjectScope.MAIN,
ProjectScope.TEST)) {
for (Language language : List.of(Language.JAVA_FAMILY,
Language.RESOURCES)) {
boolean hasModular = declaredSources.stream()
@@ -200,6 +200,8 @@ void validateNoMixedModularAndClassicSources() {
* <li>Modular project: use resources from {@code <sources>} if present,
otherwise inject defaults</li>
* <li>Classic project: use resources from {@code <sources>} if present,
otherwise use legacy resources</li>
* </ol>
+ * <p>
+ * The error behavior for conflicting legacy configuration is consistent
with source directory handling.
*
* @param scope the project scope (MAIN or TEST)
*/
@@ -221,11 +223,19 @@ void handleResourceConfiguration(ProjectScope scope) {
if (hasResourcesInSources) {
// Modular project with resources configured via <sources> -
already added above
if (hasExplicitLegacyResources(resources, scopeId)) {
- LOGGER.warn(
- "Legacy {} element is ignored because {} resources
are configured via {} in <sources>.",
- legacyElement,
- scopeId,
- sourcesConfig);
+ String message = String.format(
+ "Legacy %s element cannot be used because %s
resources are configured via %s in <sources>.",
+ legacyElement, scopeId, sourcesConfig);
+ LOGGER.error(message);
+ result.getProblemCollector()
+ .reportProblem(new DefaultModelProblem(
+ message,
+ Severity.ERROR,
+ Version.V41,
+ project.getModel().getDelegate(),
+ -1,
+ -1,
+ null));
} else {
LOGGER.debug(
"{} resources configured via <sources> element,
ignoring legacy {} element.",
@@ -236,13 +246,13 @@ void handleResourceConfiguration(ProjectScope scope) {
// Modular project without resources in <sources> - inject
module-aware defaults
if (hasExplicitLegacyResources(resources, scopeId)) {
String message = "Legacy " + legacyElement
- + " element is ignored because modular sources are
configured. "
+ + " element cannot be used because modular sources
are configured. "
+ "Use " + sourcesConfig + " in <sources> for
custom resource paths.";
- LOGGER.warn(message);
+ LOGGER.error(message);
result.getProblemCollector()
.reportProblem(new DefaultModelProblem(
message,
- Severity.WARNING,
+ Severity.ERROR,
Version.V41,
project.getModel().getDelegate(),
-1,
@@ -265,11 +275,19 @@ void handleResourceConfiguration(ProjectScope scope) {
if (hasResourcesInSources) {
// Resources configured via <sources> - already added above
if (hasExplicitLegacyResources(resources, scopeId)) {
- LOGGER.warn(
- "Legacy {} element is ignored because {} resources
are configured via {} in <sources>.",
- legacyElement,
- scopeId,
- sourcesConfig);
+ String message = String.format(
+ "Legacy %s element cannot be used because %s
resources are configured via %s in <sources>.",
+ legacyElement, scopeId, sourcesConfig);
+ LOGGER.error(message);
+ result.getProblemCollector()
+ .reportProblem(new DefaultModelProblem(
+ message,
+ Severity.ERROR,
+ Version.V41,
+ project.getModel().getDelegate(),
+ -1,
+ -1,
+ null));
} else {
LOGGER.debug(
"{} resources configured via <sources> element,
ignoring legacy {} element.",
@@ -319,7 +337,7 @@ private DefaultSourceRoot createModularResourceRoot(String
module, ProjectScope
*
* @param resources list of resources to check
* @param scope scope (main or test)
- * @return true if explicit legacy resources are present that would be
ignored
+ * @return true if explicit legacy resources are present that conflict
with modular sources
*/
private boolean hasExplicitLegacyResources(List<Resource> resources,
String scope) {
if (resources.isEmpty()) {
diff --git
a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
index 5da89a0f58..c079278ba1 100644
---
a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
+++
b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
@@ -46,6 +46,7 @@
import org.junit.jupiter.api.io.TempDir;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -422,19 +423,21 @@ void testModularSourcesInjectResourceRoots() throws
Exception {
}
/**
- * Tests that when modular sources are configured alongside explicit
legacy resources,
- * the legacy resources are ignored and a warning is issued.
+ * Tests that when modular sources are configured alongside explicit
legacy resources, an error is raised.
* <p>
* This verifies the behavior described in the design:
- * - Modular projects with explicit legacy {@code <resources>}
configuration should issue a warning
+ * - Modular projects with explicit legacy {@code <resources>}
configuration should raise an error
* - The modular resource roots are injected instead of using the legacy
configuration
* <p>
- * Acceptance Criterion: AC2 (unified source tracking for all lang/scope
combinations)
+ * Acceptance Criteria:
+ * - AC2 (unified source tracking for all lang/scope combinations)
+ * - AC8 (legacy directories error - supersedes AC7 which originally used
WARNING)
*
* @see <a href="https://github.com/apache/maven/issues/11612">Issue
#11612</a>
+ * @see <a
href="https://github.com/apache/maven/issues/11701#issuecomment-3858462609">AC8
definition</a>
*/
@Test
- void testModularSourcesWithExplicitResourcesIssuesWarning() throws
Exception {
+ void testModularSourcesWithExplicitResourcesIssuesError() throws Exception
{
File pom = getProject("modular-sources-with-explicit-resources");
MavenSession mavenSession = createMavenSession(null);
@@ -447,19 +450,19 @@ void
testModularSourcesWithExplicitResourcesIssuesWarning() throws Exception {
MavenProject project = result.getProject();
- // Verify warnings are issued for ignored legacy resources
- List<ModelProblem> warnings = result.getProblems().stream()
- .filter(p -> p.getSeverity() == ModelProblem.Severity.WARNING)
- .filter(p -> p.getMessage().contains("Legacy") &&
p.getMessage().contains("ignored"))
+ // Verify errors are raised for conflicting legacy resources (AC8)
+ List<ModelProblem> errors = result.getProblems().stream()
+ .filter(p -> p.getSeverity() == ModelProblem.Severity.ERROR)
+ .filter(p -> p.getMessage().contains("Legacy") &&
p.getMessage().contains("cannot be used"))
.toList();
- assertEquals(2, warnings.size(), "Should have 2 warnings (one for
resources, one for testResources)");
+ assertEquals(2, errors.size(), "Should have 2 errors (one for
resources, one for testResources)");
assertTrue(
- warnings.stream().anyMatch(w ->
w.getMessage().contains("<resources>")),
- "Should warn about ignored <resources>");
+ errors.stream().anyMatch(e ->
e.getMessage().contains("<resources>")),
+ "Should error about conflicting <resources>");
assertTrue(
- warnings.stream().anyMatch(w ->
w.getMessage().contains("<testResources>")),
- "Should warn about ignored <testResources>");
+ errors.stream().anyMatch(e ->
e.getMessage().contains("<testResources>")),
+ "Should error about conflicting <testResources>");
// Verify modular resources are still injected correctly
List<SourceRoot> mainResourceRoots =
project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES)
@@ -478,27 +481,64 @@ void
testModularSourcesWithExplicitResourcesIssuesWarning() throws Exception {
}
/**
- * Tests that legacy sourceDirectory and testSourceDirectory are ignored
in modular projects.
- * <p>
- * In modular projects, legacy directories are unconditionally ignored
because it is not clear
- * how to dispatch their content between different modules. A warning is
emitted if these
- * properties are explicitly set (differ from Super POM defaults).
+ * Tests AC8: ALL legacy directories are rejected when {@code <sources>}
is configured.
* <p>
- * This verifies:
- * - WARNINGs are emitted for explicitly set legacy directories in modular
projects
- * - sourceDirectory and testSourceDirectory are both ignored
- * - Only modular sources from {@code <sources>} are used
+ * Modular project with Java in {@code <sources>} for MAIN scope and
explicit legacy
+ * {@code <sourceDirectory>} that differs from default. The legacy
directory is rejected
+ * because modular projects cannot use legacy directories (content cannot
be dispatched
+ * between modules).
+ *
+ * @see <a
href="https://github.com/apache/maven/issues/11701#issuecomment-3897961755">Issue
#11701 (AC8/AC9)</a>
+ */
+ @Test
+ void testModularWithJavaSourcesRejectsLegacySourceDirectory() throws
Exception {
+ File pom = getProject("modular-java-with-explicit-source-dir");
+
+ MavenSession mavenSession = createMavenSession(null);
+ ProjectBuildingRequest configuration = new
DefaultProjectBuildingRequest();
+
configuration.setRepositorySession(mavenSession.getRepositorySession());
+
+ ProjectBuildingResult result = getContainer()
+ .lookup(org.apache.maven.project.ProjectBuilder.class)
+ .build(pom, configuration);
+
+ MavenProject project = result.getProject();
+
+ // Verify ERROR for <sourceDirectory> (MAIN scope has Java in
<sources>)
+ List<ModelProblem> errors = result.getProblems().stream()
+ .filter(p -> p.getSeverity() == ModelProblem.Severity.ERROR)
+ .filter(p -> p.getMessage().contains("Legacy") &&
p.getMessage().contains("cannot be used"))
+ .filter(p -> p.getMessage().contains("<sourceDirectory>"))
+ .toList();
+
+ assertEquals(1, errors.size(), "Should have 1 error for
<sourceDirectory>");
+
+ // Verify modular source is used, not legacy
+ List<SourceRoot> mainJavaRoots =
project.getEnabledSourceRoots(ProjectScope.MAIN, Language.JAVA_FAMILY)
+ .toList();
+ assertEquals(1, mainJavaRoots.size(), "Should have 1 modular main Java
source root");
+ assertEquals("org.foo.app",
mainJavaRoots.get(0).module().orElse(null), "Should have module org.foo.app");
+
+ // Legacy sourceDirectory is NOT used
+ assertFalse(
+
mainJavaRoots.get(0).directory().toString().contains("src/custom/main/java"),
+ "Legacy sourceDirectory should not be used");
+ }
+
+ /**
+ * Tests AC8: Modular project rejects legacy {@code <testSourceDirectory>}
even when
+ * {@code <sources>} has NO Java for TEST scope.
* <p>
- * Acceptance Criteria:
- * - AC1 (boolean flags eliminated - uses hasSources() for main/test
detection)
- * - AC7 (legacy directories warning - {@code <sourceDirectory>} and
{@code <testSourceDirectory>}
- * are unconditionally ignored with a WARNING in modular projects)
+ * Modular project with NO Java in {@code <sources>} for TEST scope and
explicit legacy
+ * {@code <testSourceDirectory>} that differs from default. The legacy
directory is rejected
+ * because modular projects cannot use legacy directories (content cannot
be dispatched
+ * between modules).
*
- * @see <a href="https://github.com/apache/maven/issues/11612">Issue
#11612</a>
+ * @see <a
href="https://github.com/apache/maven/issues/11701#issuecomment-3897961755">Issue
#11701 (AC8/AC9)</a>
*/
@Test
- void testMixedSourcesModularMainClassicTest() throws Exception {
- File pom = getProject("mixed-sources");
+ void testModularWithoutTestSourcesRejectsLegacyTestSourceDirectory()
throws Exception {
+ File pom =
getProject("modular-no-test-java-with-explicit-test-source-dir");
MavenSession mavenSession = createMavenSession(null);
ProjectBuildingRequest configuration = new
DefaultProjectBuildingRequest();
@@ -510,48 +550,271 @@ void testMixedSourcesModularMainClassicTest() throws
Exception {
MavenProject project = result.getProject();
- // Verify WARNINGs are emitted for explicitly set legacy directories
- List<ModelProblem> warnings = result.getProblems().stream()
- .filter(p -> p.getSeverity() == ModelProblem.Severity.WARNING)
- .filter(p -> p.getMessage().contains("Legacy") &&
p.getMessage().contains("ignored in modular project"))
+ // Verify ERROR for <testSourceDirectory> (modular projects reject all
legacy directories)
+ List<ModelProblem> errors = result.getProblems().stream()
+ .filter(p -> p.getSeverity() == ModelProblem.Severity.ERROR)
+ .filter(p -> p.getMessage().contains("Legacy") &&
p.getMessage().contains("cannot be used"))
+ .filter(p -> p.getMessage().contains("<testSourceDirectory>"))
+ .toList();
+
+ assertEquals(1, errors.size(), "Should have 1 error for
<testSourceDirectory>");
+
+ // No test Java sources (legacy rejected, none in <sources>)
+ List<SourceRoot> testJavaRoots =
project.getEnabledSourceRoots(ProjectScope.TEST, Language.JAVA_FAMILY)
+ .toList();
+ assertEquals(0, testJavaRoots.size(), "Should have no test Java
sources");
+ }
+
+ /**
+ * Tests AC9: explicit legacy directories raise an error in non-modular
projects when
+ * {@code <sources>} has Java for that scope.
+ * <p>
+ * This test uses a non-modular project (no {@code <module>} attribute)
with both:
+ * <ul>
+ * <li>{@code <sources>} with main and test Java sources</li>
+ * <li>Explicit {@code <sourceDirectory>} and {@code
<testSourceDirectory>} (conflicting)</li>
+ * </ul>
+ * Both legacy directories should trigger ERROR because {@code <sources>}
has Java.
+ *
+ * @see <a
href="https://github.com/apache/maven/issues/11701#issuecomment-3897961755">Issue
#11701 (AC8/AC9)</a>
+ */
+ @Test
+ void testClassicSourcesWithExplicitLegacyDirectories() throws Exception {
+ File pom = getProject("classic-sources-with-explicit-legacy");
+
+ MavenSession mavenSession = createMavenSession(null);
+ ProjectBuildingRequest configuration = new
DefaultProjectBuildingRequest();
+
configuration.setRepositorySession(mavenSession.getRepositorySession());
+
+ ProjectBuildingResult result = getContainer()
+ .lookup(org.apache.maven.project.ProjectBuilder.class)
+ .build(pom, configuration);
+
+ // Verify errors are raised for conflicting legacy directories (AC9)
+ List<ModelProblem> errors = result.getProblems().stream()
+ .filter(p -> p.getSeverity() == ModelProblem.Severity.ERROR)
+ .filter(p -> p.getMessage().contains("Legacy") &&
p.getMessage().contains("cannot be used"))
.toList();
- // Should have 2 warnings: one for sourceDirectory, one for
testSourceDirectory
- assertEquals(2, warnings.size(), "Should have 2 warnings for ignored
legacy directories");
+ assertEquals(2, errors.size(), "Should have 2 errors (one for
sourceDirectory, one for testSourceDirectory)");
+
+ // Verify error messages mention the conflicting elements
assertTrue(
- warnings.stream().anyMatch(w ->
w.getMessage().contains("<sourceDirectory>")),
- "Should warn about ignored <sourceDirectory>");
+ errors.stream().anyMatch(e ->
e.getMessage().contains("<sourceDirectory>")),
+ "Should have error for <sourceDirectory>");
assertTrue(
- warnings.stream().anyMatch(w ->
w.getMessage().contains("<testSourceDirectory>")),
- "Should warn about ignored <testSourceDirectory>");
+ errors.stream().anyMatch(e ->
e.getMessage().contains("<testSourceDirectory>")),
+ "Should have error for <testSourceDirectory>");
+ }
+
+ /**
+ * Tests AC9: Non-modular project with only resources in {@code <sources>}
uses implicit Java fallback.
+ * <p>
+ * When {@code <sources>} contains only resources (no Java sources), the
legacy
+ * {@code <sourceDirectory>} and {@code <testSourceDirectory>} are used as
implicit fallback.
+ * This enables incremental adoption of {@code <sources>} - customize
resources while
+ * keeping the default Java directory structure.
+ *
+ * @see <a
href="https://github.com/apache/maven/issues/11701#issuecomment-3897961755">Issue
#11701 (AC8/AC9)</a>
+ */
+ @Test
+ void testNonModularResourcesOnlyWithImplicitJavaFallback() throws
Exception {
+ File pom = getProject("non-modular-resources-only");
- // Get main Java source roots - should have modular sources, not
classic sourceDirectory
+ MavenSession mavenSession = createMavenSession(null);
+ ProjectBuildingRequest configuration = new
DefaultProjectBuildingRequest();
+
configuration.setRepositorySession(mavenSession.getRepositorySession());
+
+ ProjectBuildingResult result = getContainer()
+ .lookup(org.apache.maven.project.ProjectBuilder.class)
+ .build(pom, configuration);
+
+ MavenProject project = result.getProject();
+
+ // Verify NO errors - legacy directories are used as fallback (AC9)
+ List<ModelProblem> errors = result.getProblems().stream()
+ .filter(p -> p.getSeverity() == ModelProblem.Severity.ERROR)
+ .filter(p -> p.getMessage().contains("Legacy") &&
p.getMessage().contains("cannot be used"))
+ .toList();
+
+ assertEquals(0, errors.size(), "Should have no errors - legacy
directories used as fallback (AC9)");
+
+ // Verify resources from <sources> are used
+ List<SourceRoot> mainResources =
project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES)
+ .toList();
+ assertTrue(
+ mainResources.stream().anyMatch(sr -> sr.directory()
+ .toString()
+ .replace(File.separatorChar, '/')
+ .contains("src/main/custom-resources")),
+ "Should have custom main resources from <sources>");
+
+ // Verify legacy Java directories are used as fallback
List<SourceRoot> mainJavaRoots =
project.getEnabledSourceRoots(ProjectScope.MAIN, Language.JAVA_FAMILY)
.toList();
+ assertEquals(1, mainJavaRoots.size(), "Should have 1 main Java source
(implicit fallback)");
+ assertTrue(
+ mainJavaRoots
+ .get(0)
+ .directory()
+ .toString()
+ .replace(File.separatorChar, '/')
+ .endsWith("src/main/java"),
+ "Should use default src/main/java as fallback");
- // Should have 2 modular main Java sources (moduleA and moduleB)
- assertEquals(2, mainJavaRoots.size(), "Should have 2 modular main Java
source roots");
+ List<SourceRoot> testJavaRoots =
project.getEnabledSourceRoots(ProjectScope.TEST, Language.JAVA_FAMILY)
+ .toList();
+ assertEquals(1, testJavaRoots.size(), "Should have 1 test Java source
(implicit fallback)");
+ assertTrue(
+ testJavaRoots
+ .get(0)
+ .directory()
+ .toString()
+ .replace(File.separatorChar, '/')
+ .endsWith("src/test/java"),
+ "Should use default src/test/java as fallback");
+ }
- Set<String> mainModules = mainJavaRoots.stream()
- .map(SourceRoot::module)
- .flatMap(Optional::stream)
- .collect(Collectors.toSet());
+ /**
+ * Tests AC9 violation: Non-modular project with only resources in {@code
<sources>} and explicit legacy directories.
+ * <p>
+ * AC9 allows implicit fallback to legacy directories (when they match
defaults).
+ * When legacy directories differ from the default, this is explicit
configuration,
+ * which violates AC9's "implicit" requirement, so an ERROR is raised.
+ *
+ * @see <a
href="https://github.com/apache/maven/issues/11701#issuecomment-3897961755">Issue
#11701 (AC8/AC9)</a>
+ */
+ @Test
+ void testNonModularResourcesOnlyWithExplicitLegacyDirectoriesRejected()
throws Exception {
+ File pom = getProject("non-modular-resources-only-explicit-legacy");
- assertEquals(2, mainModules.size(), "Should have main sources for 2
modules");
- assertTrue(mainModules.contains("org.foo.moduleA"), "Should have main
source for moduleA");
- assertTrue(mainModules.contains("org.foo.moduleB"), "Should have main
source for moduleB");
+ MavenSession mavenSession = createMavenSession(null);
+ ProjectBuildingRequest configuration = new
DefaultProjectBuildingRequest();
+
configuration.setRepositorySession(mavenSession.getRepositorySession());
- // Verify the classic sourceDirectory is NOT used (should be ignored)
- boolean hasClassicMainSource = mainJavaRoots.stream().anyMatch(sr ->
sr.directory()
- .toString()
- .replace(File.separatorChar, '/')
- .contains("src/classic/main/java"));
- assertTrue(!hasClassicMainSource, "Classic sourceDirectory should be
ignored");
+ ProjectBuildingResult result = getContainer()
+ .lookup(org.apache.maven.project.ProjectBuilder.class)
+ .build(pom, configuration);
+
+ MavenProject project = result.getProject();
+
+ // Verify ERRORs for explicit legacy directories (differ from default)
+ List<ModelProblem> errors = result.getProblems().stream()
+ .filter(p -> p.getSeverity() == ModelProblem.Severity.ERROR)
+ .filter(p -> p.getMessage().contains("Legacy") &&
p.getMessage().contains("cannot be used"))
+ .toList();
+
+ assertEquals(2, errors.size(), "Should have 2 errors for explicit
legacy directories");
+ assertTrue(
+ errors.stream().anyMatch(e ->
e.getMessage().contains("<sourceDirectory>")),
+ "Should error about <sourceDirectory>");
+ assertTrue(
+ errors.stream().anyMatch(e ->
e.getMessage().contains("<testSourceDirectory>")),
+ "Should error about <testSourceDirectory>");
+
+ // Verify resources from <sources> are still used
+ List<SourceRoot> mainResources =
project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES)
+ .toList();
+ assertTrue(
+ mainResources.stream().anyMatch(sr -> sr.directory()
+ .toString()
+ .replace(File.separatorChar, '/')
+ .contains("src/main/custom-resources")),
+ "Should have custom main resources from <sources>");
+
+ // Verify NO Java source roots (legacy was rejected, none in <sources>)
+ List<SourceRoot> mainJavaRoots =
project.getEnabledSourceRoots(ProjectScope.MAIN, Language.JAVA_FAMILY)
+ .toList();
+ assertEquals(0, mainJavaRoots.size(), "Should have no main Java
sources (legacy rejected)");
- // Test sources should NOT be added (legacy testSourceDirectory is
ignored in modular projects)
List<SourceRoot> testJavaRoots =
project.getEnabledSourceRoots(ProjectScope.TEST, Language.JAVA_FAMILY)
.toList();
- assertEquals(0, testJavaRoots.size(), "Should have no test Java
sources (legacy is ignored)");
+ assertEquals(0, testJavaRoots.size(), "Should have no test Java
sources (legacy rejected)");
+ }
+
+ /**
+ * Tests AC8: Modular project with Java in {@code <sources>} and physical
default legacy directories.
+ * <p>
+ * Even when legacy directories use Super POM defaults (no explicit
override),
+ * if the physical directories exist on the filesystem, an ERROR is raised.
+ * This is because modular projects use paths like {@code
src/<module>/main/java},
+ * so content in {@code src/main/java} would be silently ignored.
+ *
+ * @see <a
href="https://github.com/apache/maven/issues/11701#issuecomment-3897961755">Issue
#11701 (AC8/AC9)</a>
+ */
+ @Test
+ void testModularWithPhysicalDefaultLegacyDirectory() throws Exception {
+ File pom = getProject("modular-with-physical-legacy");
+
+ MavenSession mavenSession = createMavenSession(null);
+ ProjectBuildingRequest configuration = new
DefaultProjectBuildingRequest();
+
configuration.setRepositorySession(mavenSession.getRepositorySession());
+
+ ProjectBuildingResult result = getContainer()
+ .lookup(org.apache.maven.project.ProjectBuilder.class)
+ .build(pom, configuration);
+
+ // Verify ERRORs are raised for physical presence of default
directories (AC8)
+ List<ModelProblem> errors = result.getProblems().stream()
+ .filter(p -> p.getSeverity() == ModelProblem.Severity.ERROR)
+ .filter(p -> p.getMessage().contains("Legacy directory")
+ && p.getMessage().contains("exists"))
+ .toList();
+
+ // Should have 2 errors: one for src/main/java, one for src/test/java
+ assertEquals(2, errors.size(), "Should have 2 errors for physical
legacy directories");
+ // Use File.separator for platform-independent path matching
(backslash on Windows)
+ String mainJava = "src" + File.separator + "main" + File.separator +
"java";
+ String testJava = "src" + File.separator + "test" + File.separator +
"java";
+ assertTrue(
+ errors.stream().anyMatch(e ->
e.getMessage().contains(mainJava)),
+ "Should error about physical src/main/java");
+ assertTrue(
+ errors.stream().anyMatch(e ->
e.getMessage().contains(testJava)),
+ "Should error about physical src/test/java");
+ }
+
+ /**
+ * Tests AC8: Modular project with only resources in {@code <sources>} and
physical default legacy directories.
+ * <p>
+ * Even when {@code <sources>} only contains resources (no Java), if the
physical
+ * default directories exist, an ERROR is raised for modular projects.
+ * Unlike non-modular projects (AC9), modular projects cannot use legacy
directories as fallback.
+ *
+ * @see <a
href="https://github.com/apache/maven/issues/11701#issuecomment-3897961755">Issue
#11701 (AC8/AC9)</a>
+ */
+ @Test
+ void testModularResourcesOnlyWithPhysicalDefaultLegacyDirectory() throws
Exception {
+ File pom = getProject("modular-resources-only-with-physical-legacy");
+
+ MavenSession mavenSession = createMavenSession(null);
+ ProjectBuildingRequest configuration = new
DefaultProjectBuildingRequest();
+
configuration.setRepositorySession(mavenSession.getRepositorySession());
+
+ ProjectBuildingResult result = getContainer()
+ .lookup(org.apache.maven.project.ProjectBuilder.class)
+ .build(pom, configuration);
+
+ // Verify ERRORs are raised for physical presence of default
directories (AC8)
+ // Unlike non-modular (AC9), modular projects cannot use legacy as
fallback
+ List<ModelProblem> errors = result.getProblems().stream()
+ .filter(p -> p.getSeverity() == ModelProblem.Severity.ERROR)
+ .filter(p -> p.getMessage().contains("Legacy directory")
+ && p.getMessage().contains("exists"))
+ .toList();
+
+ // Should have 2 errors: one for src/main/java, one for src/test/java
+ assertEquals(
+ 2, errors.size(), "Should have 2 errors for physical legacy
directories (no AC9 fallback for modular)");
+ // Use File.separator for platform-independent path matching
(backslash on Windows)
+ String mainJava = "src" + File.separator + "main" + File.separator +
"java";
+ String testJava = "src" + File.separator + "test" + File.separator +
"java";
+ assertTrue(
+ errors.stream().anyMatch(e ->
e.getMessage().contains(mainJava)),
+ "Should error about physical src/main/java");
+ assertTrue(
+ errors.stream().anyMatch(e ->
e.getMessage().contains(testJava)),
+ "Should error about physical src/test/java");
}
/**
@@ -563,7 +826,7 @@ void testMixedSourcesModularMainClassicTest() throws
Exception {
* <p>
* This verifies:
* - An ERROR is reported when both modular and non-modular sources exist
in {@code <sources>}
- * - sourceDirectory is ignored because {@code <source scope="main"
lang="java">} exists
+ * - sourceDirectory is not used because {@code <sources>} exists
* <p>
* Acceptance Criteria:
* - AC1 (boolean flags eliminated - uses hasSources() for source
detection)
diff --git
a/impl/maven-core/src/test/projects/project-builder/classic-sources-with-explicit-legacy/pom.xml
b/impl/maven-core/src/test/projects/project-builder/classic-sources-with-explicit-legacy/pom.xml
new file mode 100644
index 0000000000..0c5726393a
--- /dev/null
+++
b/impl/maven-core/src/test/projects/project-builder/classic-sources-with-explicit-legacy/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.1.0
https://maven.apache.org/xsd/maven-4.1.0.xsd">
+ <modelVersion>4.1.0</modelVersion>
+
+ <groupId>org.apache.maven.tests</groupId>
+ <artifactId>classic-sources-explicit-legacy-test</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <build>
+ <sources>
+ <!-- Classic (non-modular) source - no module attribute -->
+ <source>
+ <scope>main</scope>
+ <lang>java</lang>
+ <directory>src/main/java</directory>
+ </source>
+ <source>
+ <scope>test</scope>
+ <lang>java</lang>
+ <directory>src/test/java</directory>
+ </source>
+ </sources>
+ <!-- Explicit legacy directories that conflict with <sources> - should
trigger ERROR (AC9) -->
+ <sourceDirectory>src/legacy/main/java</sourceDirectory>
+ <testSourceDirectory>src/legacy/test/java</testSourceDirectory>
+ </build>
+</project>
diff --git
a/impl/maven-core/src/test/projects/project-builder/mixed-sources/pom.xml
b/impl/maven-core/src/test/projects/project-builder/mixed-sources/pom.xml
deleted file mode 100644
index caa10d9885..0000000000
--- a/impl/maven-core/src/test/projects/project-builder/mixed-sources/pom.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- Tests that legacy directories are ignored in modular projects.
-
- Expected behavior:
- - Modular sources for main Java are processed
- - Both sourceDirectory and testSourceDirectory are unconditionally ignored
- - WARNINGs are emitted because both are explicitly set (differ from defaults)
--->
-<project xmlns="http://maven.apache.org/POM/4.1.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.1.0
https://maven.apache.org/xsd/maven-4.1.0.xsd">
- <modelVersion>4.1.0</modelVersion>
-
- <groupId>org.apache.maven.tests</groupId>
- <artifactId>mixed-sources-test</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>jar</packaging>
-
- <build>
- <!-- Classic sourceDirectory - ignored with WARNING because project is
modular -->
- <sourceDirectory>src/classic/main/java</sourceDirectory>
- <!-- Classic testSourceDirectory - ignored with WARNING because
project is modular -->
- <testSourceDirectory>src/classic/test/java</testSourceDirectory>
-
- <sources>
- <!-- Modular main java sources - these should override
sourceDirectory -->
- <source>
- <scope>main</scope>
- <lang>java</lang>
- <module>org.foo.moduleA</module>
- </source>
- <source>
- <scope>main</scope>
- <lang>java</lang>
- <module>org.foo.moduleB</module>
- </source>
- <!-- No test sources defined in <sources> -->
- </sources>
- </build>
-</project>
diff --git
a/impl/maven-core/src/test/projects/project-builder/modular-java-with-explicit-source-dir/pom.xml
b/impl/maven-core/src/test/projects/project-builder/modular-java-with-explicit-source-dir/pom.xml
new file mode 100644
index 0000000000..dfab4a6b3f
--- /dev/null
+++
b/impl/maven-core/src/test/projects/project-builder/modular-java-with-explicit-source-dir/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Tests AC8 scenario 2: Modular project with Java in <sources> for MAIN scope
+ and explicit legacy <sourceDirectory> that differs from default.
+
+ Expected behavior:
+ - ERROR for <sourceDirectory> (modular project, <sources> has Java for MAIN)
+ - Modular Java sources are used, not legacy
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.1.0
https://maven.apache.org/xsd/maven-4.1.0.xsd">
+ <modelVersion>4.1.0</modelVersion>
+
+ <groupId>org.apache.maven.tests</groupId>
+ <artifactId>modular-java-with-explicit-source-dir-test</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <build>
+ <!-- Explicit legacy sourceDirectory - triggers ERROR (AC8) -->
+ <sourceDirectory>src/custom/main/java</sourceDirectory>
+
+ <sources>
+ <!-- Modular main Java source -->
+ <source>
+ <scope>main</scope>
+ <lang>java</lang>
+ <module>org.foo.app</module>
+ </source>
+ </sources>
+ </build>
+</project>
diff --git
a/impl/maven-core/src/test/projects/project-builder/modular-no-test-java-with-explicit-test-source-dir/pom.xml
b/impl/maven-core/src/test/projects/project-builder/modular-no-test-java-with-explicit-test-source-dir/pom.xml
new file mode 100644
index 0000000000..dd27dac586
--- /dev/null
+++
b/impl/maven-core/src/test/projects/project-builder/modular-no-test-java-with-explicit-test-source-dir/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Tests AC8 scenario 5: Modular project with NO Java in <sources> for TEST
scope
+ and explicit legacy <testSourceDirectory> that differs from default.
+
+ Expected behavior:
+ - ERROR for <testSourceDirectory> (modular project, no AC9 fallback)
+ - No test Java sources (legacy rejected, none in <sources>)
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.1.0
https://maven.apache.org/xsd/maven-4.1.0.xsd">
+ <modelVersion>4.1.0</modelVersion>
+
+ <groupId>org.apache.maven.tests</groupId>
+
<artifactId>modular-no-test-java-with-explicit-test-source-dir-test</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <build>
+ <!-- Explicit legacy testSourceDirectory - triggers ERROR (AC8, no AC9
fallback for modular) -->
+ <testSourceDirectory>src/custom/test/java</testSourceDirectory>
+
+ <sources>
+ <!-- Modular main Java sources only - no test Java -->
+ <source>
+ <scope>main</scope>
+ <lang>java</lang>
+ <module>org.foo.app</module>
+ </source>
+ <!-- No test Java sources in <sources> -->
+ </sources>
+ </build>
+</project>
diff --git
a/impl/maven-core/src/test/projects/project-builder/modular-resources-only-with-physical-legacy/pom.xml
b/impl/maven-core/src/test/projects/project-builder/modular-resources-only-with-physical-legacy/pom.xml
new file mode 100644
index 0000000000..92f8cb52a4
--- /dev/null
+++
b/impl/maven-core/src/test/projects/project-builder/modular-resources-only-with-physical-legacy/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Tests AC8: Modular project with only resources in <sources> and physical
default legacy directory.
+
+ Expected behavior:
+ - Modular resources from <sources> are processed
+ - Physical src/main/java and src/test/java directories exist but would be
ignored
+ - ERROR is raised because physical default directories exist (AC8 physical
presence check)
+ - Unlike non-modular projects (AC9), modular projects cannot use legacy
directories as fallback
+
+ Note: The src/main/java and src/test/java directories are created empty to
trigger
+ the physical presence check.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.1.0
https://maven.apache.org/xsd/maven-4.1.0.xsd">
+ <modelVersion>4.1.0</modelVersion>
+
+ <groupId>org.apache.maven.tests</groupId>
+ <artifactId>modular-resources-only-with-physical-legacy-test</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <build>
+ <!-- No explicit sourceDirectory/testSourceDirectory - uses Super POM
defaults -->
+ <!-- But physical src/main/java and src/test/java directories exist -->
+
+ <sources>
+ <!-- Only modular resources - no Java sources -->
+ <source>
+ <scope>main</scope>
+ <lang>resources</lang>
+ <module>org.example.app</module>
+ </source>
+ <source>
+ <scope>test</scope>
+ <lang>resources</lang>
+ <module>org.example.app</module>
+ </source>
+ </sources>
+ </build>
+</project>
diff --git
a/impl/maven-core/src/test/projects/project-builder/modular-resources-only-with-physical-legacy/src/main/java/.gitkeep
b/impl/maven-core/src/test/projects/project-builder/modular-resources-only-with-physical-legacy/src/main/java/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git
a/impl/maven-core/src/test/projects/project-builder/modular-resources-only-with-physical-legacy/src/test/java/.gitkeep
b/impl/maven-core/src/test/projects/project-builder/modular-resources-only-with-physical-legacy/src/test/java/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git
a/impl/maven-core/src/test/projects/project-builder/modular-with-physical-legacy/pom.xml
b/impl/maven-core/src/test/projects/project-builder/modular-with-physical-legacy/pom.xml
new file mode 100644
index 0000000000..27267c0bc2
--- /dev/null
+++
b/impl/maven-core/src/test/projects/project-builder/modular-with-physical-legacy/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Tests AC8: Modular project with physical default legacy directory.
+
+ Expected behavior:
+ - Modular Java sources from <sources> are processed
+ - Physical src/main/java and src/test/java directories exist but would be
ignored
+ - ERROR is raised because physical default directories exist (AC8 physical
presence check)
+
+ Note: The src/main/java and src/test/java directories are created empty to
trigger
+ the physical presence check.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.1.0
https://maven.apache.org/xsd/maven-4.1.0.xsd">
+ <modelVersion>4.1.0</modelVersion>
+
+ <groupId>org.apache.maven.tests</groupId>
+ <artifactId>modular-with-physical-legacy-test</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <build>
+ <!-- No explicit sourceDirectory/testSourceDirectory - uses Super POM
defaults -->
+ <!-- But physical src/main/java and src/test/java directories exist -->
+
+ <sources>
+ <!-- Modular Java sources -->
+ <source>
+ <scope>main</scope>
+ <lang>java</lang>
+ <module>org.example.app</module>
+ </source>
+ <source>
+ <scope>test</scope>
+ <lang>java</lang>
+ <module>org.example.app</module>
+ </source>
+ </sources>
+ </build>
+</project>
diff --git
a/impl/maven-core/src/test/projects/project-builder/modular-with-physical-legacy/src/main/java/.gitkeep
b/impl/maven-core/src/test/projects/project-builder/modular-with-physical-legacy/src/main/java/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git
a/impl/maven-core/src/test/projects/project-builder/modular-with-physical-legacy/src/test/java/.gitkeep
b/impl/maven-core/src/test/projects/project-builder/modular-with-physical-legacy/src/test/java/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git
a/impl/maven-core/src/test/projects/project-builder/non-modular-resources-only-explicit-legacy/pom.xml
b/impl/maven-core/src/test/projects/project-builder/non-modular-resources-only-explicit-legacy/pom.xml
new file mode 100644
index 0000000000..2bb12cd7a6
--- /dev/null
+++
b/impl/maven-core/src/test/projects/project-builder/non-modular-resources-only-explicit-legacy/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Tests AC9 violation: Non-modular project with only resources in <sources>
and explicit legacy directories.
+
+ AC9 allows implicit fallback to legacy directories (when they match
defaults).
+ This test verifies that explicit configuration (differs from default) is
rejected.
+
+ Expected behavior:
+ - Resources from <sources> are used
+ - ERROR for sourceDirectory and testSourceDirectory because they differ from
defaults
+ - No Java source roots are added (legacy rejected, none in <sources>)
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.1.0
https://maven.apache.org/xsd/maven-4.1.0.xsd">
+ <modelVersion>4.1.0</modelVersion>
+
+ <groupId>org.apache.maven.tests</groupId>
+ <artifactId>non-modular-resources-only-explicit-legacy-test</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <build>
+ <sources>
+ <!-- Only resources configured - no Java sources -->
+ <source>
+ <scope>main</scope>
+ <lang>resources</lang>
+ <directory>src/main/custom-resources</directory>
+ </source>
+ <source>
+ <scope>test</scope>
+ <lang>resources</lang>
+ <directory>src/test/custom-resources</directory>
+ </source>
+ <!-- No <source lang="java"> elements -->
+ </sources>
+ <!-- Explicit legacy directories - triggers ERROR (differ from
default) -->
+ <sourceDirectory>src/custom/main/java</sourceDirectory>
+ <testSourceDirectory>src/custom/test/java</testSourceDirectory>
+ </build>
+</project>
diff --git
a/impl/maven-core/src/test/projects/project-builder/non-modular-resources-only/pom.xml
b/impl/maven-core/src/test/projects/project-builder/non-modular-resources-only/pom.xml
new file mode 100644
index 0000000000..12eee4001d
--- /dev/null
+++
b/impl/maven-core/src/test/projects/project-builder/non-modular-resources-only/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Tests AC9: Non-modular project with only resources in <sources>.
+
+ Expected behavior:
+ - Resources from <sources> are used
+ - Legacy sourceDirectory/testSourceDirectory are used as implicit fallback
(AC9)
+ - No errors since <sources> doesn't have Java for these scopes
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.1.0
https://maven.apache.org/xsd/maven-4.1.0.xsd">
+ <modelVersion>4.1.0</modelVersion>
+
+ <groupId>org.apache.maven.tests</groupId>
+ <artifactId>non-modular-resources-only-test</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <build>
+ <sources>
+ <!-- Only resources configured - no Java sources -->
+ <source>
+ <scope>main</scope>
+ <lang>resources</lang>
+ <directory>src/main/custom-resources</directory>
+ </source>
+ <source>
+ <scope>test</scope>
+ <lang>resources</lang>
+ <directory>src/test/custom-resources</directory>
+ </source>
+ <!-- No <source lang="java"> elements -->
+ </sources>
+ <!-- Legacy directories should be used as fallback (AC9) -->
+ <!-- Using Super POM defaults: src/main/java and src/test/java -->
+ </build>
+</project>