A Multi-module Maven Project is a software architecture pattern that allows developers to split a large project into multiple interconnected sub-modules, all managed by a single parent project.
This parent-child Maven structure aligns well with microservices architecture. For example, a parent project can contain gateway
and auth-service
sub-modules. After packaging, these two modules can be deployed and maintained separately.
It’s important to note that this parent-child relationship is for build management, not a functional hierarchy. The parent POM centralizes dependency management, but it doesn’t mean gateway
necessarily depends on auth-service
.
Programmatic Dependencies: If the gateway
module needs to use classes from auth-service
, you would add a dependency in its pom.xml
. However, in a typical microservices setup, gateway
and auth-service
communicate via HTTP APIs or OAuth tokens, eliminating the need for a direct JAR dependency.
A typical multi-module project structure in Maven looks like this:
workspace/
├─ pom.xml <-- Parent POM, defines dependencyManagement, pluginManagement, versions
├─ gateway/ <-- Child module
│ └─ pom.xml
└─ auth-service/ <-- Child module
└─ pom.xml
Parent POM (workspace/pom.xml):
This file manages versions, dependencies (dependencyManagement
), and plugins (pluginManagement
).
-
Packaging:
<packaging>pom</packaging>
(This POM acts as a container and does not produce a JAR file). -
Primary Functions:
- Centralizes management for all sub-modules.
- Defines common dependencies and their versions (e.g., Lombok, Spring Security, MyBatis).
- Configures shared build settings.
- Manages plugin versions and configurations.
Child Modules:
Each sub-module has its own pom.xml
that inherits configuration from the parent POM via the <parent>
tag.
They can define their own specific dependencies and plugins.
They can be packaged into executable JAR or WAR files.
gateway/pom.xml
and auth-service/pom.xml
simply inherit from the parent POM, allowing them to focus on their own module-specific dependencies.
This approach ensures that multiple modules share the same version management, simplifying maintenance.
Advantages of a Multi-module Project
- Centralized Version Management: All sub-modules use the same Spring Boot, Java, and plugin versions, reducing conflicts.
- Centralized Build Process: Running
mvn clean install
in the parent directory automatically builds all sub-modules in the correct order. - Module Decoupling:
gateway
focuses on routing and security.auth-service
focuses on authentication and token management.
- Independent Testing and Deployment: Each module can be tested and deployed individually.
- Shared Dependency and Plugin Management: The parent POM manages common dependencies, so child modules don’t need to declare versions repeatedly.
Use Cases
Suitable Scenarios
- You have a small number of services (e.g., just a gateway and an auth service).
- These services are closely related and need to evolve together with synchronized versions.
- You want unified dependency management to reduce version conflicts.
- Typically, the parent POM defines a version, and all modules inherit it, which offers less flexibility but more consistency.
Unsuitable Scenarios
- Too Many Services: With a large number of services, this structure can lead to high coupling.
- Increased Coupling: Although modules are logically separate, being under one parent project increases their coupling. The parent POM can become bloated with extensive
dependencyManagement
,pluginManagement
, and version properties, making it difficult to maintain, especially for team members unfamiliar with Maven. For instance, updating the Spring Boot version for thegateway
module might force an upgrade forauth-service
and others. - Longer Build Times: By default, Maven builds all sub-modules (
mvn install
includes all modules). Even if you only change one module, the entire workspace may be recompiled, putting a strain on CI/CD pipelines. - Difficult Version Control: If sub-projects require independent versioning (e.g., Gateway
1.0.0
and Auth-Service2.1.0
), a parent-child structure is inconvenient. - Monorepo vs. Polyrepo: Multi-module projects are typically managed in a single repository (monorepo). This can be cumbersome for microservices that require independent deployment and CI/CD pipelines. If your team prefers a separate repository for each service (polyrepo), this structure is not a good fit.
Creating a Multi-Module Spring Boot Maven Project in VS Code
1. Create the Parent Project
First, create the top-level workspace (the parent project) in VS Code.
Create Java Project
> Maven (create from archetype)
> No Archetype...
> (Enter GroupID)
> (Enter ArtifactId)
After creation, delete the src/
and target/
directories.
Next, modify the pom.xml
to look 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>workspace.hoohoo.top</groupId>
<artifactId>workspace2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging><!-- Set packaging to pom -->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
2. Create a Child Module
In the JAVA PROJECTS
view, click the +
icon.
Spring Boot
> Maven Project
> 3.3.5 (or your desired version)
> (Enter Group ID)
> (Enter Artifact ID, e.g., "gateway")
> Jar
> (Select Dependencies, e.g., Spring Web + Lombok)
Once the project is generated, click “Add to Workspace”.
3. Configure the Parent pom.xml
Cut the <parent>
section (which specifies spring-boot-starter-parent
) from the child module’s pom.xml
and paste it into the parent pom.xml
. This provides unified Spring Boot support for all modules. Also, add a <modules>
section to specify the child module’s artifactId
.
<?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 http://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>3.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>workspace.hoohoo.top</groupId>
<artifactId>workspace2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>gateway</module>
</modules>
<dependencyManagement>
<!-- Centralized dependency versions go here -->
</dependencyManagement>
<build>
<pluginManagement>
<!-- Centralized plugin configurations go here -->
</pluginManagement>
</build>
Complete Parent POM 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 http://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>3.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>workspace.hoohoo.top</groupId>
<artifactId>workspace2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>gateway</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<version>1.18.38</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
4. Configure the Child pom.xml
Next, modify the child module’s <parent>
section to point to your parent project. Remove <relativePath/>
so Maven looks for the parent in your local repository and workspace.
<parent>
<groupId>top.hoohoo.workspace</groupId>
<artifactId>workspace</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>gateway</artifactId> <!-- Child module's unique identifier -->
<dependencies>
<!-- Declare dependencies without specifying versions -->
</dependencies>
Complete Child POM Example: (Notice versions are no longer needed here as they are managed by the parent)
<?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>workspace.hoohoo.top</groupId>
<artifactId>workspace2</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>gateway.hoohoo.top</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
After finishing the setup, open the Command Palette (Ctrl+Shift+P
or Cmd+Shift+P
) and run Developer: Reload Window
.
Check the JAVA PROJECTS
view. If you see the parent project with the child module nested underneath, the setup is complete. (If you only see the parent, double-check that the <modules>
section is correctly configured in the parent pom.xml
).
5. Develop the Child Module
Create a DTO (Data Transfer Object)
Create a record
named HelloResponse
:
package gateway.hoohoo.top.gateway.dto;
import java.time.LocalTime;
public record HelloResponse(String hello, LocalTime timestamp) {
}
Create a Controller
Create a class named HelloController
:
package gateway.hoohoo.top.gateway.Controller;
import java.time.LocalTime;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import gateway.hoohoo.top.gateway.dto.HelloResponse;
@RestController
public class HelloController {
@GetMapping("/hello")
public ResponseEntity<HelloResponse> Hello(){
String message = "Hello world";
return ResponseEntity.ok(new HelloResponse(
message,
LocalTime.now()
));
}
}
6. Build and Run
Run this command in the parent project’s directory to build all modules. The parent itself won’t generate a JAR/WAR file.
mvn clean install
Build a Single Module Run this command in a child module’s directory to build only that module.
mvn clean install
7. Run the Service
Run the following command in the child module’s directory to start the service:
mvn spring-boot:run