skip to content

Multi-Module Reactor

Reactor builds spanning many modules: aggregator and parent POMs, build ordering, intra-reactor resolution, and partial builds. Interviewers ask because every real Maven codebase eventually becomes multi-module.

on this pageshow

explore

questions

21

Why does an aggregator/parent POM use <packaging>pom</packaging>, and what does it produce?

level: juniorimportance: must knowfreq 55%

answer

  1. pom packaging = no jar, POM is the artifact
  2. required when <modules> present
  3. parent/BOM are pom-packaged
  4. installs only the .pom file
  5. minimal lifecycle, no compile

basics

~20 s

Because it isn't a real artifact like a jar — it only organizes other modules and/or shares config. packaging=pom makes Maven install just the POM file itself (no jar/war), and it is required for any POM with a <modules> section.

solid answer

~40 s

`<packaging>pom</packaging>` tells Maven this project produces **no compiled artifact** — no jar, war, or class output. Its deliverable is the POM file itself, which gets installed/deployed so other projects can reference it (as a parent via inheritance, or as a BOM via import). It is **mandatory** for an aggregator: a POM that lists `<modules>` must be packaging `pom`. It is also the natural packaging for a pure parent POM that only carries shared `<dependencyManagement>`, `<pluginManagement>`, and properties. Because there is nothing to compile, the default lifecycle for pom packaging is minimal — mostly `install`/`deploy` of the POM and processing of any modules. Trying to aggregate with `jar` packaging is a configuration error.

code

xml · 10 lines
xml
<project>
  <groupId>com.acme</groupId>
  <artifactId>acme-parent</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>
  <modules>
    <module>core</module>
    <module>service</module>
  </modules>
</project>

go deeper

for a junior

Knows the root/parent uses packaging=pom and makes no jar.

for a middle

Knows it is required for <modules> and used for parents/BOMs.

for a senior

Explains lifecycle differences and BOM import vs inheritance use of pom packaging.

for a principal

Decides parent-vs-BOM strategy and what gets published as pom artifacts across the org.

## What packaging means The `<packaging>` element selects which artifact a project produces and which lifecycle bindings run. `jar` (the default) compiles classes and builds a jar; `war` builds a web archive; `pom` builds *nothing executable* — its artifact **is the POM file**. ```xml <project> <groupId>com.acme</groupId> <artifactId>acme-parent</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <modules> <module>core</module> </modules> </project> ``` ## Why aggregators/parents need it Two roles both require `pom` packaging: - **Aggregator:** any POM with a `<modules>` list must be `packaging=pom`. There is no source code to compile at that level; its job is to drive the reactor. - **Parent / BOM:** a POM that exists only to be inherited (or imported via `<scope>import</scope>` for a BOM) ships only its configuration, so it is `pom` packaged. ## What gets produced and stored On `install`/`deploy`, Maven places the POM into the local/remote repository under its coordinates (e.g. `com/acme/acme-parent/1.0.0/acme-parent-1.0.0.pom`). No `.jar` accompanies it. Children resolving `<parent>` or importing the BOM download just this POM. ## Lifecycle differences With `pom` packaging, the default phase bindings are sparse — there is no `compile`/`test-compile`/`package` jar step. Running `mvn install` on a pom-packaged aggregator mainly: builds the modules (reactor) and installs the POM(s). This is why an aggregator build feels like it 'just' iterates over children. ## Common errors - Forgetting `packaging=pom` on a POM that has `<modules>` → build error. - Expecting a jar from the parent directory → there isn't one by design.

  • Can a pom-packaged project also be a BOM?
    Yes. A BOM is a pom-packaged project that defines <dependencyManagement> only; consumers import it with <scope>import</scope> and <type>pom</type> to share managed versions without inheritance.
  • What artifact ends up in ~/.m2 for a pom-packaged module?
    Only the POM file itself (e.g. acme-parent-1.0.0.pom). No jar is produced or installed.

saying these in an interview costs you the question

  • Saying an aggregator can use packaging=jar.
  • Expecting the parent module to produce a runnable/compiled jar.
  • Confusing packaging=pom with producing an empty jar.

context

open as a page

What is the Maven reactor, and how do you build only a single module out of a large multi-module project from the command line?

level: juniorimportance: must knowfreq 70%

basics

~10 s

The reactor is Maven's engine that figures out which modules to build and in what order based on dependencies. To build one module, run from the root: mvn install -pl :artifactId (or -pl module-path).

open as a page

In a Maven multi-module build, what determines the order in which the reactor builds the modules?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Maven's reactor builds modules in dependency order, not in the order they are listed in <modules>. It looks at each module's <dependency> entries and builds a needed module before the one that depends on it.

open as a page

In a Maven multi-module project, what is the difference between aggregation and inheritance, and which POM elements express each?

level: middleimportance: must knowfreq 70%

basics

~20 s

Aggregation means a parent POM lists child <modules> so one build command builds them all. Inheritance means a child declares a <parent> and reuses its config (versions, plugins, dependencies). They are independent concerns that often live in the same POM.

open as a page

In a multi-module project, how do you keep dependency versions consistent across modules?

level: middleimportance: must knowfreq 65%

basics

~10 s

Put a <dependencyManagement> block (and version properties) in the shared parent POM. Child modules declare dependencies without versions, so every module uses the parent's pinned version.

open as a page

What is a Maven BOM, and how do you author one?

level: middleimportance: must knowfreq 70%

basics

~10 s

A BOM (Bill of Materials) is a POM-packaged artifact whose only job is a <dependencyManagement> block listing curated versions. Consumers import it so they can add those dependencies without specifying versions.

open as a page

Explain the difference between -am (--also-make) and -amd (--also-make-dependents), and when you'd reach for each.

level: middleimportance: must knowfreq 60%

basics

~20 s

-am adds the modules your selected modules depend ON (upstream), so the build can resolve them. -amd adds the modules that depend on your selected modules (downstream), so you can verify nothing breaks. Both are used with -pl.

open as a page

When module-app depends on module-core in the same reactor, how does Maven resolve module-core's classes without you running mvn install first?

level: middleimportance: must knowfreq 55%

basics

~10 s

Within a single reactor build, Maven resolves a dependency module directly from its freshly built target/ output (or in-memory artifact) instead of the local ~/.m2 repository, so you don't need a separate mvn install.

open as a page

Explain how <scope>import</scope> works and why it requires <type>pom</type>.

level: seniorimportance: must knowfreq 60%

basics

~10 s

Inside <dependencyManagement>, importing a BOM with scope=import and type=pom inlines that BOM's managed versions into your own dependencyManagement. type=pom is needed because the imported artifact is a POM, not a JAR.

open as a page

A teammate imported your BOM but their build can't find the classes. What likely went wrong?

level: juniorimportance: should knowfreq 40%

basics

~10 s

Importing a BOM only sets versions; it does not add anything to the classpath. They still need to declare the actual dependencies in <dependencies> (without a version) to get the classes.

open as a page

A 40-module reactor build failed at module 27. How do you restart without rebuilding the first 26, and what are the caveats of doing so?

level: middleimportance: should knowfreq 50%

basics

~10 s

Re-run with -rf :failedModule (--resume-from). Maven starts the reactor at that module and continues to the end, skipping the modules that already succeeded.

open as a page

When you run a build on an aggregator, in what order are the modules built, and how does the reactor decide?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The reactor builds modules in dependency order, not the order they are listed in <modules>. Maven topologically sorts modules so each is built after the modules it depends on. Listed order is only a tiebreaker.

open as a page

How does Maven resolve a child module's <parent>, and what is the role of <relativePath>?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Maven first looks for the parent POM on disk via <relativePath> (default ../pom.xml), and if not found there falls back to local and remote repositories by coordinates. Setting <relativePath></relativePath> empty forces repository-only lookup.

open as a page

When would you author a BOM versus using a parent POM to share versions?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Use a parent POM to share build config, plugins, and properties across YOUR modules (single parent only). Use a BOM when you want to publish version-only management that EXTERNAL consumers import, possibly alongside their own parent.

open as a page

How do you version and release a BOM, and what SNAPSHOT pitfalls should you watch for?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Version the BOM independently and bump it whenever the curated versions change. A BOM ending in -SNAPSHOT pins consumers to mutable versions, so consumers should import released (non-SNAPSHOT) BOM versions for reproducible builds.

open as a page

Design a CI strategy that builds only the modules affected by a pull request in a large reactor. Which selectors and flags would you use, and what are the correctness risks?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Detect changed module dirs from the git diff, then run mvn verify -pl <changed> -amd (and -am if upstreams may need rebuilding). This builds the touched modules plus everything that depends on them, skipping unaffected modules.

open as a page

What causes a reactor cyclic dependency error, and how do you resolve it architecturally?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A cycle happens when module A depends on B and B depends (directly or transitively) back on A. The reactor can't topologically sort a cyclic graph, so it fails. Fix it by extracting shared code into a new third module both can depend on.

open as a page

How do -pl, -am, -amd, and -rf control which modules the reactor builds, and when would you use each?

level: seniorimportance: should knowfreq 45%

basics

~10 s

-pl picks specific modules (project-list). -am also builds their upstream dependencies (also-make). -amd also builds downstream dependents (also-make-dependents). -rf resumes from a given module (resume-from) after a failure.

open as a page

How would you structure a Maven monorepo so multiple teams share configuration but keep module boundaries clean?

level: principalimportance: should knowfreq 30%

basics

~10 s

Use a root POM that is both aggregator (lists modules) and parent (shared config). Put versions in <dependencyManagement>/<pluginManagement>, keep each module focused with explicit sibling dependencies, and avoid cycles so the reactor stays parallelizable.

open as a page

Walk through the selector syntax accepted by -pl, including path vs coordinate forms and exclusions, and a pitfall teams hit with module names.

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

-pl accepts comma-separated selectors: a relative module path (core/api), a full groupId:artifactId, or short :artifactId. Prefix with ! or - to exclude. A common pitfall: the path is the directory name, which may differ from the artifactId.

open as a page

How do reactor failure behavior (-ff/-fae/-fn) and parallel builds (-T) interact with reactor ordering?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Failure flags decide what happens after a module fails: -ff (fail-fast, default) stops immediately, -fae (fail-at-end) keeps building independent modules then reports all failures, -fn (fail-never) ignores failures. -T runs independent modules in parallel threads while still honoring dependency order.

open as a page