Spring Boot Skills
GitHub
Architecture

Multi-Module Maven

Use when working in a multi-module Maven project. Covers parent POM conventions, shared dependency management, inter-module rules, build ordering, and Spring Boot 4 modular starter selection.

View SKILL.md

Typical 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>4.1.0</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-webmvc</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 —

Boot 4 Modular Starters

Spring Boot 4 splits the framework into focused modules (spring-boot-<technology>, root package org.springframework.boot.<technology>). Pick one starter per technology, per module:

  • Several starters were renamed: spring-boot-starter-web → spring-boot-starter-webmvc, spring-boot-starter-oauth2-client → spring-boot-starter-security-oauth2-client (and the other OAuth2 starters gained the security- prefix); spring-boot-starter-data-jpa keeps its name
  • Starters follow spring-boot-starter-<technology>; test starters follow spring-boot-starter-<technology>-test (no plain spring-boot-starter-test needed alongside them)
  • Flyway/Liquibase no longer arrive transitively — add spring-boot-starter-flyway / spring-boot-starter-liquibase in the module that owns migrations (usually infrastructure)
  • AOP starter is spring-boot-starter-aspectj (renamed from spring-boot-starter-aop)
  • WAR deploys to external Tomcat use spring-boot-starter-tomcat-runtime
  • spring-boot-starter-classic / spring-boot-starter-test-classic bundle the old monolithic set — transitional only, don't use in new modules
  • Optional Maven dependencies are excluded from the repackaged jar by default — set <includeOptional>true</includeOptional> on spring-boot-maven-plugin if you rely on them

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-plugin in 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 domain module — domain must be framework-free
  • Agent uses ${project.version} for inter-module versions — correct, but update parent version to update all
  • Agent adds spring-boot-starter-web — renamed spring-boot-starter-webmvc in Boot 4
  • Agent adds spring-boot-starter-aop — renamed spring-boot-starter-aspectj in Boot 4
  • Agent adds bare flyway-core/liquibase-core expecting Boot to configure them — use spring-boot-starter-flyway / spring-boot-starter-liquibase
  • Agent adds spring-boot-starter-test next to spring-boot-starter-webmvc-test — the -test starters are self-contained in Boot 4
  • Agent reaches into auto-configuration classes from shared modules — their members are no longer public API in Boot 4
  • Agent overrides Boot-managed dependency versions without a compatibility reason - prefer the Boot BOM defaults
FROM THE REPOSITORY

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>4.1.0</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-webmvc to ALL modules,
         including domain which should have ZERO Spring dependencies -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- ❌ spring-boot-starter-classic — the Boot 4 transitional monolith starter drags in
             every module; pick focused spring-boot-starter-<technology> starters per module -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-classic</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>4.1.0</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>4.1.0</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 evidence
ADD TO YOUR AGENT

Install Boot 4 skills

claude plugin marketplace add rrezartprebreza/spring-boot-skills
claude plugin install spring-boot-4-skills@spring-boot-skills