Skip to content

[MNG-11642] Add JPMS module support to Maven 4#12135

Draft
gnodet wants to merge 1 commit into
masterfrom
plume-football
Draft

[MNG-11642] Add JPMS module support to Maven 4#12135
gnodet wants to merge 1 commit into
masterfrom
plume-football

Conversation

@gnodet
Copy link
Copy Markdown
Contributor

@gnodet gnodet commented May 20, 2026

Summary

  • Add explicit module-info.java to 17 modules (11 API + 6 impl) with proper requires, exports, uses, and provides directives
  • Add Automatic-Module-Name manifest entries to all 17 remaining modules (4 impl + 13 compat) via maven-jar-plugin configuration in root POM
  • Use qualified exports for all non-API impl packages — only org.apache.maven.api.* is public API
  • Configure javadoc plugin for JPMS compatibility (legacyMode for aggregate reports, source exclusions, --add-exports for plexus-interpolation)
  • Fix nullable DI collection handling in DefaultModelProcessor and DefaultModelBuilder
  • Resolves Assign automatic module names to core Maven components #11642

Details

Modules with module-info.java (17)

API modules (11): maven-api-annotations, maven-api-di, maven-api-xml, maven-api-metadata, maven-api-model, maven-api-settings, maven-api-toolchain, maven-api-plugin, maven-api-core, maven-api-spi, maven-api-cli

Impl modules (6): maven-di, maven-xml, maven-support, maven-impl (open module), maven-jline, maven-logging

Modules with Automatic-Module-Name only (17)

Impl (4): maven-core, maven-cli, maven-executor, maven-testing — cannot use module-info.java due to split packages with compat modules or complex DI/compat dependencies

Compat (13): All compat modules — split packages among themselves prevent named module status

Qualified exports for non-API packages

Only org.apache.maven.api.* packages are Maven 4's public API. All implementation packages use qualified exports restricted to internal consumers:

  • maven-impl: All 14 org.apache.maven.impl.* exports qualified to maven-core, maven-cli, maven-testing, maven-compat, maven-embedder. Removed unused org.apache.maven.impl.model.profile. Only org.apache.maven.api.services.model remains unqualified (it's an API package).
  • maven-di: org.apache.maven.di.impl qualified to internal consumers (kept org.apache.maven.di unqualified as DI API)
  • maven-logging: org.apache.maven.slf4j qualified to maven-cling, maven-embedder, maven-core
  • maven-jline: org.apache.maven.jline qualified to maven-logging, maven-cling, maven-embedder
  • maven-xml, maven-support: left unqualified (widely used by 5-15+ modules; qualifying would be verbose and fragile)

Since all consumers currently compile in classpath mode (no module-info.java), the qualified exports serve as documentation of intent and will be enforced when those modules gain module descriptors.

Build infrastructure changes

  • Root POM: maven-jar-plugin in <pluginManagement> sets Automatic-Module-Name from ${javaModuleName} property
  • Root POM: maven-javadoc-plugin configured with sourceFileExcludes for module-info.java to prevent javadoc tool from switching to modular mode
  • Root POM (reporting profile): legacyMode=true for aggregate javadoc — forces classpath mode to avoid "named and unnamed modules" conflict
  • maven-compat, maven-model-builder: --add-exports=org.codehaus.plexus.interpolation/org.codehaus.plexus.interpolation.util for javadoc — plexus-interpolation 1.29 does not export the .util package in its module descriptor
  • maven-impl, maven-di, maven-jline: annotationProcessorPaths for DiIndexProcessor — required when compiling with module-info.java since the processor is no longer on the classpath
  • maven-impl: useModulePath=false in surefire to preserve existing test behavior on the classpath

Bug fixes included

  • DefaultModelProcessor / DefaultModelBuilder: DI framework injects null for @Nullable collections when no providers exist; added null-safe defaults (Map.of() / List.of())
  • LookupInvoker: Reordered ProjectBuildLogAppender creation to occur before terminal initialization
  • maven-jline: Removed requires static org.jline.terminal.ffm — the FFM terminal provider is discovered via ServiceLoader at runtime, and the requires static directive fails compilation on Java 17 CI where the module doesn't exist

Notable design decisions

  • maven-impl is an open module because Maven's DI framework needs reflective access to instantiate components
  • maven-cli uses Automatic-Module-Name instead of module-info.java due to split packages with compat modules it depends on (e.g., org.apache.maven.settings in both maven-settings and maven-core)
  • API modules use requires transitive for inter-API dependencies since API types leak across module boundaries
  • maven-api-model declares uses ModelObjectProcessor for ServiceLoader support
  • maven-impl declares uses RootDetector for ServiceLoader support

Unrelated fix

  • AbstractUpgradeGoal: Fixed domtrip API change children()childElements() (pre-existing on master after domtrip bump to 1.5.1)

Test plan

  • mvn verify -B — all modules compile and tests pass
  • CI: initial-build, all 9 integration-tests, all 9 full-build (including mvn site -Preporting) pass
  • Verified jar --describe-module shows correct module descriptors for all 17 module-info modules
  • Verified Automatic-Module-Name manifest entries present in all 17 remaining module JARs

🤖 Generated with Claude Code

@desruisseaux
Copy link
Copy Markdown
Contributor

Thanks. There are a lot of export, for example in this implementation module. Are all these exported packages public API? If not, are there some exports that we could omit, or restrict to qualified exports?

@gnodet
Copy link
Copy Markdown
Contributor Author

gnodet commented May 21, 2026

Claude Code on behalf of Guillaume Nodet

Good point. None of the org.apache.maven.impl.* packages are public API — only org.apache.maven.api.* is. I've pushed a commit (252ab5e) that addresses this:

maven-impl:

  • exports org.apache.maven.api.services.model — kept unqualified (it's an API package)
  • exports org.apache.maven.impl.model.profile — removed entirely (not used by any external module)
  • All 14 other org.apache.maven.impl.* exports — now qualified to org.apache.maven.core, org.apache.maven.cling, org.apache.maven.testing, org.apache.maven.compat, org.apache.maven.embedder

Other impl modules:

  • maven-di: qualified org.apache.maven.di.impl to the same 5 internal consumers (kept org.apache.maven.di unqualified as it's the DI API)
  • maven-logging: qualified org.apache.maven.slf4j to maven-cling, maven-embedder, maven-core
  • maven-jline: qualified org.apache.maven.jline to maven-logging, maven-cling, maven-embedder

Left unchanged:

  • API modules (11): all exports are org.apache.maven.api.* — they are the public API
  • maven-xml: single package, used by 5+ modules
  • maven-support: 6 generated-model packages used by 15+ modules — qualifying would be very verbose and fragile

Since all consumers currently use Automatic-Module-Name (no module-info.java), they compile in classpath mode where JPMS boundaries aren't enforced. The qualified exports serve as documentation of intent and will be enforced if/when those modules gain their own module-info.java.

Add explicit module-info.java to 17 modules (11 API + 6 impl) and
Automatic-Module-Name manifest entries to all 17 remaining modules.

Only org.apache.maven.api.* packages are public API. All implementation
packages use qualified exports restricted to internal consumers.

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.

Assign automatic module names to core Maven components

2 participants