Spring Boot
Maven Setup
Maven is the most widely used build tool for Spring Boot applications. It manages dependencies, compiles source code, runs tests, and packages the application into an executable JAR. Understanding the Spring Boot Maven setup — the parent POM, dependency management, plugin configuration, and the Maven wrapper — gives you full control over your build pipeline.
Spring Boot and Maven
Maven is a project management and build automation tool that uses a pom.xml (Project Object Model) file to describe the project, its dependencies, and how to build it. Spring Boot integrates with Maven through two mechanisms: the spring-boot-starter-parent POM which provides dependency management and sensible build defaults, and the spring-boot-maven-plugin which packages your application into a self-contained executable JAR.
Every Spring Boot Maven project has the same three essential elements: inheriting from spring-boot-starter-parent, declaring spring-boot-starter dependencies without versions, and including the spring-boot-maven-plugin in the build section. Everything else — compiler settings, resource filtering, plugin management — is inherited from the parent and requires no manual configuration.
The Complete pom.xml
A production-ready Spring Boot pom.xml for a REST API with JPA, Security, and testing. Every element is intentional — nothing here is boilerplate you can remove.
XML
<?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>
<!-- Spring Boot parent — provides all dependency versions and plugin config: -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<!-- Project identity: -->
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>myapp</name>
<description>My Spring Boot REST API</description>
<properties>
<!-- Java version — used by maven-compiler-plugin: -->
<java.version>21</java.version>
<!-- Override a managed dependency version if needed: -->
<!-- <jackson-bom.version>2.16.1</jackson-bom.version> -->
</properties>
<dependencies>
<!-- Web — Spring MVC + embedded Tomcat + Jackson: -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA — Hibernate + Spring Data JPA + HikariCP: -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Security — Spring Security: -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Validation — Jakarta Bean Validation + Hibernate Validator: -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Actuator — health, metrics, env endpoints: -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Database driver — runtime only, not needed at compile time: -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- H2 — in-memory DB for development and tests: -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Flyway — database migration: -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<!-- Lombok — compile-time code generation:
optional=true: not inherited by projects depending on this one -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- DevTools — auto-restart and LiveReload in development:
scope=runtime: on classpath at runtime but not compile time
optional=true: not transitively included
Automatically disabled when running packaged JAR -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Test — JUnit 5, Mockito, AssertJ, MockMVC, Spring Test: -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Security test utilities: -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Spring Boot plugin — creates executable fat JAR: -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- Exclude Lombok from the packaged JAR (compile-only): -->
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>Essential Maven Commands
The Maven wrapper (mvnw) is included in every generated project — run all commands through it instead of a globally installed Maven to ensure consistent versions across machines and CI.
Shell
# The Maven wrapper — always use this instead of mvn directly:
# Unix/macOS:
./mvnw <goal>
# Windows:
mvnw.cmd <goal>
# ── DEVELOPMENT ──────────────────────────────────────────────────────
# Run the application:
./mvnw spring-boot:run
# Run with a specific profile:
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
# Run with property overrides:
./mvnw spring-boot:run -Dspring-boot.run.arguments="--server.port=9090 --spring.profiles.active=dev"
# ── BUILDING ──────────────────────────────────────────────────────────
# Compile source code:
./mvnw compile
# Run tests:
./mvnw test
# Package into JAR (runs tests first):
./mvnw package
# Package skipping tests:
./mvnw package -DskipTests
# Clean compiled output and package:
./mvnw clean package
# Install to local Maven repository (~/.m2):
./mvnw install
# ── RUNNING THE JAR ───────────────────────────────────────────────────
# Run the packaged JAR:
java -jar target/myapp-1.0.0-SNAPSHOT.jar
# Run with profile:
java -jar target/myapp.jar --spring.profiles.active=prod
# Run with property override:
java -jar target/myapp.jar --server.port=9090
# ── DEPENDENCY MANAGEMENT ─────────────────────────────────────────────
# Show full dependency tree:
./mvnw dependency:tree
# Show where a specific dependency comes from:
./mvnw dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind
# Check for dependency updates:
./mvnw versions:display-dependency-updates
# Check for plugin updates:
./mvnw versions:display-plugin-updates
# Download sources and javadoc for dependencies (IDE navigation):
./mvnw dependency:sources
# ── USEFUL DIAGNOSTICS ────────────────────────────────────────────────
# Show the effective POM (with all inherited values expanded):
./mvnw help:effective-pom
# Show the effective settings:
./mvnw help:effective-settings
# Describe a plugin goal:
./mvnw help:describe -Dplugin=spring-boot -Dgoal=runDependency Scopes
Maven dependency scopes control which classpath a dependency appears on and whether it is included in the final packaged JAR. Getting scopes right keeps your JAR lean and your build correct.
XML
<!-- compile (default) — available at compile, test, and runtime:
Included in the final JAR.
Use for: all Spring Boot starters, your core dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- scope: compile (default — no need to declare) -->
</dependency>
<!-- runtime — NOT available at compile time, available at runtime:
Included in the final JAR.
Use for: JDBC drivers, database connectors — you compile against
the javax.sql.DataSource interface, not the MySQL driver class -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- test — only available during test compilation and execution:
NOT included in the final JAR.
Use for: JUnit, Mockito, test utilities -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- provided — available at compile and test time, NOT at runtime:
NOT included in the final JAR — provided by the container.
Use for: Servlet API when deploying to an external server (rare with Boot) -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- optional — compile scope but NOT inherited by dependents:
Included in this project's final JAR.
Use for: Lombok (compile-only code gen), optional framework extensions -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>Multi-Module Maven Projects
Large applications are split into multiple Maven modules — each a separate project with its own pom.xml, all managed by a parent aggregator POM. Spring Boot dependency management flows from the root parent down through all modules.
XML
<!-- Root pom.xml — the aggregator (packaging=pom): -->
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
</parent>
<groupId>com.example</groupId>
<artifactId>myapp-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <!-- aggregator — not a JAR -->
<!-- Child modules: -->
<modules>
<module>myapp-core</module> <!-- domain entities, shared utilities -->
<module>myapp-data</module> <!-- repositories, database config -->
<module>myapp-service</module> <!-- business logic -->
<module>myapp-web</module> <!-- REST controllers, the runnable module -->
</modules>
<!-- Versions managed centrally for ALL modules: -->
<dependencyManagement>
<dependencies>
<!-- Internal module cross-references: -->
<dependency>
<groupId>com.example</groupId>
<artifactId>myapp-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>myapp-data</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>myapp-service</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
<!-- myapp-web/pom.xml — the runnable Spring Boot module: -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>myapp-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>myapp-web</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Internal deps — version from parent dependencyManagement: -->
<dependency>
<groupId>com.example</groupId>
<artifactId>myapp-service</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Only the runnable module needs the Spring Boot plugin: -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
# Build all modules from the root:
./mvnw clean package
# Build a specific module only:
./mvnw clean package -pl myapp-web -am
# -pl myapp-web: build only this module
# -am: also build modules it depends on (also-make)Maven Wrapper
The Maven wrapper (mvnw) is a shell script that downloads and uses a specific Maven version defined in the project. It guarantees every developer and every CI server uses exactly the same Maven version without requiring a global installation.
Shell
# Maven wrapper files included in every generated Spring Boot project:
# mvnw ← shell script (Unix/macOS/Linux)
# mvnw.cmd ← batch script (Windows)
# .mvn/wrapper/
# maven-wrapper.properties ← specifies which Maven version to use
# maven-wrapper.jar ← bootstrap JAR that downloads Maven
# .mvn/wrapper/maven-wrapper.properties:
# distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/
# apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
# wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/
# maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
# On first run, mvnw downloads the specified Maven version to ~/.m2/wrapper/
# Subsequent runs use the cached download — fast
# Always commit the wrapper files to version control:
git add mvnw mvnw.cmd .mvn/
git commit -m "Add Maven wrapper"
# Make the wrapper executable (after git clone on Unix):
chmod +x mvnw
# Add a Maven wrapper to an existing project:
mvn wrapper:wrapper # uses current Maven version
mvn wrapper:wrapper -Dmaven=3.9.6 # use a specific version
# Verify which Maven version the wrapper uses:
./mvnw --version
# Apache Maven 3.9.6
# Maven home: /Users/alice/.m2/wrapper/dists/apache-maven-3.9.6/...
# Java version: 21, vendor: Eclipse Adoptium