You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #995 (the module-info module rules: requireExplicitModules, requireMinimalExports, banUnjustifiedOpens).
Problem
The Java module system requires every package to belong to exactly one module. When two modules on the module path
contain the same package, the boot layer refuses to resolve:
Such a split package is easy to introduce by accident — most often when a build-time code generator, an unpacked or
shaded dependency, or a copied resource puts classes of a foreign package into the current project's output. The
failure surfaces late (at packaging, jlink, or first boot), and the message describes the symptom rather than the
build step that caused it.
Two further shapes make it worse:
Silent variant. If the project does not requires the split module, the resolver never pulls it in — so it
never complains. The build is green and the application boots, but a type or an endpoint that was supposed to come
from that module is silently absent (a 404, a late LayerInstantiationException, a NoClassDefFoundError).
On the classpath. A split package is legal there and the JVM never complains at all — but it is precisely what
will break a later migration to the module path.
There is currently no Enforcer rule that catches any of these at build time.
Proposed rule: banSplitPackages
Primary target is the module path, but the rule is useful on the classpath too (as a modularization aid), so
it covers both. Bind it after compilation (e.g. process-classes, or verify); it fails or warns when a package
produced by the current project also belongs to one of its dependencies.
Detection:
Project packages — enumerate the packages actually present under the compiled output directory (the
directories that hold .class files). This is intentionally based on the real output, not the compiled module-info descriptor: the offending classes are frequently added aftercompile (generation, copy), and the
descriptor's ModulePackages attribute is only finalized at packaging time.
Dependency packages — resolve the project's dependency artifacts with java.lang.module.ModuleFinder, whose ModuleReference.descriptor().packages() yields the full package set and transparently handles automatic modules;
fall back to scanning jar entries for plain (non-modular) artifacts.
Intersect against the full declared-dependency set, not only the modules the project requires. This is what
lets the rule catch the silent variant that the resolver misses: the split provider is a real Maven dependency,
so it is in the set even when the module graph would never pull it in.
Report every package owned by more than one place, naming both owners.
The rule handles the Maven 4 module source hierarchy (POM model 4.1.0), where one project compiles several modules,
each to ${project.build.outputDirectory}/<module-name>/.
Severity model
The severity depends on how real the problem is, and is configurable:
Overlap kind
Default
Rationale
Project module vs. a dependency module (incl. the silent, not-requiresd case)
error
An actual ResolutionException (or a silent missing type) — a real defect.
Any overlap involving a non-modular / classpath artifact
warn
Legal on the classpath today; matters only for a future move to modules — flag it, don't fail.
The classpath check is on by default at warn (at least a warning is emitted); set classpathSeverity=ignore
to opt out entirely, or error to enforce module-readiness strictly. The module-path/severity defaults can still be
lowered globally through the plugin's per-rule <level>.
Configuration
Parameter
Default
Meaning
classpathSeverity
warn
Severity for overlaps involving non-modular artifacts: warn, error, or ignore (opt-out). Module-vs-module overlaps are always errors.
allowedSplitPackages
–
Packages permitted to overlap (escape hatch).
ignoredModules / ignoredArtifacts
–
Dependencies to exclude from the check.
message
–
Custom failure message.
Relationship to existing rules
Complements MojoHaus banDuplicateClasses (the classpath duplicate-class check); this rule is the package-level counterpart and is module-aware. The real classpath bug is a duplicate class (handled there); a
split package without a duplicate class is the module-migration hazard this rule surfaces.
A guard is a safety net, not a fix: the durable cure is placing generated or copied classes in the module that
owns their package.
Only declared dependencies are inspected. A provider pulled in purely at run time (not a Maven dependency)
cannot be seen at build time and is out of scope; an additionalModulePath / scanArtifacts parameter could let
advanced users feed such paths in.
On a large legacy classpath the warn default may be chatty; allowedSplitPackages / ignoredArtifacts / classpathSeverity=ignore are the escape hatches.
Follow-up to #995 (the
module-infomodule rules:requireExplicitModules,requireMinimalExports,banUnjustifiedOpens).Problem
The Java module system requires every package to belong to exactly one module. When two modules on the module path
contain the same package, the boot layer refuses to resolve:
Such a split package is easy to introduce by accident — most often when a build-time code generator, an unpacked or
shaded dependency, or a copied resource puts classes of a foreign package into the current project's output. The
failure surfaces late (at packaging,
jlink, or first boot), and the message describes the symptom rather than thebuild step that caused it.
Two further shapes make it worse:
requiresthe split module, the resolver never pulls it in — so itnever complains. The build is green and the application boots, but a type or an endpoint that was supposed to come
from that module is silently absent (a 404, a late
LayerInstantiationException, aNoClassDefFoundError).will break a later migration to the module path.
There is currently no Enforcer rule that catches any of these at build time.
Proposed rule:
banSplitPackagesPrimary target is the module path, but the rule is useful on the classpath too (as a modularization aid), so
it covers both. Bind it after compilation (e.g.
process-classes, orverify); it fails or warns when a packageproduced by the current project also belongs to one of its dependencies.
Detection:
directories that hold
.classfiles). This is intentionally based on the real output, not the compiledmodule-infodescriptor: the offending classes are frequently added aftercompile(generation, copy), and thedescriptor's
ModulePackagesattribute is only finalized at packaging time.java.lang.module.ModuleFinder, whoseModuleReference.descriptor().packages()yields the full package set and transparently handles automatic modules;fall back to scanning jar entries for plain (non-modular) artifacts.
requires. This is whatlets the rule catch the silent variant that the resolver misses: the split provider is a real Maven dependency,
so it is in the set even when the module graph would never pull it in.
The rule handles the Maven 4 module source hierarchy (POM model 4.1.0), where one project compiles several modules,
each to
${project.build.outputDirectory}/<module-name>/.Severity model
The severity depends on how real the problem is, and is configurable:
requiresd case)ResolutionException(or a silent missing type) — a real defect.The classpath check is on by default at
warn(at least a warning is emitted); setclasspathSeverity=ignoreto opt out entirely, or
errorto enforce module-readiness strictly. The module-path/severity defaults can still belowered globally through the plugin's per-rule
<level>.Configuration
classpathSeveritywarnwarn,error, orignore(opt-out). Module-vs-module overlaps are always errors.allowedSplitPackagesignoredModules/ignoredArtifactsmessageRelationship to existing rules
banDuplicateClasses(the classpath duplicate-class check); this rule is thepackage-level counterpart and is module-aware. The real classpath bug is a duplicate class (handled there); a
split package without a duplicate class is the module-migration hazard this rule surfaces.
module-inforules proposed in New rules: enforce Java Module System descriptor policy #995 (requireExplicitModules,requireMinimalExports,banUnjustifiedOpens) and can reuse theirmodule-inforeader.Notes and non-goals
owns their package.
cannot be seen at build time and is out of scope; an
additionalModulePath/scanArtifactsparameter could letadvanced users feed such paths in.
warndefault may be chatty;allowedSplitPackages/ignoredArtifacts/classpathSeverity=ignoreare the escape hatches.Motivation
This surfaced from a real multi-module modular application: a code generator wrote classes into a consumer module's
output, producing a split package that failed
jlinkand boot — and, in one configuration, silently returned404 because the affected module was never resolved. Write-up:
https://vidocq.dev/posts/multi-module-jpms-apps-on-vidocq-one-opens-one-split-package-and-a-fix/.
I'm happy to contribute the implementation.