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

  1. Too Many Services: With a large number of services, this structure can lead to high coupling.
  2. 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 the gateway module might force an upgrade for auth-service and others.
  3. 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.
  4. Difficult Version Control: If sub-projects require independent versioning (e.g., Gateway 1.0.0 and Auth-Service 2.1.0), a parent-child structure is inconvenient.
  5. 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