Spring BootSpring Boot Dependency Management
Spring Boot

Spring Boot Dependency Management

Spring Boot's dependency management eliminates one of the most painful parts of Java development — version conflicts. Through a parent POM and a Bill of Materials (BOM), Spring Boot pre-selects compatible versions for hundreds of libraries so you never have to manually coordinate versions again.

The Problem — Version Hell

Before Spring Boot's dependency management, adding JPA to a Spring project meant manually researching and coordinating the versions of spring-orm, spring-jdbc, hibernate-core, hibernate-entitymanager, jakarta.persistence-api, and a connection pool — all of which had to be compatible with each other and with your version of Spring Framework. One wrong version caused cryptic NoSuchMethodError or ClassNotFoundException failures at runtime that could take hours to diagnose. Spring Boot solves this completely through two mechanisms: the parent POM and the Bill of Materials (BOM). Both declare the exact version of every library in the Spring ecosystem — tested and guaranteed to work together. You declare the dependency; Spring Boot supplies the version.

The Parent POM

The simplest way to use Spring Boot dependency management in Maven is to inherit from spring-boot-starter-parent. It provides version management for hundreds of libraries, sensible compiler settings, resource filtering, and plugin configuration.
XML
<!-- pom.xml — inherit from Spring Boot parent: -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
    <relativePath/>
</parent>

<!-- Now declare dependencies WITHOUT versions — parent manages them all: -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- No version needed -->
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
        <!-- Version managed by Spring Boot parent — currently 8.x -->
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
        <!-- Version managed by Spring Boot parent -->
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<!-- What spring-boot-starter-parent also gives you beyond version management:
     - Java source/target version (set spring.jmv.version property to change)
     - UTF-8 source encoding
     - Resource filtering for application.properties and application.yml
     - Sensible plugin configuration (maven-compiler-plugin, maven-jar-plugin)
     - Exec plugin for mvn spring-boot:run
     - Repackage goal to produce the fat executable JAR -->

What the Parent POM Manages

Spring Boot's parent POM inherits from spring-boot-dependencies, which is the actual BOM containing all the version declarations. It covers the entire Java ecosystem — not just Spring libraries.
XML
<!-- A sample of the ~300+ libraries version-managed by Spring Boot 3.2: -->

<!-- Spring ecosystem: -->
<!-- spring-framework          6.1.x  -->
<!-- spring-security           6.2.x  -->
<!-- spring-data-commons        3.2.x  -->
<!-- spring-data-jpa            3.2.x  -->
<!-- spring-kafka               3.1.x  -->

<!-- Persistence: -->
<!-- hibernate-core            6.4.x  -->
<!-- HikariCP                  5.1.x  -->
<!-- flyway-core               9.22.x -->
<!-- liquibase-core            4.24.x -->

<!-- JSON: -->
<!-- jackson-databind          2.16.x -->
<!-- gson                      2.10.x -->

<!-- Testing: -->
<!-- junit-jupiter             5.10.x -->
<!-- mockito-core              5.7.x  -->
<!-- assertj-core              3.24.x -->
<!-- testcontainers            1.19.x -->

<!-- Logging: -->
<!-- logback-classic           1.4.x  -->
<!-- log4j2                    2.21.x -->
<!-- slf4j-api                 2.0.x  -->

<!-- Utilities: -->
<!-- lombok                    1.18.x -->
<!-- mapstruct                 1.5.x  -->
<!-- micrometer-core           1.12.x -->

// Check which version Spring Boot manages for any library:
// mvn dependency:tree | grep jackson
// Or browse: https://docs.spring.io/spring-boot/docs/current/reference/html/dependency-versions.html

Overriding Managed Versions

Spring Boot's managed versions are defaults — you can override any of them with a property in your pom.xml when you need a different version.
XML
<!-- Override a managed version using the property Spring Boot exposes: -->
<properties>
    <java.version>21</java.version>

    <!-- Override specific library versions: -->
    <jackson-bom.version>2.16.1</jackson-bom.version>
    <hibernate.version>6.4.1.Final</hibernate.version>
    <mockito.version>5.8.0</mockito.version>
    <testcontainers.version>1.19.3</testcontainers.version>
    <lombok.version>1.18.30</lombok.version>
    <flyway.version>10.3.0</flyway.version>
</properties>

<!-- The property names follow a pattern: {artifactId}.version -->
<!-- Find the exact property name in spring-boot-dependencies BOM:   -->
<!-- https://github.com/spring-projects/spring-boot/blob/main/       -->
<!-- spring-boot-project/spring-boot-dependencies/build.gradle       -->

<!-- Add a library NOT managed by Spring Boot — specify version manually: -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
    <!-- Not in Spring Boot's BOM — you manage this version yourself -->
</dependency>

<!-- Force a transitive dependency to a specific version: -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>2.2</version>
        </dependency>
    </dependencies>
</dependencyManagement>

Using the BOM Without the Parent POM

If your project already has a parent POM (common in multi-module enterprise projects), you can't inherit from spring-boot-starter-parent. Instead, import spring-boot-dependencies as a BOM in the dependencyManagement section — you get all the version management without changing your parent.
XML
<!-- Use BOM import when you can't inherit from spring-boot-starter-parent: -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.2.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- Now declare dependencies without versions — same as using the parent: -->
<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>

<!-- What you lose vs the parent POM:
     - Default plugin configurations (need to add spring-boot-maven-plugin manually)
     - Resource filtering for application.properties
     - Default Java version, encoding settings
     You gain: freedom to use your own parent POM -->

<!-- You must add the Spring Boot Maven plugin explicitly: -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>3.2.0</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Dependency Management with Gradle

Gradle users apply the Spring Boot plugin and the dependency management plugin to get the same version management as Maven's parent POM.
groovy
// build.gradle (Groovy DSL):
plugins {
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'java'
}

// The dependency-management plugin imports Spring Boot's BOM automatically
// when the Spring Boot plugin is applied — no explicit BOM import needed

dependencies {
    // No versions needed — all managed by Spring Boot BOM:
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'

    runtimeOnly 'com.mysql:mysql-connector-j'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
}

// Override a managed version:
ext['jackson-bom.version'] = '2.16.1'
ext['hibernate.version'] = '6.4.1.Final'
ext['mockito.version'] = '5.8.0'

// build.gradle.kts (Kotlin DSL):
plugins {
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.4"
    kotlin("jvm") version "1.9.21"
    kotlin("plugin.spring") version "1.9.21"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("com.mysql:mysql-connector-j")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

// Override versions in Kotlin DSL:
extra["jackson-bom.version"] = "2.16.1"

Analyzing Dependencies

Understanding what's actually on your classpath — and where each version came from — is essential for debugging dependency conflicts and keeping your project lean.
Shell
# Maven — show the full dependency tree:
mvn dependency:tree

# Filter to a specific artifact:
mvn dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind

# Show which dependency is pulling in a specific transitive dependency:
mvn dependency:tree -Dverbose -Dincludes=org.yaml:snakeyaml

# List all managed versions (from BOM):
mvn help:effective-pom | grep -A2 "jackson"

# Check for dependency updates (with versions-maven-plugin):
mvn versions:display-dependency-updates

# Gradle — dependency tree:
./gradlew dependencies

# Filter to a specific configuration:
./gradlew dependencies --configuration runtimeClasspath

# Show why a specific dependency is included:
./gradlew dependencyInsight --dependency jackson-databind

# Check for dependency updates (with ben-manes plugin):
./gradlew dependencyUpdates

Multi-Module Projects

In multi-module Maven projects, the parent module inherits from Spring Boot and child modules inherit from the parent. Dependency versions are declared once and shared across all modules.
XML
<!-- Parent pom.xml — the root of the multi-module project: -->
<groupId>com.example</groupId>
<artifactId>myapp-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

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

<modules>
    <module>myapp-core</module>
    <module>myapp-api</module>
    <module>myapp-service</module>
    <module>myapp-web</module>
</modules>

<!-- Declare shared dependencies for all child modules: -->
<dependencyManagement>
    <dependencies>
        <!-- Internal module versions — managed centrally: -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>myapp-core</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- Third-party libs not in Spring Boot BOM: -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- Child module pom.xml (myapp-web): -->
<parent>
    <groupId>com.example</groupId>
    <artifactId>myapp-parent</artifactId>
    <version>1.0.0</version>
</parent>

<artifactId>myapp-web</artifactId>

<dependencies>
    <!-- Spring Boot starter — version from Spring Boot BOM: -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Internal module — version from parent dependencyManagement: -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>myapp-service</artifactId>
    </dependency>
    <!-- Third-party — version from parent dependencyManagement: -->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
    </dependency>
</dependencies>

The Spring Boot Maven Plugin

The Spring Boot Maven plugin is what turns a regular JAR into a self-contained executable fat JAR. It repackages all dependencies into a single file and configures the manifest so the JAR is directly runnable with java -jar.
XML
<!-- Included automatically when inheriting from spring-boot-starter-parent: -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <!-- Exclude Lombok from the final JAR (compile-only): -->
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

# Key Maven goals provided by the plugin:

# Run the application directly (no JAR needed):
mvn spring-boot:run

# Build the executable fat JAR:
mvn clean package
# Produces: target/myapp-1.0.0.jar  (~30-80MB — all dependencies included)

# Run the JAR on any machine with Java:
java -jar target/myapp-1.0.0.jar

# Build a Docker image directly (no Dockerfile needed):
mvn spring-boot:build-image

# Run integration tests:
mvn spring-boot:start   # start app in background
mvn failsafe:integration-test
mvn spring-boot:stop    # stop app after tests

# Generate build info (exposes in /actuator/info):
mvn spring-boot:build-info