In this tutorial, we'll learn how to build a multi-module project with Maven.
First, we'll discuss what a multi-module project is, and have a look at the benefits of following this approach. Then we'll set up our sample project. For a good introduction to Maven, check out this tutorial.
2. Maven's Multi-Module Project
A multi-module project is built from an aggregator POM that manages a group of submodules. In most cases, the aggregator is located in the project's root directory and must have packaging of type pom.
The submodules are regular Maven projects, and they can be built separately or through the aggregator POM.
By building the project through the aggregator POM, each project that has a packaging type different from pom will result in a built archive file.
3. Benefits of Using Multi-Modules
The significant advantage of using this approach is that we may reduce duplication.
Let's say we have an application that consists of several modules, a front-end module and a back-end module. Now imagine we work on them and change the functionality, which affects them both. In that case, without a specialized build tool, we'd have to build both components separately or write a script to compile the code, run tests, and show the results. Then, after we got even more modules in the project, it would become harder to manage and maintain.
Therefore, when leveraging multi-modules, we can build our application's modules in a single command, and if the order matters, Maven will figure it out for us. We can also share a vast amount of configuration with other modules.
4. Parent POM
Maven supports inheritance in a way that each pom.xml file has the implicit parent POM. It's called Super POM and can be located in the Maven binaries. These two files are merged by Maven and form the Effective POM.
We can create our own pom.xml file, which will serve us as the parent project. Then we can include in it all configuration with dependencies, and set it as the parent of our child modules, so they'll inherit from it.
Besides the inheritance, Maven provides the notion of aggregation. A parent POM that leverages this functionality is called an aggregate POM. Basically, this kind of POM declares its modules explicitly in its pom.xml file.
Submodules, or subprojects, are regular Maven projects that inherit from the parent POM. As we already know, inheritance lets us share the configuration and dependencies with submodules. However, if we'd like to build or release our project in one shot, we have to declare our submodules explicitly in the parent POM. Ultimately, our parent POM will be the parent, as well as the aggregate POM.
6. Building the Application
Now that we understand Maven's submodules and hierarchy, let's build a sample application to demonstrate them. We'll use Maven's command-line interface to generate our projects.
This app will consist of three modules that'll represent:
- The core part of our domain
- A web service providing some REST APIs
- A webapp containing user-facing web assets of some sort
Since we'll focus on Maven, the implementation of these services will remain undefined.
6.1. Generating Parent POM
First, let's create a parent project:
mvn archetype:generate -DgroupId=com.baeldung -DartifactId=parent-project
Once the parent is generated, we have to open the pom.xml file located in the parent's directory and add the packaging as pom:
By setting the packaging to pom type, we're declaring that the project will serve as a parent or an aggregator; it won't produce further artifacts.
Now, as our aggregator is done, we can generate our submodules.
However, we need to note, this is where all the configuration to be shared is located, which will eventually be re-used in child modules. Among other things, we can make use of dependencyManagement or pluginManagement here.
6.2. Creating Submodules
As our parent POM was named parent-project, we need to make sure we're in the parent's directory and run generate commands:
cd parent-project mvn archetype:generate -DgroupId=com.baeldung -DartifactId=core mvn archetype:generate -DgroupId=com.baeldung -DartifactId=service mvn archetype:generate -DgroupId=com.baeldung -DartifactId=webapp
Notice the command used. It's the same as we used for the parent. The thing here is, these modules are regular Maven projects, yet Maven recognized that they're nested. When we changed the directory to the parent-project, it found that the parent has the packaging of type pom, and it will modify the pom.xml files accordingly.
In the parent-project‘s pom.xml it will add all the submodules inside the modules section:
<modules> <module>core</module> <module>service</module> <module>webapp</module> </modules>
and in the individual submodules' pom.xml, it will add the parent-project in the parent section:
<parent> <artifactId>parent-project</artifactId> <groupId>com.baeldung</groupId> <version>1.0-SNAPSHOT</version> </parent>
Next, Maven will generate the three submodules successfully.
It's important to note that submodules can have only one parent. However, we can import many BOMs. More details about the BOM files can be found in this article.
6.3. Building the Project
Now we can build all three modules at once. In the parent's project directory, we'll run:
This will build all the modules. We should see the following output of the command:
[INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Reactor Build Order: [INFO] parent-project [pom] [INFO] core [jar] [INFO] service [jar] [INFO] webapp [war] ... [INFO] Reactor Summary for parent-project 1.0-SNAPSHOT: [INFO] parent-project ..................................... SUCCESS [ 0.272 s] [INFO] core ............................................... SUCCESS [ 2.043 s] [INFO] service ............................................ SUCCESS [ 0.627 s] [INFO] webapp ............................................. SUCCESS [ 0.572 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
The Reactor lists the parent-project, but since it's pom type it's excluded, and the build results in three separate .jar files for all the other modules. In this case, build occurs in three of them.
Moreover, Maven Reactor will analyze our project and build it in the proper order. So if our webapp module depends on the service module, Maven will first build the service, then the webapp.
6.3. Enable Dependency Management in Parent Project
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.
Let's take a look at a sample parent's pom.xml:
<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>
Finally, 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>
Please note that while child modules inherit from their parent project, a parent project does not necessarily have any modules that it aggregates. On the other hand, a parent project may also aggregate projects that do not inherit from it.
For more information on inheritance and aggregation please refer to this documentation.
6.4. Updating the Submodules and Building a Project
We can change the packaging type of each submodule. For example, let's change the packaging of the webapp module to WAR by updating the pom.xml file:
and adding maven-war-plugin in the plugins list:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.2</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build>
Now we can test the build of our project by using the mvn clean install command. The output of the Maven logs should be similar to this:
[INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Reactor Build Order: [INFO] [INFO] parent-project [pom] [INFO] core [jar] [INFO] service [jar] [INFO] webapp [war]
//............. [INFO] Reactor Summary for parent-project 1.0-SNAPSHOT: [INFO] [INFO] parent-project ..................................... SUCCESS [ 0.272 s] [INFO] core ............................................... SUCCESS [ 2.043 s] [INFO] service ............................................ SUCCESS [ 0.627 s] [INFO] webapp ............................................. SUCCESS [ 1.047 s]
In this article, we discussed the benefits of using Maven multi-modules. We also distinguished between regular Maven's parent POM and an aggregate POM. Finally, we explored how to set up a simple multi-module to start to play with.
Maven is a great tool, but it's complex on its own. If we want to learn more details about Maven, we can look at the Sonatype Maven reference or Apache Maven guides. If we seek advanced usages of Maven's multi-modules set-up, we can look at how the Spring Boot project leverages its usage.
All the code examples used in this article are available over Github.