Creating a Multi-Module Maven Project With Spring Boot

A multi-module Maven project gives a Spring Boot application a clear structure as it grows. Instead of placing controllers, business logic, persistence code, and shared models in one large module, you can separate responsibilities while keeping a single build process.

This approach suits Australian development teams working across Sydney, Melbourne, Brisbane, or remote locations. It supports independent testing, consistent dependency management, and easier deployment to cloud platforms used by local businesses, including systems that process GST, Australian payment methods, and customer data covered by the Privacy Act 1988.

Why Split A Spring Boot Application

A Maven module is a smaller project managed by a parent project. The parent controls common versions and build plugins, while child modules contain application code. A typical design might include an API module, a service module, a persistence module, and a shared module.

This arrangement reduces coupling. Developers can change a JPA repository without placing web controllers at risk, and automated tests can focus on one layer. It also makes ownership clearer when a Melbourne product team maintains the API while a Brisbane-based team works on integrations.

A common directory structure looks like this:

shop-platform/
├── pom.xml
├── shop-api/
├── shop-service/
├── shop-persistence/
└── shop-common/

Set Up The Parent Maven Project

Create a root directory and add a parent pom.xml. The parent uses packaging set to pom, which tells Maven that its main role is to manage child modules rather than produce a JAR file.

<project>
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.5</version>
  </parent>

  <groupId>au.com.javawhizz</groupId>
  <artifactId>shop-platform</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <modules>
    <module>shop-common</module>
    <module>shop-persistence</module>
    <module>shop-service</module>
    <module>shop-api</module>
  </modules>
</project>

Spring Boot 3 requires a compatible Java version, so define Java 17 or later in the parent properties. Centralising this value prevents one module from compiling with Java 17 while another silently targets a different release.

Create Child Module POMs

Each directory needs its own pom.xml. A shared module might contain DTOs, enums, and exception classes used by several other modules.

<project>
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>au.com.javawhizz</groupId>
    <artifactId>shop-platform</artifactId>
    <version>1.0.0</version>
  </parent>

  <artifactId>shop-common</artifactId>
</project>

The service module can depend on both the common and persistence modules. Maven uses these relationships to determine the build order and place required JAR files on the classpath.

<dependencies>
  <dependency>
    <groupId>au.com.javawhizz</groupId>
    <artifactId>shop-common</artifactId>
    <version>${project.version}</version>
  </dependency>
  <dependency>
    <groupId>au.com.javawhizz</groupId>
    <artifactId>shop-persistence</artifactId>
    <version>${project.version}</version>
  </dependency>
</dependencies>

Avoid adding dependencies in both the parent and every child without a reason. Put reusable version declarations in <dependencyManagement>, then add the actual dependency only to modules that need it.

Choose Module Boundaries Carefully

A multi-module design works best when each module has a clear responsibility. Do not create a separate module for every package, because excessive separation can make a small application harder to navigate.

Useful boundaries for a Spring Boot system include:

Keep dependencies flowing in one direction. The API can call the service layer, and the service layer can call persistence. Persistence should not depend on controllers, as that would create an unnecessary cycle.

Wire Spring Boot Components

The main class normally belongs in the API module, with a package above the other application packages. This allows Spring component scanning to discover services and repositories.

@SpringBootApplication(scanBasePackages = "au.com.javawhizz")
public class ShopApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShopApplication.class, args);
    }
}

If modules use packages outside the scan path, use explicit @ComponentScan, @EntityScan, or @EnableJpaRepositories. In most cases, a consistent root package is simpler and reduces configuration errors.

For Australian commerce applications, integration code may connect to an Australian payment gateway, calculate GST, or format dates using the Australia/Sydney time zone. Keep those concerns in dedicated services rather than embedding them in controllers.

Build, Test, And Run The Project

Run Maven from the root directory so it builds every module in dependency order:

./mvnw clean verify

The verify phase compiles source code, runs unit tests, packages modules, and executes configured verification plugins. To start the Spring Boot application, use the module containing the main class:

./mvnw -pl shop-api -am spring-boot:run

The -pl option selects the API module, while -am also builds required modules. This is useful for local development, including teams working across AEST and AWST time zones where repeatable builds reduce environment differences.

Prepare Production Configuration

Keep database credentials, API keys, and payment secrets outside source control. Spring profiles can separate local, test, staging, and production settings, while environment variables or a managed secrets service supply sensitive values.

Before release, check these operational details:

Package the API module as an executable JAR and run it in a container or managed Java service. Maven’s reactor build ensures that the application uses the current versions of its internal modules, giving deployment pipelines a dependable and repeatable artefact.