Chapter 09Lesson 04~155 minutes

Maven Multi-Module Projects, Parent POMs, Aggregation, Inheritance, and Reactor Builds: Diagnostics, Failure Modes, Security, and Performance

Diagnose multi-module failures from graph evidence: aggregation without inheritance, wrong parent paths, dependency cycles, incomplete targeted builds, stale installed artifacts, and inherited configuration drift.

DiagnosticsCyclesrelativePathStale InstallReactor Selection

Learning objectives

  • Apply a repeatable diagnostic sequence to multi-module failures.
  • Diagnose aggregation-without-inheritance and wrong-parent relativePath behavior.
  • Interpret reactor-cycle and incomplete-selection failures without deleting normal caches.
  • Prove when an installed sibling artifact masks a broken reactor selection.
  • Separate resolution, model, reactor, compilation/test, and performance evidence.
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. Diagnostic sequence: preserve graph evidence before changing state

  1. Preserve concise error output and Reactor Build Order/Summary.
  2. Confirm wrapper, Maven, and JDK identity.
  3. Inspect aggregator <modules> and child <parent>.
  4. Inspect effective POM and actual dependency tree.
  5. Inspect selected project set: -pl, -am, -amd, -rf, -N.
  6. Repeat with a fresh project-local repository when stale installs could mask the issue.
  7. Inspect the specific compiler/test/plugin failure only after model/reactor scope is correct.
  8. Apply the least destructive correction that fixes the proven cause, then verify from controlled state.

2. Failure: aggregated module does not inherit expected policy

Symptom: the module appears in the reactor but its effective POM lacks the root's compiler/plugin/dependency management. The root lists the module, so aggregation works; however, the child's <parent> points somewhere else or is absent.

Evidence Interpretation
Module appears in Reactor Build Order Aggregator collected it.
Child effective POM lacks root property/plugin management Inheritance relationship is absent/different.
Child POM has another parent Legal architecture; expectation was wrong.
Fix Either change the intended parent deliberately or stop expecting aggregation to imply inheritance.

3. Failure: relativePath resolves the wrong or no local parent

Intentionally change a child to <relativePath>../not-the-parent.xml</relativePath>. From a fresh isolated local repository, Maven should no longer be able to use the intended repository-local parent through that path. Preserve the model-building error before editing anything.

<parent>
  <groupId>dev.academy.reactor</groupId>
  <artifactId>reactor-lab-parent</artifactId>
  <version>1.0.0</version>
  <relativePath>../not-the-parent.xml</relativePath>
</parent>
Do not “fix” this by installing random parent versions into the normal local repository. Restore the intended path or intentionally switch to governed repository-based parent resolution.

4. Failure: dependency cycle prevents a valid reactor order

If A depends on B and B depends on A, no topological ordering satisfies both. Maven should report a cyclic project relationship during project sorting/build setup. Treat the cycle as an architecture defect, not an ordering typo in <modules>.

Impossible dependency order
flowchart LR
 A[model] -->|depends on| C[app]
 C -->|depends on| S[service]
 S -->|depends on| A

The correction is to remove or invert a dependency, extract an interface/shared model into a lower-level module, or redesign the coupling. Reordering module declarations cannot solve a directed cycle.

5. Failure: targeted build omits prerequisites

Start from an empty lab repository and select only the application without -am. If the application depends on an in-reactor library that is not selected and not available from a repository, resolution should fail. This is the exact class of problem --also-make exists to prevent.

REPO_EMPTY="$PWD/.lab-m2-empty"
rm -rf "$REPO_EMPTY"
set +e
./mvnw -Dmaven.repo.local="$REPO_EMPTY" -pl :greeter-app package > evidence/app-only-failure.log 2>&1
rc=$?
set -e
printf 'exit=%s
' "$rc"
grep -nE 'Could not resolve|greeter-lib|BUILD FAILURE' evidence/app-only-failure.log || true

Repair by selecting the application plus its prerequisites: -pl :greeter-app -am. Do not copy a prebuilt sibling JAR into place.

6. Failure masked by installed local state

Now compare two environments. In repository A, run install for the whole reactor, then an app-only build may resolve the library from that local repository. In fresh repository B, the same app-only selection exposes the missing producer. The source tree did not change; the hidden state did.

Run Local state What it proves
Full install then app-only build Sibling artifact installed App can resolve a repository artifact; does not prove reactor selection is complete.
App-only build from fresh repo No sibling artifact Tests whether selection itself contains/provides required producer.
App + -am from fresh repo Producer included from source Strong evidence of graph-correct targeted build.

7. Resume-from is recovery, not a dependency solver

-rf/--resume-from can continue a reactor at a project after a failure. It does not redesign the dependency graph or guarantee prerequisite artifacts are available if you changed state between runs. Before resuming in CI, understand which earlier modules were successfully built and where their outputs live.

8. Security-sensitive multi-module changes

Review changes to external parent coordinates, module paths, build extensions, plugin repositories, inherited plugins, repository settings, or CI-selected project expressions. A malicious or mistaken parent/module change can execute code across many projects.

Never diagnose by weakening repository verification, bypassing wrapper checksums, adding broad untrusted plugin repositories, or placing credentials in POMs. Keep the failure visible and correct the graph/model source.

9. Performance: measure collection, resolution, compilation, tests, and packaging separately

Targeted builds can reduce compilation/test work, but cold dependency/plugin resolution and model construction still cost time. A warm developer machine is not a valid benchmark for an ephemeral CI agent. Compare full and selected builds with the same isolated repository/cache assumptions before declaring one strategy faster.

Observation Likely dimension
First run slow, second much faster Cold dependency/plugin resolution or filesystem/JIT effects.
Many unaffected modules compile/test Selection policy too broad or full reactor chosen.
Model fails before compilation Parent/aggregator/reactor configuration problem.
Only tests dominate Test workload; not primarily reactor sorting.
Package/install dominates Packaging/local repository side effects; inspect separately.

10. Minimal correction playbook

Symptom Do first Avoid
Missing inherited config Compare child parent + effective POM Adding duplicate config blindly to every child
Parent not resolvable Check coordinates + relativePath + fresh repo Installing arbitrary parent into normal cache
Cycle Draw dependency graph and break architectural cycle Reordering modules
Selected app cannot resolve sibling Use -am or correct repository strategy Copying JARs manually
Works only after install Repeat with fresh isolated repo Calling stale state “reproducible”

Knowledge check

A module is in Reactor Build Order but lacks root compiler configuration. What relationship should you inspect first?

Can reordering the modules list repair A→B→A dependency cycle?

Why repeat an app-only targeted build with a fresh repository?

What does -rf solve?

Why are parent POM changes security-sensitive?

11. Summary and next bridge

Multi-module troubleshooting is graph troubleshooting. Preserve the model, reactor selection, and local-state evidence before touching files. Lesson 5 integrates those skills into a three-module checkpoint where the same repository is observed under full, targeted, broken-cycle, and broken-parent conditions.

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.