Multi-Module Maven
Use when working in a multi-module Maven project. Covers parent POM conventions, shared dependency management, inter-module rules, and build ordering.
View SKILL.mdTypical Structure
my-app/
├── pom.xml ← Parent POM (packaging = pom)
├── my-app-domain/ ← Pure Java domain — no Spring
│ └── pom.xml
├── my-app-application/ ← Use cases — depends on domain
│ └── pom.xml
├── my-app-infrastructure/ ← JPA, Redis, HTTP clients
│ └── pom.xml
└── my-app-web/ ← Spring Boot app, REST — depends on all above
└── pom.xml
Parent POM
<!-- pom.xml (parent) -->
<project>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>my-app-domain</module>
<module>my-app-application</module>
<module>my-app-infrastructure</module>
<module>my-app-web</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.16</version>
</parent>
<!-- Shared versions — all modules inherit -->
<properties>
<java.version>21</java.version>
<mapstruct.version>1.6.3</mapstruct.version>
</properties>
<!-- Dependency management — centralizes versions, NOT adding to classpath -->
<dependencyManagement>
<dependencies>
<!-- Inter-module dependencies -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-app-domain</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-app-application</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-app-infrastructure</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Third-party versions -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Shared plugins -->
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Child Module POM (domain — no Spring)
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>my-app-domain</artifactId>
<dependencies>
<!-- No Spring. No JPA. Pure Java only. -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
Child Module POM (web — the runnable app)
<project>
<parent>...</parent>
<artifactId>my-app-web</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-app-application</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-app-infrastructure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Only in the runnable module, not parent -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Dependency Rules
| Module | Can depend on | Cannot depend on |
|---|---|---|
domain |
Nothing | Everything |
application |
domain |
infrastructure, web |
infrastructure |
domain, application |
web |
web |
All modules | — |
Gotchas
- Agent configures Lombok and MapStruct processors without
lombok-mapstruct-binding— the build then depends on processor order: with MapStruct first, generated mappers miss Lombok's builders/getters and fail to compile; the binding makes it order-independent - Agent puts
spring-boot-maven-pluginin parent POM — only in the runnable module - Agent adds
<dependencies>in parent instead of<dependencyManagement>— adds to all modules' classpath - Agent creates circular dependencies between modules — enforce the dependency direction above
- Agent imports Spring in
domainmodule — domain must be framework-free - Agent uses
${project.version}for inter-module versions — correct, but update parent version to update all - Agent overrides Boot-managed dependency versions without a compatibility reason - prefer the Boot BOM defaults
Examples & templates
bad-parent-pom.xmlexamples
Source on GitHub
<!-- ❌ BAD — every common multi-module Maven mistake -->
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.16</version>
</parent>
<groupId>com.example</groupId>
<artifactId>order-system</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>domain</module>
<module>application</module>
<module>infrastructure</module>
<module>web</module>
</modules>
<!-- ❌ <dependencies> in parent — adds spring-boot-starter-web to ALL modules,
including domain which should have ZERO Spring dependencies -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- ❌ spring-boot-maven-plugin in parent — tries to repackage ALL modules
as executable JARs, breaking library modules (domain, application) -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!-- ❌ Missing <dependencyManagement> — each child module specifies its own versions,
leading to version mismatches and diamond dependency conflicts -->
<!-- ❌ No inter-module version management — children use hardcoded versions:
<dependency>
<groupId>com.example</groupId>
<artifactId>domain</artifactId>
<version>1.0.0-SNAPSHOT</version> instead of ${project.version}
</dependency>
-->
</project>good-parent-pom.xmlexamples
Source on GitHub
<!-- ✅ GOOD — dependencyManagement in parent, plugin only in web module -->
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.16</version>
</parent>
<groupId>com.example</groupId>
<artifactId>order-system</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <!-- parent must be pom -->
<modules>
<module>domain</module>
<module>application</module>
<module>infrastructure</module>
<module>web</module>
</modules>
<properties>
<java.version>21</java.version>
<mapstruct.version>1.6.3</mapstruct.version>
</properties>
<!-- dependencyManagement — declares versions, does NOT add to classpath -->
<dependencyManagement>
<dependencies>
<!-- Inter-module dependencies -->
<dependency>
<groupId>com.example</groupId>
<artifactId>domain</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>application</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>infrastructure</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Third-party versions -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Shared test dependencies only — compile deps go in child modules -->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>parent-pom.xmltemplates
Source on GitHub
<?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>3.5.16</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>order-system</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Order System</name>
<modules>
<module>domain</module> <!-- pure Java, no Spring -->
<module>application</module> <!-- use cases, depends on domain -->
<module>infrastructure</module> <!-- JPA, REST clients, depends on domain + application -->
<module>web</module> <!-- Spring Boot app, depends on all -->
</modules>
<properties>
<java.version>21</java.version>
<mapstruct.version>1.6.3</mapstruct.version>
</properties>
<!-- Declare versions here — children import without specifying version -->
<dependencyManagement>
<dependencies>
<!-- Inter-module -->
<dependency>
<groupId>com.example</groupId>
<artifactId>domain</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>application</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>infrastructure</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Third-party -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Only truly shared dependencies (Lombok is compile-time only) -->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<!-- Plugin management — children opt in, not forced -->
<pluginManagement>
<plugins>
<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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>Verification scope
- Compiled sources
- No
- Runtime checks
- No
Coverage describes checked-in fixtures, not a guarantee for every snippet or project.
Coverage matrix & agent evidenceADD TO YOUR AGENTInstall Boot 3 skills
Install Boot 3 skills
claude plugin marketplace add rrezartprebreza/spring-boot-skills
claude plugin install spring-boot-3-skills@spring-boot-skillscodex plugin marketplace add rrezartprebreza/spring-boot-skills
codex plugin add spring-boot-3-skills@spring-boot-skills