Why you should use parent pom for your multi-module Java projects
In this article, we are going to look deep into why we should use parent for our Java projects especially those composed of multi-modules like microservice projects.
When we start a new project, most probably it will require different functionalities which we will not be keeping in a single module as otherwise would cause a lot of pain. Or we have a microservice project that will be composed of several services each of which will be different module in our project. Let’s see example pom files for a microservice project.
Parent pom
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/>
</parent>
<groupId>com.ofk.template</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>parent</name>
<description>Parent project</description>
<packaging>pom</packaging>
<modules>
<module>gateway</module>
<module>discovery-service</module>
<module>authentication-service</module>
<module>document-service</module>
<module>config-server</module>
</modules>
</project>
Parent pom generally has spring-boot-starter-parent as parent to use what version spring package gives us to have consistent environment. You will also define groupId and version here to share it across sub modules. Packaging will be pom as this is not a actual java module that contains source code. Last part is the <modules> part where you define your sub modules. We will go into more detail in the next section.
And a sub module
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>com.ofk.template</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>document-service</artifactId>
<name>document-service</name>
<description>Document service</description> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
As you see, we defined parent as the parent we created above. You do not need to define groupId or version here. If you want to set different version for this module apart from parent one, you can still define it. Next, we will see the advantages and how to get the most out of it.
Let’s look at how we can we can organize our project and advantages over sample pom files.
1- Unified build management and versioning
When you have multiple related modules in your project, it may be convenient to run their lifecycle methods together instead of building them one by one which may take too much time if you have a lot of modules. With parent module, you can define them under <modules> tag and manage build lifecycles from one place as you can see below.
<modules>
<module>gateway</module>
<module>discovery-service</module>
<module>authentication-service</module>
<module>document-service</module>
<module>config-server</module>
</modules>
But this is optional. Sometimes, you just want to use parent pom to have uniform dependency versioning or have some place to define commons. In that case, you do not have to define child modules in the parent pom.
Another advantage is that you can version all your modules directly from your parent pom and keep them consistent. When you define version in your parent pom, you do not have to put version in your child poms eventhough you can.
2- Uniform dependency and plugin versioning
In your project, different modules will have different dependencies. Some of them will be special to one module while some are used in different modules. It is important to have consistency in dependency versioning as different versions of the same dependency may cause conflicts and undesired results. By defining dependency versions in the parent pom, you will just need to add the dependency without version to your sub module without having to go through other modules that use the same dependency to find out what version they use. Same applies for plugins. See below for an example.
parent pom
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/>
</parent>
<groupId>com.ofk.template</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>parent</name>
<description>Parent project</description>
<packaging>pom</packaging> <modules>
<module>gateway</module>
<module>discovery-service</module>
<module>authentication-service</module>
<module>document-service</module>
<module>config-server</module>
</modules> <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
child pom
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>com.ofk.template</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>document-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
Note that you do not have to define sub modules under <modules> tag in parent pom to pass down versions to sub modules.
We will face two different case here and will ask ourselves a question. Are this dependency going to be definetely in all sub modules or only in some of them? If the answer is “yes, it will be in all modules”, then you need to define the dependency in your parent pom under <dependencies> tag as follows and do not have to define dependency in your child pom.
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
If the answer is “no, just some modules will use it” which is the most common one, then define dependencies in pom under <dependencyManagement> tag as follows and define the dependency in sub modules without version (you can also define with version if you want to use seperate version in that module).
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
</dependencyManagement>
Again, same applies for plugins with <plugins> and <pluginManagement> tags.
One practical use for Spring projects is that you can use spring-boot-starter-parent as your parent’s parent and define spring cloud dependencies in dependencyManagement tag in parent pom so that you will have consistent environment across your modules.
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.ofk.template</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<dependencies>
<!-- other dependencies to pass down -->
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3- Declaring commons in one place and using everywhere
In your pom file, you can define several different configurations like distribution management. Without parent pom, you have to define them in their own pom files which turns out to be unnecessary work. When you define the common configurations in the parent pom, it will be inherited to sub modules. See the following example
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/>
</parent>
<groupId>com.ofk.template</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>parent</name>
<description>Parent project</description>
<packaging>pom</packaging>
<distributionManagement>
<repository>
<id>github</id>
<url>url</url>
</repository>
</distributionManagement>
</project>
We defined distribution management here and sub modules can use this without having to redefine it.
In this article, we talked about why we should use parent pom to organize our project and advantages of it. I hope you will find it useful.