Skip to content

Fail on legacy config in modular projects#11702

Open
ascheman wants to merge 5 commits intoapache:masterfrom
support-and-care:bugfix/11701-fail-on-modular-sources-with-legacy-dirs
Open

Fail on legacy config in modular projects#11702
ascheman wants to merge 5 commits intoapache:masterfrom
support-and-care:bugfix/11701-fail-on-modular-sources-with-legacy-dirs

Conversation

@ascheman
Copy link
Contributor

@ascheman ascheman commented Feb 6, 2026

NOTE: This summary is somewhat outdated, check for an update comment below, for clarification what this PR finally does.

Summary

  • Legacy directories and resources that conflict with modular sources now trigger an ERROR instead of WARNING
  • Prevents silent loss of user-configured sources/resources
  • AC8 supersedes AC7 which originally used WARNING

Affected configurations in modular projects:

  • 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

Test plan

  • ProjectBuilderTest#testModularSourcesWithExplicitResourcesIssuesError passes
  • ProjectBuilderTest#testMixedSourcesModularMainClassicTest passes

Fixes #11701
See #11701 (comment)

🤖 Generated with Claude Code

@ascheman ascheman force-pushed the bugfix/11701-fail-on-modular-sources-with-legacy-dirs branch from 70a7358 to c1b431b Compare February 6, 2026 10:05
@ascheman ascheman marked this pull request as ready for review February 6, 2026 13:27
@ascheman
Copy link
Contributor Author

ascheman commented Feb 6, 2026

Reviews are welcome, in particular from @elharo and @desruisseaux - thanks!

Copy link
Contributor

@elharo elharo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not obvious to me how claude code is being used here. Generally I would not expect simply pointing it at this issue without human editing to produce correct results.

*
* Additionally, for modular projects, legacy directories are unconditionally
* ignored because it is not clear how to dispatch their content between
* rejected because it is not clear how to dispatch their content between
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is what we were looking for. The build fails completely, not simply a warning

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposal: replace "A warning is emitted if …" by "The build fails if …".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment block has been completely rewritten — the old "warning" wording is gone. The code now raises an ERROR and the comment reflects that.

* </ul>
* Legacy directories are unconditionally ignored in modular projects because it is not clear
* how to dispatch their content between different modules.
* In both cases, the legacy directory conflicts with modular sources and must not be used.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not "and must not be used" but rather the complete build fails.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not "and must not be used" but rather the complete build fails.

Isn't what the next line below is saying?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "must not be used" wording is gone. The code raises an ERROR and the rewritten comment/Javadoc reflects that.

scopeId,
sourcesConfig);
String message = String.format(
"Legacy %s element must not be used because %s resources are configured via %s in <sources>.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must not be used --> cannot be used in modular projects

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in an earlier push.

if (hasExplicitLegacyResources(resources, scopeId)) {
String message = "Legacy " + legacyElement
+ " element is ignored because modular sources are configured. "
+ " element must not be used because modular sources are configured. "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must not --> cannot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in an earlier push.

scopeId,
sourcesConfig);
String message = String.format(
"Legacy %s element must not be used because %s resources are configured via %s in <sources>.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cannot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in an earlier push.

* 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure how the AC are being used, but shouldn't AC7 be changed rather than superseded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As AC7 has already become part of the master branch implementation, I do not want to override it now. I consider it better to invalidate it and write a new one. As they all belong to proper legacy source/resource handling wrt to the new <sources> configuration element, I proceed with the numbering. I am also preparing some overall documentation, covering all aspects (an update/replacement for the Confluence page where it all began.

* 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).
* In modular projects, legacy directories must not occur because it is not clear
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In modular projects, legacy directories must not occur --> Legacy directories cannot be used in modular projects

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in an earlier push.

/*
* `sourceDirectory`, `testSourceDirectory` and `scriptSourceDirectory`
* are ignored if the POM file contains at least one enabled <source> element
* are not used if the POM file contains at least one enabled <source> element
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again here don't we need the build to fail in this case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that it is a change that was forgot in the documentation. The actual code raises an error as I read it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this more closely, I think we've uncovered an inconsistency:

Current behavior:

  • Resources (<resources>/<testResources>): ERROR for both modular and classic projects when <sources> conflicts with legacy config (handled in SourceHandlingContext)
  • Source directories (<sourceDirectory>/<testSourceDirectory>): ERROR only for modular projects, silently skipped for classic projects

AC8 specifically scopes the error behavior to modular projects:

"Legacy directories trigger ERROR in modular projects"

This leaves classic projects with <sources> in an inconsistent state - their legacy resources cause an error, but legacy source directories are silently ignored.

Proposal: If @desruisseaux and @elharo agree, we could add:

AC9: For classic (non-modular) projects, legacy <sourceDirectory> and <testSourceDirectory> also trigger an ERROR when <sources> elements are configured for the same scope/language. This ensures consistency with resource handling and prevents silent configuration loss.

Should I create a follow-up issue for this, or would you prefer to extend the current PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for spotting that. I suggest to extend the current pull request if this is fine for you.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fully up to speed on all of this. I try to avoid modules. However, the general principle I would follow is that if the client project contains code or resources that the developer likely intended to be included and for some reason we are not including it, then Maven should hard fail the build.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems very good to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems very good to me.

OK, I will implement it this way then.

Copy link
Contributor Author

@ascheman ascheman Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yet another use case: Assume we have the following structure:

<build>
    <sources>
      <source>
        <scope>main</scope>
        <lang>resources</lang>
        <directory>src/main/custom-resources</directory>
      </source>
      <!-- No <source lang="java"> - expects implicit src/main/java? -->
    </sources>
  </build>

I would think in a legacy directory structure but 4.1.0-model configuration (but no modules), the legacy directories should still be (implicitly) used, unless explicitly overridden. What do you think, @desruisseaux ?

Said that: the classic values were defined as default by the super POM. Do we expect similar defaults by the super POM for <sources> as well (currently not configured)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, when the <module> element is not used I think that we can continue to use the legacy directories.

Regarding the default values specified in the super POM, I suggest to not do any change for now (we may revisit in some future version). The default values of <source> elements are implemented in Java code for now, and they are designed in such a way that, when no <module> element is used, they match the default values of the super POM.

Copy link
Contributor Author

@ascheman ascheman Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I fully agree that we should not require something on the Super POM by now but just use the classic directories as fallback if nothing is explicitly configured. Nevertheless, I will create a new Acceptance Criterion to express that design choice - we are back at (another) AC9 then 😉:

AC9 Definition (Non-Modular Projects)

AC9 (Legacy directory handling for non-modular projects):

For non-modular projects using <sources>:

  1. When <sources> has Java for a scope: Legacy directories are rejected if they differ from the default (explicit configuration detected)
  2. When <sources> has no Java for a scope: Legacy directories are used as implicit fallback only if they match the default (could be inherited)

I already made some progress implementing and testing this and will deliver test cases for both AC8 and AC9

@ascheman
Copy link
Contributor Author

AC8 and AC9: Legacy Directory Handling with <sources>

Summary

This PR meanwhile covers two related acceptance criteria for handling legacy directories when <sources> is configured (cf. #11701 / AC8+9):

  • AC8 (Modular projects): ALL legacy directories are rejected
  • AC9 (Non-modular projects): Implicit fallback to legacy directories when <sources> doesn't configure that scope/language

Background

Originally AC8 was proposed to reject legacy directories in modular projects because content cannot be dispatched between modules. However, for non-modular projects using <sources>, there's a valid use case for incremental adoption: customize only specific source types (e.g., resources) while keeping the default Java directory structure.

AC8 Definition (Modular Projects)

AC8 (Legacy directory rejection for modular projects):

For modular projects (projects with <module> attribute in <source> elements), the build fails with an ERROR if:

  1. An explicit legacy directory configuration (<sourceDirectory> or <testSourceDirectory>) exists that differs from the Super POM default, OR

  2. The default legacy directory (e.g., src/main/java) exists physically on the filesystem

Rationale

In modular projects using paths like src/<module>/main/java, legacy directories like src/main/java cannot be dispatched between modules. Any content there would be silently ignored, so the build must fail.

AC9 Definition (Non-Modular Projects)

AC9 (Legacy directory handling for non-modular projects):

For non-modular projects using <sources>:

  1. When <sources> has Java for a scope: Legacy directories are rejected if they differ from the default (explicit configuration detected)
  2. When <sources> has no Java for a scope: Legacy directories are used as implicit fallback only if they match the default (could be inherited)

Behavior

<sources> contains Legacy directory Result
<source><lang>java</lang><scope>main</scope></source> <sourceDirectory> differs ERROR
<source><lang>java</lang><scope>main</scope></source> <sourceDirectory> default OK (ignored)
<source><lang>resources</lang><scope>main</scope></source> only <sourceDirectory> differs ERROR
<source><lang>resources</lang><scope>main</scope></source> only <sourceDirectory> default OK (used as fallback)

Rationale

This allows incremental adoption of <sources>:

  • Users can customize only specific source types (e.g., resources)
  • Default Java directories continue to work without explicit configuration
  • Explicit legacy overrides (differ from default) always trigger errors - this is detectable and indicates mixing of approaches

Example

<build>
  <sources>
    <!-- Only customize resources, keep default Java directories -->
    <source>
      <lang>resources</lang>
      <scope>main</scope>
      <directory>src/main/custom-resources</directory>
    </source>
  </sources>
  <!-- src/main/java is used implicitly (AC9) -->
</build>

Complete Test Matrix

# Project Type <sources> has Java? Legacy differs? Physical exists? Result AC Test Project Test Method
1 Classic N/A Any Any OK - - (default behavior)
2 Modular Yes Yes Any ERROR AC8 modular-java-with-explicit-source-dir testModularWithJavaSourcesRejectsLegacySourceDirectory
3 Modular Yes No Yes ERROR AC8 modular-with-physical-legacy testModularWithPhysicalDefaultLegacyDirectory
4 Modular Yes No No OK AC8 - (happy path)
5 Modular No Yes Any ERROR AC8 modular-no-test-java-with-explicit-test-source-dir testModularWithoutTestSourcesRejectsLegacyTestSourceDirectory
6 Modular No No Yes ERROR AC8 modular-resources-only-with-physical-legacy testModularResourcesOnlyWithPhysicalDefaultLegacyDirectory
7 Modular No No No OK AC8 - (happy path)
8 Non-modular Yes Yes Any ERROR AC9 classic-sources-with-explicit-legacy testClassicSourcesWithExplicitLegacyDirectories
9 Non-modular Yes No Any OK AC8 - (happy path)
10 Non-modular No Yes Any ERROR AC9 non-modular-resources-only-explicit-legacy testNonModularResourcesOnlyWithExplicitLegacyDirectoriesRejected
11 Non-modular No No Any OK (fallback) AC9 non-modular-resources-only testNonModularResourcesOnlyWithImplicitJavaFallback

Legend

  • Project Type:

    • Classic: No <sources> element configured
    • Modular: <sources> has <source> elements with <module> attribute
    • Non-modular: <sources> has <source> elements without <module> attribute
  • <sources> has Java?: Whether <sources> contains <source><lang>java</lang>...</source> for the scope being validated

  • Legacy differs?: Whether <sourceDirectory>/<testSourceDirectory> differs from Super POM default (src/main/java/src/test/java)

    Limitation: Maven works with the effective model after inheritance resolution. It cannot distinguish whether a value matching the default was inherited (from Super POM or any parent) or explicitly configured. For modular projects, the physical presence check provides an additional safeguard.

  • Physical exists?: Whether the default legacy directory physically exists on the filesystem (only checked for modular projects)

Test Coverage Summary

Category Tests
AC8 - Modular with explicit legacy testModularWithJavaSourcesRejectsLegacySourceDirectory, testModularWithoutTestSourcesRejectsLegacyTestSourceDirectory
AC8 - Modular with physical presence testModularWithPhysicalDefaultLegacyDirectory, testModularResourcesOnlyWithPhysicalDefaultLegacyDirectory
AC9 - Non-modular with Java in sources (legacy rejected) testClassicSourcesWithExplicitLegacyDirectories
AC9 - Non-modular without Java in sources (implicit fallback) testNonModularResourcesOnlyWithImplicitJavaFallback
AC9 - Non-modular without Java in sources (explicit rejected) testNonModularResourcesOnlyWithExplicitLegacyDirectoriesRejected

Implementation

The implementation uses:

  1. failIfLegacyDirectoryPresent method with checkPhysicalPresence parameter:

    • true for modular projects (checks both config and filesystem)
    • false for non-modular projects (checks only explicit config)
  2. Conditional validation:

    • Modular: Always validate both <sourceDirectory> and <testSourceDirectory>
    • Non-modular: Validate each scope independently:
      • <sourceDirectory> is validated only if hasSources(JAVA_FAMILY, MAIN) returns true
      • <testSourceDirectory> is validated only if hasSources(JAVA_FAMILY, TEST) returns true
  3. Implicit fallback:

    • Non-modular without Java in <sources>: call addCompileSourceRoot()/addTestCompileSourceRoot()

Code Structure

if (sources.isEmpty()) {
    // Classic: use all legacy directories
    project.addScriptSourceRoot(build.getScriptSourceDirectory());
    project.addCompileSourceRoot(build.getSourceDirectory());
    project.addTestCompileSourceRoot(build.getTestSourceDirectory());
    sourceContext.handleResourceConfiguration(MAIN);
    sourceContext.handleResourceConfiguration(TEST);
} else {
    // Source roots from <sources> are added by SourceHandlingContext.processSourceElements()
    if (isModularProject) {
        // AC8: reject ALL legacy directories (both scopes)
        // (source roots come exclusively from <sources> elements)
        failIfLegacyDirectoryPresent(sourceDirectory, ..., checkPhysicalPresence=true);
        failIfLegacyDirectoryPresent(testSourceDirectory, ..., checkPhysicalPresence=true);
    } else {
        // Non-modular: always validate (error if differs from default)
        failIfLegacyDirectoryPresent(sourceDirectory, ..., checkPhysicalPresence=false);
        failIfLegacyDirectoryPresent(testSourceDirectory, ..., checkPhysicalPresence=false);

        // AC9 fallback: only if no Java in <sources> AND legacy matches default
        if (!hasSources(JAVA_FAMILY, MAIN) && matchesDefault(sourceDirectory)) {
            project.addCompileSourceRoot(build.getSourceDirectory());
        }
        if (!hasSources(JAVA_FAMILY, TEST) && matchesDefault(testSourceDirectory)) {
            project.addTestCompileSourceRoot(build.getTestSourceDirectory());
        }
    }
}

Related

ascheman and others added 3 commits February 14, 2026 12:16
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.
AC8 supersedes AC7 which originally used WARNING.

Fixes apache#11701
See apache#11701 (comment)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use "cannot" instead of "must not" in error messages
- Update Javadoc: "The build fails" instead of "A warning is emitted"
AC8 (modular projects):
- Reject all legacy directories when <sources> is configured
- Check physical presence of default directories (src/main/java)

AC9 (non-modular projects):
- Reject legacy directories when <sources> has Java for that scope
- Allow implicit fallback only when legacy matches default

Split test projects for clearer scenario coverage.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ascheman ascheman force-pushed the bugfix/11701-fail-on-modular-sources-with-legacy-dirs branch from 4cfb6e9 to befcf4a Compare February 14, 2026 11:18
Use File.separator in test assertions for platform-independent
path matching instead of normalizing paths in production code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@desruisseaux
Copy link
Contributor

@elharo, @gnodet or others, are we ready to merge?

Copy link
Contributor

@elharo elharo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there documentation for this somewhere? I feel like I'm trying to read the tea leaves to understand what's going on. I'm missing the big picture.

In particular I would like to understand how sources are expected to be laid out in the file system and how that maps to different elements in pom.xml.

I'm beginning to worry that the design for this is much more complex and error prone than it needs to be. Why are we failing when there;s a src/main/.java present? Isn't that just the unnamed module?

*
* 2. MODULAR projects (have <module> in <sources>):
* - ALL legacy directories are rejected (can't dispatch between modules)
* - Physical presence of default directories (src/main/java) also triggers ERROR
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could well be missing something since I avoid modules like the plague, but why doesn't this go into the unnamed module?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concept of unnamed modules usually applies to dependencies: a JAR placed on the class-path is an unnamed module. This concept does not really applies to the sources to compile, unless we interpret "unnamed module" as "sources without module-info".

We cannot mix sources for Java modules and sources without module-info in the same compilation unit. Either the Maven sub-project is fully modular, or either it does not use module source hierarchy at all. From javac man page about the --source-path and --module-source-path options:

You can only specify one of these options: if you are not compiling code for a module, or if you are only compiling code for a single module, use the source path; if you are compiling code for multiple modules, use the module source path.

Therefore, a Maven project using <module> cannot accept unnamed module. If a developer wants an unnamed module, it must be a separated Maven sub-project.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is the rule one maven project/subproject per module? My mental model is that there are sources for each module, including the unnamed module. The pom is describing how to produce jars. There is, I think, a 1:1 relationship between jars and modules although there can be multiple jars for the unamed/no-module case. So is the pom describing how to build one exactly one jar for one module?

Again, documentation -- not code -- could help a lot here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the new proposed terminology where "module" = "Java module" and "sub-project" = "what we were used to call module in Maven 3", then:

  • Yes, there is a 1:1 relationship between JARs and modules, i.e. Java modules.
    • This is true in all circumstances, both package hierarchy and module hierarchy.
  • There is a 1:1 relationship between Maven sub-projects and Java modules when package hierarchy is used.
  • Therefore, there is a 1:1 relationship between Maven sub-projects and JARs when package hierarchy is used.
  • There is no longer a 1:1 relationship between Maven sub-project and Java modules when module hierarchy is used: a single Maven sub-project can create many Java modules.
  • Therefore, there is no longer a 1:1 relationship between Maven sub-project and JARs when module hierarchy is used: a single Maven sub-project can create many JARs.

Package hierarchy is the classical layout of Java source files: package/class.java. Module hierarchy is the same as package hierarchy, but prefixed with the module name: module/package/class.java. Modules hierarchy are supported by java, javac, javadoc and some other tools (this is not a concept invented by Maven), sometime with distinct options such as --source-path versus --module-source-path (for package and module hierarchy respectively).

In Maven, the rule is straightforward: if the <source> elements contain a <module> child element, then the sub-project uses module hierarchy. Otherwise, the sub-project uses package hierarchy. There is no special cases or other complexity, the rule is as straightforward as that.

But another rule is that we cannot mix the two hierarchies: each sub-project must choose one or the other (but different Maven sub-projects can use different hierarchy). However, that latter rule was not enforced by Maven core. If a sub-project breaks that rule, the problem was detected only in the compiler plugin, sometime only when that plugin tries to pass options to javac. On the principle that errors are easier to resolve when they are detected early, this pull request tries do detect in advance sub-projects that would fail to build.

This pull request is not technically mandatory: we could perform no verification in Maven core and wait for the Maven Compiler Plugin to fail. This is an attempt to help the users with earlier error report, and also because Maven core has information that the Maven Compiler Plugin does not have: it is possible in Maven core to report the exact line in the pom.xml where the problem is detected (although the current pull request does no go that far, but it can be improved in future cycles).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is useful information. I will repeat that this should be in the docs. I still don't have a full picture in my head of what's in the pom.xml and how that now relates what files are where in the project. I don't think we need to think about subprojects just yet. If the docs explained how a single project with two named modules and code in the unnamed module needs to be set up, that probably covers 90% of the complexity. Part of this might be the JPMS version of

https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html

Does this exist yet?

The other part would be the JPMS version of the Maven POM reference: https://maven.apache.org/pom.html

Does this exist yet?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some documentation of the compiler plugin 4.x cover source directory layout, in particular:

This documentation has not yet been reflected in the main Maven documentation such as "Introduction to the Standard Directory Layout". A reason is that while the work is completed on the compiler plugin side it is not finished on JAR plugin side (pull request ready, waiting for the next Maven 4 release before to submit for merge) and Surefire plugin side (waiting for the ongoing big simplification).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I think I'm starting to see the issue. But I'm not yet convinced that having a legacy source base and a modular source base si a deal breaker. Could we run the compiler twice? Once to produce the no-module jar and once to produce the modularized jars passing different sources each time?

It is possible to have Java projects that contain both modular and non-modular jars on the classpath/module-path. So are we requiring that these different jars be in separate Maven projects?

Traditionally a Maven pom.xml produced exactly one library jar (and maybe a test jar, source jars, and so forth but we can ignore that for now). Now with modules we have one pom.xml producing multiple jars. So why can't we take the legacy code in the traditional src/main/java location and put it in the unnamed module? That is, why are ALL legacy directories rejected in a modular project?

Copy link
Contributor Author

@ascheman ascheman Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, thats the point: The compiler cannot handle modular and non-modular sources at the same time. We would need two compile passes.

Therefore, we designed it in a way that you have to choose, either this way or the other (but still with as much convention over configuration as possible - the good old Maven way). If you want both, make two (or more) <subprojects> (known as Maven <modules> up to Maven 3). This keeps concerns separated and (rather) simple. Imagine the complexity we need if we want to combine both approaches. It is already hard to handle (and describe) as we see here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we compile a multi-module project by a single call to javac, we let the Java compiler manages the dependencies between modules. This is one of the reasons for supporting this feature, because Maven dependencies are restricted to acyclic graphs while Java module dependencies accept a limited form of cyclic dependencies (e.g. in qualified exports).

If we run the compiler twice for producing the no-module JAR, we would have to manage its dependency relatively to the modular JARs. Do we compile the legacy source base before the modules, then add the legacy output on the class-path of all modules? Or do we compile the modules first, then add them on the module-path for compiling the legacy source? In the latter case, do we add all modules or a subset of them? There is no module-info for telling us which subset of the pom.xml dependencies is effective for the legacy JAR.

It was a choice to stick to "one sub-project = one compilation unit" for the main code (ignoring tests and multi-release for this discussion). By requiring legacy source base to be compiled in its own Maven sub-project, we let the developers decide if the legacy source base depends on the modules or the converse, which modules, whether a module is optional, and whether to place them on the class-path or module-path. By contrast, if the Maven Compiler Plugin did two compilations for supporting modular sources and legacy sources in the same sub-project, we would be forced to take many arbitrary decisions.

* - Physical presence of default directories (src/main/java) also triggers ERROR
*
* 3. NON-MODULAR projects with <sources>:
* - Explicit legacy directories (differ from default) are always rejected
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is current behavior, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If by current behaviour we mean Maven 3 behaviour, the current behaviour is to take the source directories from the following elements: <sourceDirectory>, <scriptSourceDirectory>, <testSourceDirectory>, <resources>, <testResources>. This is what we mean by "legacy directories". The <source> element did not existed in Maven 3.

The new <source> element provides an unified handling capable to replace all the legacy elements, and more. But we think that if a developer mixes the legacy elements with the new <source> element, while it would be technically possible to merge the legacy elements with the <source> elements, such mix is more likely to be a configuration error. This is what this item tries to catch.

For projects that do not use the new <source> at all, nothing changed.

*
* 3. NON-MODULAR projects with <sources>:
* - Explicit legacy directories (differ from default) are always rejected
* - If <sources> has Java for a scope: legacy is not used (even if matching default)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bullet point needs to be rewritten. I cannot parse it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded in a5ad905. Changed to "Legacy directories for scopes where <sources> defines Java are ignored". Also dropped leading asterisks from the block comment per your suggestion.

* 3. NON-MODULAR projects with <sources>:
* - Explicit legacy directories (differ from default) are always rejected
* - If <sources> has Java for a scope: legacy is not used (even if matching default)
* - If <sources> has no Java for a scope: legacy is used as implicit fallback
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same commit (a5ad905). Reworded to "Legacy directories for scopes where <sources> has no Java serve as implicit fallback (only if they match the default, e.g., inherited)".

* ignored because it is not clear how to dispatch their content between
* different modules. A warning is emitted if these properties are explicitly set.
* 1. CLASSIC projects (no <sources>):
* - All legacy directories are used
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably need to use HTML in the javadoc comments for lists

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not Javadoc, but an ordinary comment inside the method body. The comment starts with /* instead of /**, which tells the compiler that this is not javadoc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, In that case I suggest dropping all the preceding asterisks that make it look like Javadoc. They actually make the text harder to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in a5ad905. Dropped leading asterisks from the block comment.

* <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><b>Configuration presence</b>: an explicit legacy configuration differs from the default</li>
Copy link
Contributor

@elharo elharo Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably don't use <b> here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced <b> with <strong> for better semantics in a5ad905.

- Drop leading asterisks from block comment to distinguish from Javadoc
- Reword bullet points for non-modular source handling clarity
- Replace <b> with <strong> in Javadoc for better semantics

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Legacy resources in modular build should fail

3 participants