Maven - Multi-Module Projects

Reading material

  1. https://maven.apache.org/pom.html#Aggregation
  2. https://www.baeldung.com/maven-multi-module
  3. https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

Parent pom

A project with modules is known as a multi-module, or aggregator project. Modules are projects that this POM lists, and are executed as a group. A pom packaged project may aggregate the build of a set of projects by listing them as modules, which are relative paths to the directories or the POM files of those projects.

You do not need to consider the inter-module dependencies yourself when listing the modules; i.e. the ordering of the modules given by the POM is not important. Maven will topologically sort the modules such that dependencies are always build before dependent modules.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.codehaus.mojo</groupId>
  <artifactId>my-parent</artifactId>
  <version>2.0</version>
  <packaging>pom</packaging>

  <modules>
    <module>my-project</module>
    <module>another-project</module>
    <module>third-project/pom-example.xml</module>
  </modules>
</project>

By setting the packaging to pom type, we declare that the project will serve as a parent or an aggregator; it won’t produce further artifacts.

<packaging>pom</packaging>

In the parent-project‘s pom.xml, all the submodules are added inside the modules section:

<modules>
    <module>core</module>
    <module>service</module>
    <module>webapp</module>
</modules>

In the individual submodules’ pom.xml, it will add the parent-project in the parent section:

<parent>
  <groupId>com.my.company</groupId>
  <artifactId>design-pattern-samples</artifactId>
  <version>1.0-SNAPSHOT</version>
</parent>

To build the project, go to the root of the parent project and run this:

mvn package

or

mvn verify

How to make one module depend on another module?

Some of these child modules may have inter-dependencies.

A: parent
   B: child1
   C: child2

C can have a dependency on B.

A pom.xml

<groupId>AAA</groupId>
<artifactId>A</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>

<modules>
    <module>B</module>
    <module>C</module>
</modules>

B pom.xml

<groupId>AAA</groupId>
<artifactId>B</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<parent>
    <artifactId>A</artifactId>
    <groupId>AAA</groupId>
    <version>0.0.1-SNAPSHOT</version>
</parent>

In C’s pom file, specify the dependency like this:

<groupId>AAA</groupId>
<artifactId>C</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>

<parent>
    <artifactId>A</artifactId>
    <groupId>AAA</groupId>
    <version>0.0.1-SNAPSHOT</version>
</parent>

<dependencies>
    <dependency>
      <groupId>A</groupId>
      <artifactId>B</artifactId>
      <version>${project.parent.version}</version>
      <type>jar</type>
    </dependency>
    ...

Enable Dependency Management in Parent Project

https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

Dependency management is a mechanism for centralizing the dependency information for a multi-module parent project and its children.

When you have a set of projects or modules that inherit a common parent, you can put all the required information about the dependencies in the common pom.xml file. This will simplify the references to the artifacts in the child POMs.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.16</version>
        </dependency>
        //...
    </dependencies>
</dependencyManagement>

By declaring the spring-core version in the parent, all submodules that depend on spring-core can declare the dependency using only the groupId and artifactId, and the version will be inherited:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
    </dependency>
    //...
</dependencies>

Moreover, you can provide exclusions for dependency management in parent’s pom.xml, so that specific libraries will not be inherited by child modules:

<exclusions>
    <exclusion>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </exclusion>
</exclusions>

If a child module needs to use a different version of a managed dependency, you can override the managed version in the child’s pom.xml file:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.3.30.RELEASE</version>
</dependency>

Inheritance v. Aggregation

  1. Inheritance and aggregation create a nice dynamic to control builds through a single, high-level POM. You often see projects that are both parents and aggregators.
  2. For example, the entire Maven core runs through a single base POM org.apache.maven:maven, so building the Maven project can be executed by a single command: mvn compile.
  3. However, an aggregator project and a parent project are both POM projects, they are not one and the same and should not be confused.
  4. A parent POM project may be inherited from (by child projects) - but need not necessarily have - any modules that it aggregates.
  5. Conversely, a parent POM project may aggregate projects that do not inherit from it.

Common issues when dealing with multi-module projects

cannot access class

Use Maven - maven-compiler-plugin with the same configuration in all of the sub-modules. Do not depend on the default behavior. What happens if we don’t? We will run into error like this:

Maven java compile error can not access CommonClassA

This class is expected to be compiled in a sub-module and is expected to be accessed by another class in another sub-module. If the versions of jdk, release, etc. don’t match up exactly, there will be errors.

package does not exist

[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.600 s
[INFO] Finished at: 2025-07-06T19:35:22-04:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project my-java-solutions: Compilation failure
[ERROR] /home/explorer436/Downloads/GitRepositories/programming-playground/java-playground/my-implementations/my-java-solutions/src/main/java/com/my/company/hackerrank/SimpleArraySum.java:[3,33] package com.my.company.streamsapi does not exist
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
[ERROR]
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR]   mvn <args> -rf :my-java-solutions

Failed to read artifact descriptor

https://stackoverflow.com/questions/6642146/maven-failed-to-read-artifact-descriptor

Failed to read artifact descriptor for com.morrislgn.merchandising.common:test-data-utils:jar:0.3b-SNAPSHOT: Could not find artifact com.morrislgn.merchandising:merchandising:pom:0.3b-SNAPSHOT

You can always try mvn -U clean install

-U forces a check for updated releases and snapshots on remote repositories.


Links to this note