Spring Multi-module Maven Project

Prashantprakash
5 min readJan 28, 2024

--

Spring Boot

Java 9+ comes with an amazing features out of the box. One of them which I love is Modular projects support. It helps to divide the project into multiple modules, and control what each module exposes to other modules. It gives a lot of fine tuning to make sure, you are not exposing something you don’t want.

You can refer to the official documentation, which is quite good. You can find the links in the end of this blog.

We will be using Java 17 + Maven to make our spring project.

New Project

Create a new Maven project. The pom of this project will act as the parent pom. We will create modules inside this project. POM of those modules will be child of that parent pom. Why are we doing this? This will help us to manage common dependencies at one place, like keeping the version of dependencies consistent.

Initially our project would look like this:

notification-service
│ .gitignore
│ mvnw
│ mvnw.cmd
│ pom.xml

└───.mvn
└───wrapper
maven-wrapper.jar
maven-wrapper.properties

The content of pom is as follows:

<?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>

<groupId>io.Geek8080</groupId>
<artifactId>notification-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>notification-service</name>
<description>Notification Service</description>
<packaging>pom</packaging>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.boot.version>3.2.2</spring.boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<async.logger.lmax.disruptor.version>3.4.4</async.logger.lmax.disruptor.version>
<lombok.version>1.18.28</lombok.version>
<apache.commons.io.version>2.11.0</apache.commons.io.version>
<apache.commons.lang.version>3.12.0</apache.commons.lang.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- Spring boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
</dependency>

<!-- Utility Dependencies -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${apache.commons.io.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${apache.commons.lang.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- logging dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>${async.logger.lmax.disruptor.version}</version>
</dependency>

<!-- Utility Libraries -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>

<!-- Health check libraries and other runtime utilities -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>${spring.boot.version}</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
</dependencies>

<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>

</project>

That’s a lot of code. Skip this part if you understand the xml above.

If you don’t know maven, I have added a link in bottom, you can refer that.

Let’s see the file from top. We are defining the groupId, artifactID, name, version, description for our project. You can put in any value for these fields. Then we have packaging tag. The value is pom. This means, the pom is not for a java application, but a parent pom for other java applications, and there will be no main class associated with this particular pom to be run.

We define a few properties after this, which is used for dependencies in this pom. Note that these properties will be inherited by all the child pom.

Next, we define dependencyManagement tag to define the dependencies and their versions, which might be used in child modules. We define the versions. When a child wants a dependency, it can just use the artifactID and groupId in dependencies to import this particular version. We defined dependencies here, which may or may not be necessary in child modules.

The dependencies tag after dependencyManagement defines the dependencies which will be inherited by all child, whether added to their pom or not. In dependency I have defined three types of libraries. First I have added two dependencies for the logging to work well. Then I have added lombok to have annotation based code generation for models. The third type is utility libraries, you can skip those from your pom, won’t be an issue. I like to have them for easier management, that’s all.

After dependency I have plugins defined. All the plugins are the one which are always there in maven, irrespective of the fact whether it is added or not. You must be thinking why I added it then. I did that just to have specific versions of default plugins so that project doesn’t start to break on any newer version which might come out of the box.

Code till this point: https://github.com/Geek8080/notification-service/tree/init

Creating Modules

Create a folder, dao. This folder will have all the POJO of data classes. I am keeping this separate so that both producer and consumer can just use these classes instead of making copies.

Define this module in parent pom as follows:

    <!-- Modules -->
<modules>
<module>dao</module>
</modules>

Create a new directory, “dao”. Create a new pom in dao folder, which looks like this:

<?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>

<groupId>io.Geek8080</groupId>
<artifactId>dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dao</name>
<description>Module for data definition</description>
<packaging>jar</packaging>

<parent>
<groupId>io.Geek8080</groupId>
<artifactId>notification-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
</project>

And… Voila. You have a multi-module Java project, with one module, “dao”. But, wait, this is a valid multi-module maven project. Java doesn’t recognize this as a valid multi-module project. To make this truly a multi-module, we need to create on more file in “dao”. module-info.java, content is as follows:

module dao {

}

You have a valid java module now. The above file declares a module name dao. We will learn what to put inside braces very soon.

If your IDE is failing to detect the folder dao as module, restart the IDE. You can simply run “mvn validate” in the project folder, to see if your maven project is valid. If you are facing any issue, comment here, I will respond.

Your project structure must look like mine at this point.

notification-service
│ .gitignore
│ mvnw
│ mvnw.cmd
│ pom.xml
|
├───.mvn
│ └───wrapper
│ maven-wrapper.jar
│ maven-wrapper.properties

└───dao
module-info.java
pom.xml

Code: https://github.com/Geek8080/notification-service/tree/1-dao-module-setup

NOTE: Although I have the module-info.java in the base directory of my project, that is not the right location. It should be in the source directory for our project, which will be src/main/java for a maven project.

--

--