Chapter 09Lesson 03~135 minutes

Maven Multi-Module Projects, Parent POMs, Aggregation, Inheritance, and Reactor Builds: Configuration, Design Choices, and Tradeoffs

Choose deliberately between combined or separate parent/aggregator roles, shallow or deep module structures, centralized management or module autonomy, and full versus targeted reactor builds.

ArchitectureCorporate ParentModule OwnershipCI ScopeTradeoffs

Learning objectives

  • Evaluate combined versus separate parent/aggregator architectures.
  • Choose module depth and ownership boundaries based on coupling and observable build behavior.
  • Balance centralized dependency/plugin management against module autonomy.
  • Design targeted CI scope without sacrificing clean-room correctness.
  • Use a decision table to justify a multi-module structure in operational terms.
Current baseline — verified 2026-08-23. Labs use Maven 3.9.16 via Maven Wrapper 3.3.4, JDK 21 to run Maven, Java 17 as the compiler release target, Help Plugin 3.5.2, Dependency Plugin 3.11.0, Compiler Plugin 3.15.0, Surefire 3.5.6, and JAR Plugin 3.5.1. Every lab uses a project-local isolated Maven repository. No normal ~/.m2, global settings, production repository, or CI configuration is modified.

1. Start with the two roles: collection and policy

An aggregator answers “what projects are built together from this root?” A parent answers “what model defaults and policies does this child inherit?” Combining them is convenient, but it couples repository topology to policy ownership. Separating them can make enterprise policy reusable across repositories, but external parent resolution becomes another supply-chain dependency.

Pattern Strength Cost
One root is parent + aggregator Simple navigation; one version/policy point; easy local reactor. Repository topology and shared policy evolve together.
Local aggregator + external corporate parent Repository can select modules locally while policy is shared across many repos. Parent availability/version/repository policy become critical build inputs.
Aggregator only; modules have independent parents Useful for heterogeneous projects collected for one build. Harder to reason about consistent plugin/dependency/toolchain policy.
Parent only; no modules Reusable policy/BOM-style parent across independent builds. Does not provide a reactor or cross-module source build.

2. Prefer ownership boundaries over decorative hierarchy

Deep module trees are not inherently more “enterprise.” Each level can add parent/aggregator indirection, relative-path complexity, inherited configuration, and reactor selection ambiguity. Use nested aggregators when they represent a real ownership or build boundary, not to mimic Java package names.

Signal Flatter structure Nested structure
Few tightly coupled modules Usually easier Often unnecessary
Distinct product areas with dozens of modules Can become noisy Nested aggregator may clarify responsibility
Independent release cadence Separate builds/repositories may be better One giant reactor can over-couple releases
Shared parent policy only External/shared parent is enough No need to create aggregation solely for policy

3. Central management should reduce accidental variance, not erase module intent

dependencyManagement is valuable for shared versions and scopes; pluginManagement is valuable for plugin versions/configuration. But management sections do not activate dependencies or plugins. Modules should still make their actual usage visible.

A healthy parent centralizes rules that must be consistent—Java release, encoding, approved plugin versions, organization-wide dependency versions—while allowing module-specific dependencies, test choices, packaging, and specialized plugin executions where justified.

4. Decide whether modules share a version lifecycle

A single reactor often uses one shared version because modules are developed and released together. That is operationally simple, especially when application modules depend on sibling libraries at the same version. But independent modules with independent compatibility promises may deserve separate repositories or release processes instead of forcing every change through one version train.

Do not use the reactor as a substitute for release architecture. “Maven can build these together” does not imply “these should always version and release together.”

5. Full reactor versus targeted CI is a correctness/speed tradeoff

Full reactor builds maximize coverage and minimize selection mistakes, but may waste time in large repositories. Targeted builds reduce work only when the changed-module graph and required downstream coverage are known. -pl, -am, and -amd are precise Maven controls, but CI still needs a policy for mapping changed files to modules.

CI event Reasonable starting scope Why
Parent POM or shared plugin/dependency policy changed Full reactor Inherited model may change everywhere.
Leaf application changed -pl :app -am Build app and prerequisites from source.
Core library changed -pl :lib -amd plus required prerequisites as needed Exercise downstream blast radius.
Only docs outside modules changed Potentially skip Maven build Only after path ownership is explicit.
Uncertain impact Full reactor Correctness beats speculative pruning.

6. Targeted builds need an independence test

A targeted build that succeeds on a long-lived developer machine may be using installed sibling artifacts. Before trusting targeted CI, reproduce it with an isolated local repository or ephemeral agent. If it fails there, the selection policy was incomplete or the build depended on undeclared external state.

7. External corporate parents are supply-chain inputs

Separating a corporate parent from the repository can be architecturally clean, but it moves shared build policy into a remotely resolved artifact. Pin its version. Govern the repository that supplies it. Review changes as executable build policy. Avoid mutable parent coordinates.

Trust boundary: a parent can affect repositories, plugins, profiles, compiler/test configuration, dependency management, and more. Treat a parent upgrade like a build-system dependency upgrade, not a cosmetic XML change.

8. relativePath communicates locality

For a repository-local parent, an explicit ../pom.xml documents the relationship. For an intentionally external parent, teams commonly use an empty <relativePath/> to prevent accidental pickup of a neighboring POM and force repository-based resolution. The choice should reflect intended ownership, not trial-and-error around a build error.

9. Worked scenario: a 40-module product repository

Decision Choice Observable justification
Parent/aggregator One repository root combines both; corporate compliance parent is one level above it only if truly shared across products. Local root keeps module collection obvious; effective POM proves inherited corporate policy separately.
Hierarchy Three product-area aggregators at most; modules otherwise flat within area. Reactor summaries remain understandable; ownership matches teams.
Dependency versions Centralize organization-approved/common versions; modules still declare actual dependencies. Dependency tree shows explicit uses while effective POM shows managed versions.
Plugin policy Pin build plugin versions centrally; activate only where needed. Effective POM and build logs reveal active plugins; management alone does not execute code.
CI Full reactor for parent/policy changes; targeted graph-aware builds for leaf changes; scheduled clean-room full build. Selection logs and fresh-repository runs prove scope is not relying on stale installs.

10. Anti-patterns to reject

  • A root with dozens of modules but no documented ownership or dependency direction.
  • Using parent inheritance to smuggle environment credentials or machine paths into children.
  • Assuming directory nesting creates dependency relationships.
  • Running -pl alone everywhere because it is faster, while agents retain sibling installs.
  • Putting every plugin in active parent <plugins> when most modules do not need it.
  • Changing an external parent version without inspecting the effective model diff.

Knowledge check

When is a separate corporate parent preferable to making every repository root the universal parent?

Why can deep module nesting increase risk even when Maven supports it?

A parent manages a plugin version but no child declares the plugin. Does it execute?

What change most strongly argues for a full reactor build?

What evidence makes a targeted CI strategy credible?

11. Summary and next bridge

Architecture choices should make the model easier to predict: ownership boundaries visible, shared policy intentional, and CI scope explainable from graphs. Lesson 4 now breaks those assumptions deliberately and teaches an evidence-first diagnostic sequence.

Official references and version notes

Version-sensitive statements in this lesson were checked against Apache Maven primary documentation on 2026-08-23. The mandatory path uses Maven 3.9.16 through Maven Wrapper 3.3.4, JDK 21 to run Maven, Java 17 as the project release target, Help Plugin 3.5.2, Dependency Plugin 3.11.0, Compiler Plugin 3.15.0, Surefire 3.5.6, and JAR Plugin 3.5.1. Maven 4 terminology is called out only where it differs materially; the hands-on path remains Maven 3.9.16.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.