Maven Multi-Module Project: Inheritance, Aggregation and Reactor - Quiz
Total: 5 questions
1. What is a Maven multi-module project, and how does inheritance (parent) differ from aggregation (modules)?
What is a Maven multi-module project, and how does inheritance (parent) differ from aggregation (modules)?
A multi-module project is a project split into several modules, each with its own pom.xml and its own artifact, built together with one command from the root POM. It rests on two independent mechanisms.
- Inheritance: a module names its parent in
<parent>(by GAV) and gets shared settings - groupId, version, properties, managed versions, plugins. The reference goes from the module to the parent; the parent does not know its children. - Aggregation: the root POM lists module directories in
<modules>, and the reactor builds them in one run in dependency order. The reference goes from the aggregator to the modules; a module may not know it is aggregated.
Both the parent and the aggregator need <packaging>pom</packaging>. Without it jar applies, and the build stops with an error like Invalid packaging for parent POM ..., must be "pom" but is "jar" or Aggregator projects require 'pom' as packaging.
Most often one root POM plays both roles (like shop-parent), but that is not required: a corporate parent can live in a repository while the project has its own aggregator; a module can have no parent at all - then it sets groupId, version and plugin versions itself. An important consequence: <parent> in a module does not add it to the build - a forgotten line in <modules> gives BUILD SUCCESS without that module.
2. What does a module of a multi-module project get from the parent POM, and how does Maven find the parent (relativePath)?
What does a module of a multi-module project get from the parent POM, and how does Maven find the parent (relativePath)?
Inherited: groupId and version, properties (a module property with the same name overrides the parent one), dependencyManagement and pluginManagement (versions and settings only), dependencies - added to every module, build/plugins with executions, repositories, distributionManagement and metadata. That is why the parent <dependencies> holds only what every module needs, and the rest goes into dependencyManagement.
Not inherited: artifactId, name and prerequisites (according to the Maven documentation), as well as packaging and modules: a module without its own packaging gets jar, and the module list only applies in its own POM.
Finding the parent in Maven 3: first at <relativePath> (../pom.xml by default), checking groupId, artifactId and version; if the file is missing or the coordinates differ - in the local and remote repositories. A parent elsewhere is given explicitly (../shop-parent/pom.xml), an empty <relativePath/> means "repositories only" (external parents such as spring-boot-starter-parent). Not found - a Non-resolvable parent POM error while reading the model; a common cause is a wrong path or a version in <parent> after a manual edit.
3. What is the Maven reactor, why can a module fail to see a sibling module, and how do you build only part of a multi-module project?
What is the Maven reactor, why can a module fail to see a sibling module, and how do you build only part of a multi-module project?
The reactor collects the modules from <modules> (recursively), builds a graph of links (dependencies, parent POMs, plugins and extensions from sibling modules) and sorts the modules so that each is built after the ones it depends on. Within one build it passes sibling results to a module directly, without installing them into ~/.m2.
Why a sibling is not visible. If the sibling is not part of the build (a run from a subdirectory or -pl without -am), it is an ordinary dependency from the repositories: not in ~/.m2 - a Could not find artifact error; an old installation there - the build passes with old code.
-pl- only the listed modules (directory or:artifactId);-am- plus the modules they depend on;-amd- plus the modules that depend on them;-rf :shop-web- resume a failed build; skipped modules come from~/.m2;-pl '!shop-web'- exclude a module (since Maven 3.2.1, quoted in bash); a module other participants depend on can be excluded only if its artifact is already in~/.m2;-T 4or-T 1C- parallel build of independent modules; Maven flags plugins not marked thread-safe with a warning, and on odd failures build sequentially first.
From a module directory you can point to the root explicitly: mvn -f ../pom.xml -pl shop-web -am package. The lesson describes Maven 3.9.x; in Maven 4 building from subdirectories changes.
4. How do you manage the version of a Maven multi-module project: ${project.version}, versions:set and the CI-friendly ${revision} version?
How do you manage the version of a Maven multi-module project: ${project.version}, versions:set and the CI-friendly ${revision} version?
Usually all modules share one version: it is set in the parent and the modules inherit it.
Sibling versions in <dependency> are written as ${project.version} or set once in the parent dependencyManagement. A literal 1.0-SNAPSHOT breaks on a version bump: the modules become 1.1-SNAPSHOT while the dependency stays old - the reactor does not recognize it and takes the old artifact from ~/.m2 or fails.
The version in <parent> must be a literal in Maven 3, so it changes in every POM. Options:
mvn versions:set -DnewVersion=1.1.0-SNAPSHOT(versions-maven-plugin, MojoHaus) changes the parent version and the references in the reactor modules, leavingpom.xml.versionsBackup; thenversions:commitorversions:revert. The plugin may not update literal sibling versions in dependencies.- CI-friendly versions (since Maven 3.5.0):
${revision},${sha1},${changelist}- the only exception to the literals rule. The version is a property, and CI passes its own:mvn clean deploy -Drevision=1.0.0; the default can be kept in.mvn/maven.config.
The ${revision} trap: without extra setup, POMs with the literal ${revision} go to ~/.m2 and the remote repository, and an external project cannot resolve the parent. So flatten-maven-plugin is added: in resolveCiFriendliesOnly mode it writes .flattened-pom.xml with the version substituted, and that file is installed (the files go into .gitignore).
5. Why split a project into Maven modules, when is it not needed, and where does code shared by several modules go?
Why split a project into Maven modules, when is it not needed, and where does code shared by several modules go?
Why: modules separate code by layer or feature with explicit boundaries - a module sees only the modules declared in its dependencies. Each module is built into its own artifact (jar, war), can be tested and reused on its own, and the shared versions and settings live in a single parent POM.
Example: shop-model (domain classes), shop-service (business logic, depends on shop-model), shop-web (the web layer, depends on shop-service). As long as no dependency on a data-access module is declared, the web layer cannot go around the services straight to the database: the compiler rejects such code. The root project has no code: its pom.xml only ties the modules together and holds the shared settings.
When not to split: modules pay off when parts of the project have different dependencies, different consumers or need to be built independently. A training project with a dozen classes should not be split: modules add POM files and build time for nothing.
Shared code goes into a separate module (for example, shop-common or shop-api) that the consumers depend on. It cannot go into the parent POM: its packaging is pom, and no sources are compiled there. A shared module also breaks cyclic dependencies, which the reactor does not allow.
A Maven module and a Java module (JPMS, since Java 9) are different mechanisms: the first is a unit of build (its own pom.xml and artifact), the second is a unit of encapsulation where module-info.java lists the exported packages and the required modules. They can be combined, but neither requires the other.