Maven Multi-Module Projects, Parent POMs, Aggregation, Inheritance, and Reactor Builds: Concepts, Architecture, and Mental Model
Scale Maven from one project to many by separating aggregation, inheritance, dependency edges, parent resolution, reactor sorting, and targeted build selection into distinct, inspectable relationships.
Learning objectives
- Distinguish aggregation, inheritance, dependency relationships, and reactor selection as separate graphs.
- Explain what a parent POM contributes to an effective child POM and what an aggregator contributes to project collection.
- Predict Maven reactor order from instantiated project/plugin/extension relationships rather than directory order.
-
Explain how
relativePath, the local repository, and remote repositories participate in parent resolution. -
Interpret
-pl,-am,-amd,-rf, and-Nas graph-selection controls.
~/.m2, global settings, production repository, or CI
configuration is modified.
1. The problem: one repository contains several independently addressable projects
A single Maven project has one POM, one project coordinate, one source tree, and one lifecycle execution context. A large repository quickly needs more than that: reusable libraries, applications, adapters, generated-code projects, test fixtures, or platform BOMs may need separate coordinates and separate dependency edges while still being built together.
The dangerous shortcut is to call every relationship “parent/child.” Maven uses at least four different relationships that can overlap but are not equivalent: aggregation says which projects a reactor collects; inheritance says which POM contributes model values; dependency edges say which artifacts a module consumes; and reactor selection says which collected projects Maven will actually build for one invocation.
flowchart LR R[Root aggregator POM] -->|modules| L[Library project] R -->|modules| A[Application project] L -->|parent model| R A -->|parent model| R A -->|project dependency| L C[CLI selection -pl/-am] -->|selects subset| X[Reactor execution set]
2. Define the objects before touching the build
| Term | What it is | What it is not |
|---|---|---|
| Aggregator POM |
A pom-packaged project whose
<modules> list collects projects into a
reactor.
|
It does not automatically become every module's parent. |
| Parent POM |
A POM referenced by a child's <parent>;
contributes inheritable model values.
|
It does not automatically aggregate that child. |
| Module |
A Maven project collected from an aggregator's
<modules> path.
|
Not merely a Java package or source directory. |
| Reactor | Maven core mechanism that collects projects, sorts them, then builds the selected projects. | Not the local repository and not a dependency cache. |
| Dependency edge |
A project/artifact relationship declared in
<dependencies>.
|
Not the same as inheritance or aggregation. |
dependencyManagement |
Central metadata defaults/constraints for dependencies used by inheriting POMs. | Does not itself create a dependency or reactor sort edge. |
pluginManagement |
Central plugin defaults/versions used when a plugin is actually referenced. | Does not by itself execute a plugin or change reactor order. |
relativePath |
A hint for locating the parent POM from the child. | Not a module path and not a dependency path. |
3. Aggregation and inheritance can coincide—but they remain independent
A common repository uses one root POM as both aggregator and parent. That is convenient because the root lists modules and also centralizes versions/properties/plugins. But Maven explicitly allows the roles to diverge. A corporate parent can live in a separate repository and be consumed by many services, while each service repository has its own local aggregator. Conversely, an aggregator can collect projects that inherit from different parents.
| Question | Look here |
|---|---|
| Which projects are collected? | Aggregator <modules>. |
| Which model values are inherited? |
Child <parent> plus effective-POM merge
rules.
|
| Which project must be built first? | Instantiated reactor relationships, then module-list order as fallback. |
| Which artifact does code consume? | Dependency coordinates and resolved dependency graph. |
4. Reactor sorting is topological, not alphabetical or directory order
The Maven 3 multi-module guide documents the ordering relationships
used by the reactor. An actual project dependency on another reactor
project creates an ordering edge. So can an in-reactor plugin
declaration, plugin dependency, or build extension. Only when no
stronger relationship applies does the order in
<modules> act as the fallback.
dependencyManagement and
pluginManagement do not create reactor sort edges
because they are management metadata, not instantiated uses.
| Relationship | Changes reactor order? | Reason |
|---|---|---|
| Project A depends on project B | Yes | B must be available before A needs it. |
| A uses reactor project B as a build plugin | Yes | Plugin must be built before it executes. |
| A has a plugin dependency on reactor project B | Yes | Plugin classpath needs B. |
| A declares reactor project B as build extension | Yes | Extension must exist before model/build use. |
| B appears only in dependencyManagement | No | No artifact use is instantiated. |
| B appears only in pluginManagement | No | No plugin execution is instantiated. |
| No relationship between A and B | Fallback | <modules> order breaks the tie. |
5. Parent resolution is model construction, not reactor dependency resolution
When Maven builds a child model, it must resolve the parent POM. The
normal default relativePath is ../pom.xml.
An explicit path can point elsewhere. If the local path does not
identify the requested parent coordinates, Maven can fall back to
repository resolution according to Maven's model-building rules and
available repositories.
This is why a stale installed parent can make a broken repository layout appear healthy. A clean-room check should use a fresh project-local repository when you are testing whether parent resolution really comes from the intended source tree.
<parent>
<groupId>dev.academy.reactor</groupId>
<artifactId>reactor-lab-parent</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
6. Read-only inspection before mutation
Before adding modules or changing a parent, capture current identity and effective model evidence. In a multi-module project, run commands from the intended aggregator directory unless the lesson explicitly asks you to test another starting POM.
set -o pipefail
./mvnw -v
./mvnw -Dmaven.repo.local="$PWD/.lab-m2" help:effective-pom -Doutput=evidence-effective-root.xml
./mvnw -Dmaven.repo.local="$PWD/.lab-m2" help:effective-pom -pl :greeter-app -Doutput=evidence-effective-app.xml
./mvnw -Dmaven.repo.local="$PWD/.lab-m2" dependency:tree -pl :greeter-app
help:effective-pom reveals inherited and managed model
state. dependency:tree reveals actual artifact
dependencies. Neither should be confused with the aggregator's
module list.
7. Reactor selectors are scope controls, not dependency declarations
| Option | Meaning in Maven 3 | Typical use |
|---|---|---|
-pl / --projects |
Select reactor projects rather than building all collected projects. | Build one service/module. |
-am / --also-make |
Also build reactor projects required by the selected projects. | Build an app plus its in-reactor prerequisites. |
-amd / --also-make-dependents
|
Also build reactor projects that depend on selected projects. | Test blast radius after a library change. |
-rf / --resume-from |
Resume a reactor from a specified project. | Continue after a known failure after root cause is fixed. |
-N / --non-recursive |
Build only the current project, ignoring declared child modules. | Inspect/build aggregator project itself without recursive modules. |
8. Multi-module state stores must stay distinct
| State | Example | Why it matters |
|---|---|---|
| Source/model | Root and module pom.xml files |
Defines aggregation, inheritance, dependency, and plugin model. |
| Generated outputs | Each module's target/ |
Lifecycle output; disposable. |
| Reactor in-memory state | Collected MavenProject set and sorted execution list | Exists for an invocation; not a persistent cache. |
| Local repository | Project-local .lab-m2 in labs |
Can mask missing reactor relationships if previously installed artifacts exist. |
| Remote repositories | Configured artifact/POM sources | May resolve external parents/dependencies. |
| CI artifacts/cache | Pipeline-controlled state | Must not be mistaken for Maven reactor correctness. |
9. Parent POMs and modules expand the build trust boundary
A parent POM can inject repositories, plugin repositories, build plugins, plugin executions, properties, profiles, reporting, and dependency management into many children. An aggregator can cause many modules to execute in one invocation. Treat both as executable build-policy inputs that require code review.
10. DevOps operating contract
CI should know which root POM defines the reactor, which parent defines shared policy, which modules changed, and which dependency edges require upstream or downstream builds. A fast targeted build is useful only if its selection is graph-correct. A pipeline that silently relies on an agent's previously installed module is not proving the repository can build from source.
Knowledge check
A root POM lists module A, but A declares a completely different parent. Is that legal?
Yes. Aggregation and inheritance are independent relationships. The root can collect A without being A's parent.
Does dependencyManagement on module B force B to build before module A?
No. Management metadata alone does not create an instantiated dependency edge and therefore does not change reactor sort order.
Why can module order in the root POM be intentionally “wrong” yet the build still succeed?
The reactor topologically sorts stronger project/plugin/extension relationships first; module-list order is only a fallback when no such relationship decides the order.
What risk does a populated local repository introduce during a targeted-build test?
An installed module or parent can satisfy resolution even when
the selected reactor scope is incomplete, masking a missing
-am or broken parent relationship.
Which command family should you inspect before redesigning CI module selection?
Reactor selection and graph evidence: the root module list, dependency tree/effective POM, and the actual Reactor Build Order/summary produced by a controlled Maven invocation.
11. Summary and next bridge
Chapter 09 starts with one discipline: draw the graphs separately. Aggregation determines collection. Parent inheritance determines model composition. Dependencies determine artifact/classpath relationships and often reactor order. CLI selection determines what one invocation builds. Lesson 2 now turns that model into a small reproducible reactor.
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.
- Maven — Guide to Working with Multiple Modules (Maven 3)
- Maven — POM Reference: Inheritance and Aggregation
- Maven — Introduction to the POM
- Maven — Maven Model Builder
- Maven — CLI Reference
- Maven Help Plugin 3.5.2
- Maven Dependency Plugin 3.11.0
- Maven Compiler Plugin 3.15.0
- Maven Surefire Plugin 3.5.6
- Maven JAR Plugin 3.5.1
- Apache Maven Wrapper
- Maven 3.9.16 Release Notes
- Maven 4 Multi-Subproject Guide — comparison only
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.