Hi,

based on my work to migrate a number of Java Modules (aka. Jigsaw/JPMS) to 
Maven (4), I did a deep investigation of the current implementation. I already 
discussed with some of you on Slack about my work and one thing we found is the 
missing handling of resources when (Java) modules are used.

At first I just tried to implement proper resource handling (cf. my proposed, 
yet draft, PR). But the deeper I looked into it, I found Maven support for Java 
Modules incomplete, in particular wrt the old (Maven <= 3) conventions. 
Therefore, I have now created a deep analysis based on some typical examples. 
The analysis culminates in a full matrix of combinations of sources/resources 
etc. I also propose how to handle the different cases. 

Please have a look into it and provide feedback about it. The subject is rather 
complex, so I might have missed important view points.

Perhaps other media are better suited to discuss the different cases and their 
consequences (in particular wrt. to warnings and errors)? If its helpful, I’d 
be happy to place the stuff into the Maven Wiki (Confluence).

However, in the end, the work should result in some decisions. I propose the 
following
Handle model loading and proper handling of sources/resources completely in the 
core (currently, the compiler plugin contributes to some handling, see below)
Implement a strict approach how to handle misconfigurations (this is mostly 
about warnings vs. errors, see below). Personally, I plead for a very strict 
handling (errors) here as the feature is new with Maven 4 and have no need to 
handle legacy code about Java modules.
Build this handling into the Maven core and provide proper documentation.
Perhaps redraw some of the existing error handling from the compiler plugin as 
it much better suites into the core (sorry, Martin).

Of course, I hereby volunteer to perform the implementation. I have already 
started with some of it, to be honest.

For phase 1 (proper handling of resources in the context of <sources> and 
<modules> I have implemented a solution (cf. Draft PR 
<https://github.com/apache/maven/pull/11505> on maven-core). The nice thing 
about this implementation is, that even M-Resources-P in version 3.x can handle 
the resources with this Maven core as it is using the proper abstractions.

Regards
--
Gerd Aschemann --- Veröffentlichen heißt Verändern (Carmen Thomas)
+49/173/3264070 -- [email protected] -- http://www.aschemann.net


Build Sources Validation

This document captures a discussion about validating <sources> configuration in 
Maven 4.x.

0. Scope

This document focuses exclusively on the Maven 4.x core implementation (master 
branch of apache/maven <https://github.com/apache/maven>). Specifically:

The <sources> element introduced in POM model version 4.1.0
The source/resource handling logic in DefaultProjectBuilder.java
Validation gaps in the core that should be addressed
0.1 Compiler Plugin Compensation

Some validation gaps in the core are currently compensated by the 
maven-compiler-plugin, which performs its own validation at compile time, e.g.,

Mixed modular/non-modular sources → compiler plugin fails with "Mix of modular 
and non-modular sources", cf. ToolExecutor.java#L595-L608 
<https://github.com/apache/maven-compiler-plugin/blob/050664790af28be9107aff6172f8e61adcb8ed8a/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java#L595-L608>
However, relying on plugin-level validation is suboptimal:

Later feedback: Errors appear during compilation, not during POM processing
No line numbers: Plugin errors cannot reference the exact POM location
Plugin-specific: Other language plugins (Kotlin, Groovy, Scala) may not have 
equivalent validation
Incomplete coverage: Some invalid configurations are not caught at all (e.g., 
test-only modular projects)
The goal should be to implement proper validation in the Maven core, providing 
early, consistent feedback with precise error locations via ModelProblem.

0.2 Origin of This Discussion

This discussion was triggered by investigating how to best implement resource 
handling in the new modular source directory hierarchy. Maven 4.x introduces a 
unified <sources> element that supports modular layouts like:

src/
├── org.foo.moduleA/
│   ├── main/
│   │   ├── java/
│   │   └── resources/    ← Module-specific resources
│   └── test/
│       ├── java/
│       └── resources/
└── org.foo.moduleB/
    ├── main/
    │   ├── java/
    │   └── resources/    ← Module-specific resources
    └── test/
However, the current implementation does not automatically pick up resources 
from the modular paths (src/<module>/main/resources). Instead, it always uses 
the legacy <resources> element which defaults to src/main/resources.

Solution: See Phase 1 
<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#41-phase-1-module-aware-resource-handling-implemented>
 for the implementation that automatically injects module-aware resources.

Current Workaround: Users can configure the maven-resources-plugin with 
explicit executions for each module:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-resources-plugin</artifactId>
  <executions>
    <execution>
      <id>copy-resources-moduleA</id>
      <phase>process-resources</phase>
      <goals><goal>copy-resources</goal></goals>
      <configuration>
        <resources>
          <resource>
            <directory>src/org.foo.moduleA/main/resources</directory>
          </resource>
        </resources>
        <outputDirectory>${project.build.outputDirectory}</outputDirectory>
      </configuration>
    </execution>
    <execution>
      <id>copy-resources-moduleB</id>
      <phase>process-resources</phase>
      <goals><goal>copy-resources</goal></goals>
      <configuration>
        <resources>
          <resource>
            <directory>src/org.foo.moduleB/main/resources</directory>
          </resource>
        </resources>
        <outputDirectory>${project.build.outputDirectory}</outputDirectory>
      </configuration>
    </execution>
  </executions>
</plugin>
This workaround is verbose and error-prone. The investigation into implementing 
this properly in the core led to the Phase 1 implementation 
<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#41-phase-1-module-aware-resource-handling-implemented>
 and revealed the broader set of validation and configuration handling issues 
documented here.

1. Problem Statement

Let's investigate some of the problems with the current implementation of 
<sources> handling in Maven 4.x. Maven 4.x introduces a new <sources> element 
that supports modular project layouts (src/<module>/<scope>/<lang>). However, 
the current implementation has several issues when handling the interaction 
between the new <sources> element and legacy configuration elements 
(<sourceDirectory>, <resources>, etc.).

This is not a comprehensive list, but highlights key problems that should be 
addressed in the core. A systematic Analysis and Proposed Solutions follow in 
subsequent sections.

Problem 1: Mixed Modular/Non-Modular Sources Accepted Silently

Mixing modular and non-modular sources in <sources> is an invalid configuration 
that will fail at compile time, but DefaultProjectBuilder accepts it silently 
without early validation.

Example - Mixed configuration:

<build>
  <sources>
    <source>
      <scope>main</scope>
      <lang>java</lang>
      <module>org.foo.moduleA</module>  <!-- Modular -->
    </source>
    <source>
      <scope>main</scope>
      <lang>java</lang>
      <!-- No module - classic style -->
    </source>
  </sources>
</build>
Expected Behavior       Actual Behavior
Early warning/error about inconsistent configuration    Silent acceptance by 
DefaultProjectBuilder
Clear feedback during project build     Delayed failure at compile time
Current Workaround: The maven-compiler-plugin validates this at compile time 
and fails with:

"Mix of modular and non-modular sources."
See above (Compiler Plugin Compensation 
<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#compiler-plugin-compensation>)
 for drawbacks of this approach.

Problem 2: Test-Only Modular Projects Get Classic Main Sources Injected

When a project configures only test sources in <sources> (no main sources), 
DefaultProjectBuilder silently injects the classic src/main/java as a main 
source directory.

Example - Integration test module with only test sources:

<build>
  <sources>
    <source>
      <scope>test</scope>
      <lang>java</lang>
      <module>org.foo.integrationTests</module>
    </source>
  </sources>
</build>
Expected Behavior       Actual Behavior
No main sources (project is test-only)  src/main/java silently added as main 
source
Test sources from src/org.foo.integrationTests/test/java        Test sources 
work correctly
Root cause: The hasMain boolean remains false when no <source> has scope=main + 
lang=java. Line 697-698 then adds build.getSourceDirectory() (defaults to 
src/main/java from Super POM).

See: DefaultProjectBuilder.java#L697-L698 
<https://github.com/apache/maven/blob/25c80d8ece4252421c9dce5c34fb46a21c7b9f23/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L697-L698>
Impact:

If src/main/java doesn't exist → probably harmless (empty directory)
If src/main/java exists with old/unrelated code → it gets compiled!
Modular test project silently becomes a mixed modular-test + classic-main 
project
Use cases affected:

Integration test modules (only contain tests)
Test fixtures modules (shared test utilities)
BOM/parent projects with test verification but no main code
Note: Unlike Problem 1, this is not caught by the compiler plugin - it silently 
succeeds but with potentially wrong behavior.

Problem 3: Resources Are Not Module-Aware

When a project uses modular sources, resources should follow the same modular 
layout. Currently, they don't.

Example - Modular Java sources without explicit resources:

<build>
  <sources>
    <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>
  </sources>
</build>
Expected Behavior       Actual Behavior
Resources from src/org.foo.moduleA/main/resources       Resources from 
src/main/resources only
Resources from src/org.foo.moduleB/main/resources       (legacy path, not 
module-aware)
Impact: Module-specific resources (like module-specific configs) cannot be 
organized per-module.

Solution: See Phase 1 
<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#41-phase-1-module-aware-resource-handling-implemented>
 for the implementation.

Workaround (no longer needed): See Origin of This Discussion 
<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#origin-of-this-discussion>
 for how to configure the maven-resources-plugin with explicit executions for 
each module.

Problem 4: Legacy Configuration Silently Ignored

When <sources> is present, legacy elements like <sourceDirectory> are silently 
ignored without warning.

Example - Explicit sourceDirectory with sources:

<build>
  <sourceDirectory>src/custom/java</sourceDirectory>  <!-- User expects this to 
be used -->
  <sources>
    <source>
      <scope>main</scope>
      <lang>java</lang>
      <module>org.foo.bar</module>
    </source>
  </sources>
</build>
Expected Behavior       Actual Behavior
Warning: "sourceDirectory is ignored because sources are configured"    Silent 
- user doesn't know their config is ignored
Clear feedback to user  Confusion when src/custom/java isn't used
2. Analysis

2.1 Core Design Question

How should the source reading and validation logic work?

The fundamental question is about priority and conflict resolution when both 
new (<sources>) and legacy (<sourceDirectory>, <resources>) configuration 
elements are present.

Key Design Principle: Modular Sources Have Priority

When a project uses the new <sources> element with modular configuration 
(<module> element), the modular approach should take precedence:

Modular sources (<sources> with <module>) demand proper modular layout and 
behavior
Non-modular sources (<sources> without <module>) should not be used (error or 
warning) in a modular project
Legacy configuration (<sourceDirectory>, <resources>) is used only as fallback 
when no <sources> are configured
This principle ensures:

Clear, predictable behavior
No silent mixing of old and new approaches
Users explicitly opt-in to the new model
Path Resolution Logic

From DefaultSourceRoot.fromModel() — abbreviated pseudo code of the current 
implementation 
<https://github.com/apache/maven/blob/25c80d8ece4252421c9dce5c34fb46a21c7b9f23/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java#L135-L141>:

if (module specified) {
    // Modular mode
    path = directory specified ? baseDir.resolve(directory)
                               : baseDir/src/<module>/<scope>/<lang>
} else {
    // Classic mode
    path = directory specified ? baseDir.resolve(directory)
                               : baseDir/src/<scope>/<lang>
}
Module  Directory       Mode    Result
No      No      Classic src/<scope>/<lang> (default)
No      Yes     Classic <directory> (override)
Yes     No      Modular src/<module>/<scope>/<lang> (default)
Yes     Yes     Modular <directory> (override, module = metadata)
2.2 Strict vs Lenient Approach

Two approaches are possible for handling configuration conflicts:

Approach A: Strict (Fail Fast)

Configuration conflicts result in errors that fail the build.

Rule    Condition       Severity        Message
R1      <sources> present AND explicit <sourceDirectory>        ERROR   "Cannot 
combine <sources> with explicit <sourceDirectory>. Use one or the other."
R2      <sources> with mixed module/no-module elements  ERROR   "Cannot mix 
modular and non-modular sources. All <source> elements must either have a 
module or none."
R3      Duplicate <source> (same scope/lang/module)     ERROR   "Duplicate 
source definition for scope='X', lang='Y', module='Z'"
R4      <sources> configured but classic directory exists on filesystem WARN    
"Directory 'src/main/java' exists but will be ignored because <sources> is 
configured"
Approach B: Lenient (Warn and Continue)

Configuration conflicts result in warnings but the build continues.

Rule    Condition       Severity        Message
R1'     <sources> present AND explicit <sourceDirectory>        WARN    
"Explicit <sourceDirectory> will be ignored because <sources> is configured."
R2'     <sources> with mixed module/no-module elements  WARN    "Mixing modular 
and non-modular sources may lead to unexpected behavior."
R3      Duplicate <source> (same scope/lang/module)     ERROR   "Duplicate 
source definition for scope='X', lang='Y', module='Z'"
R4      <sources> configured but classic directory exists on filesystem WARN    
"Directory 'src/main/java' exists but will be ignored because <sources> is 
configured"
Comparison

Aspect  Strict (A)      Lenient (B)
<sources> + explicit SD/TSD     ERROR   WARN
Mixed module/no-module  ERROR   WARN
Duplicate sources       ERROR   ERROR
Filesystem mismatch     WARN    WARN
Migration friendliness  Lower   Higher
User confusion risk     Lower   Higher
Fail-fast principle     Yes     No
Recommendation

Approach A (Strict) is recommended for new projects because:

Configuration conflicts indicate user error/misunderstanding
Silent ignoring of explicit configuration is confusing
Better to fail early than have unexpected behavior at compile time
Users can easily fix by removing the conflicting element
Approach B (Lenient) could be considered if:

Migration from Maven 3.x to 4.x needs to be smoother
Tooling (IDEs) may generate both elements during transition
A deprecation period is desired before enforcing strict rules
2.3 Unified Permutation Matrix

This matrix shows all configuration combinations and their expected behavior 
under each approach.

Legend

Abbreviation    Meaning
SD      <sourceDirectory> - classic Maven 3.x element for main Java sources
TSD     <testSourceDirectory> - classic Maven 3.x element for test Java sources
S       <source> element within <sources> - Maven 4.x way to define sources
R       <resources> / <resource> - classic Maven 3.x element for resources
M       <module> element within <source> - specifies JPMS module name
Java Sources

#       Configuration   Current Lenient Strict  Compiler Plugin
No <sources> (classic mode)                             
1       SD=implicit (Super POM) src/main/java   OK      OK      -
2       SD=explicit     user's path     OK      OK      -
<sources> present (new mode)                            
3       S(no M), SD=implicit    src/main/java   OK      OK      -
4       S(no M), SD=explicit    Silent ignore   WARN    ERROR   Not caught
5       S(M=X), SD=implicit     src/X/main/java OK      OK      -
6       S(M=X), SD=explicit     Silent ignore   WARN    ERROR   Not caught
7       S(M=X) + S(M=Y) Both paths      OK      OK      -
8       S(M=X) + S(no M)        Silent accept   WARN    ERROR   Caught ✓
9       S(dir=custom, no M)     custom path     OK      OK      -
10      S(dir=custom, M=X)      custom (M=meta) OK      OK      -
11      Duplicate S     Silent accept   ERROR   ERROR   Not caught
12      S(M=X) + src/main/java exists   Silent ignore   WARN    WARN    Not 
caught
13      S(scope=test, M=X) only Injects src/main/java   No inject       No 
inject       Not caught ⚠️
Resources

#       S Config        R Config        Current Proposed (Phase 1)      Lenient 
Strict
1       S(lang=resources)       R=any   Duplicates!     Use S, WARN if R 
explicit       Use S, WARN     Use S, WARN
2       S(java, no M)   R=implicit      src/main/resources      
src/main/resources      OK      OK
3       S(java, no M)   R=explicit      User's path     User's path     OK      
OK
4       S(java, M=X)    R=implicit      src/main/resources      Inject modular  
Inject modular  Inject modular
5       S(java, M=X)    R=explicit      User's path     Inject modular, WARN    
WARN    ERROR
3. Current Implementation Status

As of commit 25c80d8 
<https://github.com/apache/maven/blob/25c80d8ece4252421c9dce5c34fb46a21c7b9f23/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L645-L708>
Implementation Location

The source/resource handling logic is in DefaultProjectBuilder.initProject():

// Lines 669-686: Iterate over <sources> and track what's present
boolean hasScript = false;
boolean hasMain = false;   // true if ANY <source> has lang=java, scope=main
boolean hasTest = false;   // true if ANY <source> has lang=java, scope=test
for (var source : sources) {
    var src = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, 
source);
    project.addSourceRoot(src);
    // ... tracking logic
}

// Lines 694-701: Decide whether to use legacy 
sourceDirectory/testSourceDirectory
// (Lines 687-692 contain a comment explaining this behavior)
if (!hasScript) {
    project.addScriptSourceRoot(build.getScriptSourceDirectory());
}
if (!hasMain) {
    project.addCompileSourceRoot(build.getSourceDirectory());  // Silent 
fallback
}
if (!hasTest) {
    project.addTestCompileSourceRoot(build.getTestSourceDirectory());  // 
Silent fallback
}

// Lines 703-708: Resources are ALWAYS added from legacy elements (no checks!)
for (Resource resource : project.getBuild().getDelegate().getResources()) {
    project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.MAIN, 
resource));
}
for (Resource resource : project.getBuild().getDelegate().getTestResources()) {
    project.addSourceRoot(new DefaultSourceRoot(baseDir, ProjectScope.TEST, 
resource));
}
Gap Analysis

Issue   Description     Core Status     Compiler Plugin
No warnings     Explicit <sourceDirectory> silently ignored when <sources> 
present      Not implemented Not caught
No mixed validation     Mixing S(M=X) with S(no M) silently accepted    Not 
implemented Caught ✓
Test-only injection     src/main/java injected for test-only modular projects   
Not implemented Not caught ⚠️
No duplicates check     Same source can be defined multiple times       Not 
implemented Not caught
No modular resources    Resources always from legacy <resources> element        
✅ Implemented (Phase 1 
<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#41-phase-1-module-aware-resource-handling-implemented>)
        N/A
Key insight: Only the mixed modular/non-modular validation (#8) is currently 
caught by the compiler plugin. All other issues require core implementation.

What Works

Processing <source> elements and adding them as source roots
Fallback to legacy <sourceDirectory> when no Java sources in <sources>
Path resolution for both modular and classic layouts
What's Missing

Warnings when explicit configuration is ignored
Validation for mixed modular/non-modular sources (currently deferred to 
compiler plugin)
Correct handling of test-only modular projects (no main injection)
Duplicate detection
Modular resource handling ✅ Implemented in Phase 1 
<http://localhost:63345/markdownPreview/1827621791/markdown-preview-index-9kc3mhenfb2quhf90a0ci0f472.html#41-phase-1-module-aware-resource-handling-implemented>
4. Implementation Approaches

4.1 Phase 1: Module-Aware Resource Handling (Implemented)

Goal: Enable modular resource handling without requiring explicit 
maven-resources-plugin configuration.

Status: ✅ Implemented in 3e2d01f 
<https://github.com/support-and-care/maven/commit/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3>
Implementation:

Resource tracking via <sources>: (Lines 672-673 
<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L672-L673>)
boolean hasMainResources = false;
boolean hasTestResources = false;
for (var source : sources) {
    if (Language.RESOURCES.equals(language)) {
        if (ProjectScope.MAIN.equals(scope)) {
            hasMainResources = true;
        } else {
            hasTestResources |= ProjectScope.TEST.equals(scope);
        }
    }
}
Module extraction to detect modular projects: (Lines 713-714 
<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L713-L714>,
 extractModules() at L1256 
<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L1256-L1272>)
Set<String> modules = extractModules(sources);
boolean isModularProject = !modules.isEmpty();
Module-aware resource injection for modular projects: (Lines 729-770 
<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L729-L770>,
 createModularResourceRoot() at L1275 
<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L1275-L1300>)
If resources configured via <sources>: use them (already added during iteration)
If no resources in <sources>: inject module-aware defaults for each module
Warn (as ModelProblem) if legacy <resources> is present but ignored
if (isModularProject) {
    if (hasMainResources) {
        // Already added via <sources>, warn if legacy <resources> present
    } else {
        // Inject module-aware defaults: src/<module>/main/resources
        for (String module : modules) {
            project.addSourceRoot(createModularResourceRoot(baseDir, module, 
ProjectScope.MAIN, ...));
        }
    }
}
Super POM default detection: (hasOnlySuperPomDefaults() at L1303 
<https://github.com/support-and-care/maven/blob/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java#L1303-L1331>)
hasOnlySuperPomDefaults() checks if <resources> contains only inherited defaults
Warnings are only issued for explicitly configured legacy resources, not Super 
POM defaults
Priority Hierarchy (Proposed):

Priority        Condition       Behavior
1       Modular project + resources in <sources>        Use <sources> 
resources, warn if legacy present
2       Modular project + no resources in <sources>     Inject 
src/<module>/<scope>/resources for each module
3       Classic project + resources in <sources>        Use <sources> 
resources, warn if legacy present
4       Classic project + no resources in <sources>     Use legacy <resources> 
element
Example - Would Work Without Explicit Plugin Configuration:

<build>
  <sources>
    <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>
  </sources>
</build>
Resources would be automatically picked up from:

src/org.foo.moduleA/main/resources
src/org.foo.moduleB/main/resources
What's NOT Addressed in This Proposal:

Problem 1: Mixed modular/non-modular sources validation (still deferred to 
compiler plugin)
Problem 2: Test-only modular project main injection
Problem 4: Warning when explicit <sourceDirectory> is ignored
Duplicate source detection
Per-module tracking (project-level booleans still used)
4.2 Phase 2: Comprehensive Refactoring

Goal: Full implementation of the design principles from Section 2 with proper 
validation.

Scope:

Per-module tracking instead of project-level booleans:
record ModuleConfig(boolean hasMain, boolean hasTest, boolean hasMainResources, 
boolean hasTestResources) {}
Map<String, ModuleConfig> moduleConfigs = new HashMap<>();
Validation in ModelValidator (not DefaultProjectBuilder):
Move conflict detection to validateEffectiveModel()
Report ModelProblem with line numbers and severity
Early feedback during POM processing
Strict validation rules (from Section 2.2):
Rule    Condition       Severity        Message
R1      <sources> + explicit <sourceDirectory>  ERROR   "Cannot combine 
<sources> with explicit <sourceDirectory>"
R2      Mixed modular/non-modular sources       ERROR   "Cannot mix modular and 
non-modular sources"
R3      Duplicate <source> definition   ERROR   "Duplicate source for 
scope='X', lang='Y', module='Z'"
R4      <sources> configured but classic dir exists     WARN    "Directory 
'src/main/java' exists but will be ignored"
Fix test-only modular project injection (Problem 2):
// Don't inject legacy main sources for modular projects
if (!hasMain && !isModularProject) {
    project.addCompileSourceRoot(build.getSourceDirectory());
}
Warning when explicit configuration is ignored (Problem 4):
if (hasMain && isExplicitSourceDirectory(build)) {
    // Report ModelProblem with line number
}
InputLocation-based detection for explicit vs inherited configuration:
InputLocation location = build.getLocation("sourceDirectory");
boolean isExplicit = location != null && !isSuperPomLocation(location);
Implementation Location:

Component       Responsibility
DefaultModelValidator   Validation rules R1-R4, early feedback with line numbers
DefaultProjectBuilder   Source root creation, fallback logic
DefaultSourceRoot       Path resolution (already implemented)
Implementation Timeline

Phase   Scope   Status
Phase 1 Module-aware resource handling  ✅ Implemented (3e2d01f 
<https://github.com/support-and-care/maven/commit/3e2d01f7ffc5cd52cbdbd48a39dd87a3ea9b38b3>)
Phase 2 Full validation + per-module tracking + ModelValidator integration      
Pending
5. Reference Material

Model Version vs Maven Version

Model Version   Maven Version   Notes
4.0.0   Maven 3.x       Classic POM model, no <sources> element
4.1.0   Maven 4.x       Introduces <sources> element
4.2.0   Future  Reserved for future use
The <sources> element requires model version 4.1.0:

<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>
    ...
</project>
The Maven 4.x Unified Source Model

In Maven 4.x, all source types can be configured via <sources>:

<sources>
  <!-- Java sources -->
  <source>
    <scope>main</scope>
    <lang>java</lang>
    <module>org.foo.bar</module>
  </source>
  <!-- Resources -->
  <source>
    <scope>main</scope>
    <lang>resources</lang>
    <module>org.foo.bar</module>
  </source>
</sources>
The <lang> element accepts:

java - compiled by compiler plugin (default if omitted)
resources - processed by resources plugin
script - deprecated, use resources instead
Extensible via LanguageProvider SPI for other languages (Kotlin, Groovy, etc.).

Processing Flow

┌─────────────────────────────────────────────────────────────────┐
│                         POM Model                                │
│  <sources>                                                       │
│    <source><lang>java</lang><module>X</module></source>          │
│    <source><lang>resources</lang><module>X</module></source>     │
│  </sources>                                                      │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    DefaultProjectBuilder                         │
│  Creates SourceRoot objects from <source> elements               │
│  Adds to project.addSourceRoot(...)                              │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      MavenProject                                │
│  getEnabledSourceRoots(ProjectScope scope, Language language)    │
│  - Plugins query this to find relevant sources                   │
└─────────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┴───────────────┐
              ▼                               ▼
┌─────────────────────────┐     ┌─────────────────────────┐
│    Compiler Plugin       │     │    Resources Plugin      │
│  Queries: lang=java      │     │  Queries: lang=resources │
└─────────────────────────┘     └─────────────────────────┘
Detecting Explicit vs Implicit Configuration

To detect whether <sourceDirectory> was explicitly configured (vs inherited 
from Super POM):

// Option 1: Compare to known Super POM default
String superPomDefault = "${project.basedir}/src/main/java";
String resolvedDefault = baseDir.resolve("src/main/java").toString();
boolean isExplicit = !build.getSourceDirectory().equals(resolvedDefault);

// Option 2: Use InputLocation tracking
InputLocation location = build.getLocation("sourceDirectory");
boolean isExplicit = location != null && !isSuperPomLocation(location);
Where to Implement Validation

Option  Location        Pros    Cons
ModelValidator (Recommended)    validateEffectiveModel()        Line numbers, 
early, standard   Values not fully resolved
DefaultProjectBuilder   initProject()   All values resolved     Later, less 
standard
Lifecycle phase validate        -       Too late
Related Code Locations

impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java
impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.0.0.xml (Super 
POM 4.0)
impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.1.0.xml (Super 
POM 4.1)
Open Questions

Should we validate at validateRawModel() or validateEffectiveModel()?
How to reliably detect "explicit" configuration (InputLocation vs value 
comparison)?
Should the filesystem warning (R4) be optional/configurable?

Reply via email to