Back to blog

Spring Boot Dependency Management: Maven & Gradle

javaspring-bootmavengradlebackend
Spring Boot Dependency Management: Maven & Gradle

Giới Thiệu

Mọi Spring Boot project đều bắt đầu với một build file — pom.xml hoặc build.gradle. Những file này define xem application của bạn depend vào library nào, cách resolve chúng, và cách project được build. Làm đúng dependency management nghĩa là ít version conflict hơn, build nhanh hơn, và codebase dễ maintain hơn.

Trong bài hướng dẫn chi tiết này, chúng ta sẽ cover:

  • Cách Maven và Gradle handle dependency
  • Spring Boot Starters và auto-configuration
  • Spring Boot BOM (Bill of Materials)
  • Quản lý transitive dependency và version conflict
  • Setup multi-module project
  • Dependency security scanning
  • Best practices cho production-ready build

Yêu cầu: Quen thuộc với Java và các concept cơ bản của Spring Boot. Nếu bạn mới bắt đầu, hãy đọc Getting Started with Spring Boot trước.


Maven vs Gradle: Chọn Build Tool Nào?

Cả Maven và Gradle đều là lựa chọn tuyệt vời. So sánh nhanh:

FeatureMavenGradle
ConfigurationXML (pom.xml)Groovy/Kotlin DSL (build.gradle)
Build SpeedTốt (với -T parallel)Nhanh hơn (incremental + cache)
Learning CurveThấp hơn (convention-heavy)Trung bình (flexible DSL)
EcosystemPlugin ecosystem lớn nhấtĐang phát triển, Kotlin-first
Spring Boot SupportFirst-classFirst-class
Dependency LockingQua pluginBuilt-in
Build CacheChỉ localLocal + remote

Recommendation: Dùng Maven nếu team đã quen. Chọn Gradle cho project mới mà build speed quan trọng — đặc biệt trong CI/CD pipeline.


Hiểu Spring Boot Starters

Spring Boot Starters là những dependency bundle được curate sẵn, pull vào mọi thứ bạn cần cho một feature cụ thể. Thay vì manually add 5-10 library riêng lẻ, bạn chỉ cần thêm một starter duy nhất.

Cách Starters Hoạt Động

Thêm spring-boot-starter-web là bạn có ngay embedded Tomcat server, Jackson cho JSON, Spring MVC, và toàn bộ transitive dependency — với version tương thích được đảm bảo.

Bảng Tham Khảo Các Starter Phổ Biến

StarterBao Gồm
spring-boot-starter-webSpring MVC, Tomcat, Jackson
spring-boot-starter-data-jpaHibernate, Spring Data JPA, HikariCP
spring-boot-starter-securitySpring Security, authentication filter
spring-boot-starter-validationHibernate Validator, Jakarta Validation
spring-boot-starter-actuatorHealth check, metric, monitoring endpoint
spring-boot-starter-cacheSpring Cache abstraction
spring-boot-starter-mailJavaMail, Spring mail integration
spring-boot-starter-testJUnit 5, Mockito, AssertJ, MockMvc
spring-boot-starter-webfluxReactive web với Netty
spring-boot-starter-amqpRabbitMQ integration
spring-boot-starter-data-redisRedis client, Spring Data Redis

Những Gì KHÔNG Phải Là Starter

Không phải mọi Spring Boot dependency đều là starter. Đây là những standalone library bạn cần add riêng:

<!-- Những cái này KHÔNG phải starter — add riêng -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.3.0</version>
</dependency>
 
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>
 
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

Maven Dependency Management

Setup Project với Spring Initializr

Cách dễ nhất để bắt đầu là Spring Initializr. Nó generate pom.xml với Spring Boot parent POM và các starter bạn chọn.

Spring Boot Parent POM

Mọi Maven-based Spring Boot project đều inherit từ spring-boot-starter-parent:

<?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>
 
    <!-- Inherit Spring Boot defaults -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.3</version>
        <relativePath/>
    </parent>
 
    <groupId>com.example</groupId>
    <artifactId>my-app</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>my-app</name>
    <description>My Spring Boot Application</description>
 
    <properties>
        <java.version>21</java.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!-- Không cần version! Inherit từ parent -->
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Điểm chính:

  • Không cần version number cho Spring Boot starter — parent POM quản lý hết
  • spring-boot-starter-parent extend spring-boot-dependencies BOM
  • Hơn 400+ version library được define sẵn và test cùng nhau
  • spring-boot-maven-plugin package app thành executable JAR

Chuỗi Parent POM

Parent POM cung cấp:

  1. Dependency version: Toàn bộ Spring ecosystem + common third-party library
  2. Plugin default: Compiler version, resource filtering, packaging
  3. Profile support: Production-ready default
  4. Property override: Dễ dàng customize version qua <properties>

Dùng BOM Mà Không Cần Parent POM

Nếu project đã có parent POM riêng (ví dụ corporate parent), import Spring Boot BOM thay thế:

<project>
    <!-- Corporate parent của bạn -->
    <parent>
        <groupId>com.company</groupId>
        <artifactId>company-parent</artifactId>
        <version>2.0.0</version>
    </parent>
 
    <dependencyManagement>
        <dependencies>
            <!-- Import Spring Boot BOM -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.4.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!-- Vẫn không cần version -->
        </dependency>
    </dependencies>
</project>

Dependency Scope Trong Maven

Maven scope kiểm soát khi nào dependency available:

ScopeCompileTestRuntimePackaged
compile (default)YesYesYesYes
providedYesYesNoNo
runtimeNoYesYesYes
testNoYesNoNo
systemYesYesNoNo
<dependencies>
    <!-- compile scope (default) — luôn available -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
 
    <!-- runtime — chỉ cần lúc runtime, không cần cho compilation -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
 
    <!-- provided — available lúc compile, container cung cấp lúc runtime -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
 
    <!-- test — chỉ dùng cho testing -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Override Version Được Quản Lý

Cần version khác của managed dependency? Override qua properties:

<properties>
    <java.version>21</java.version>
    <!-- Override managed version -->
    <jackson.version>2.16.1</jackson.version>
    <snakeyaml.version>2.2</snakeyaml.version>
    <postgresql.version>42.7.1</postgresql.version>
</properties>

Để tìm property nào control version dependency, check spring-boot-dependencies POM.

Xem Dependency Tree

Dependency tree là debug tool tốt nhất của bạn:

# Full dependency tree
mvn dependency:tree
 
# Filter cho artifact cụ thể
mvn dependency:tree -Dincludes=com.fasterxml.jackson
 
# Hiện conflict và duplicate
mvn dependency:tree -Dverbose
 
# Output ra file cho project lớn
mvn dependency:tree -DoutputFile=deps.txt

Output ví dụ:

[INFO] com.example:my-app:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:3.4.3:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:3.4.3:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:3.4.3:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:3.4.3:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:3.4.3:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-json:jar:3.4.3:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.16.1:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:3.4.3:compile
[INFO] |  +- org.springframework:spring-webmvc:jar:6.2.3:compile

Exclude Transitive Dependency

Đôi khi starter pull vào library bạn không muốn:

<!-- Đổi từ Tomcat sang Jetty -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!-- Exclude default logging (Logback) để dùng Log4j2 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

Maven Profile Cho Dependency Theo Môi Trường

<profiles>
    <!-- Development profile -->
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <dependencies>
            <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
        </dependencies>
    </profile>
 
    <!-- Production profile -->
    <profile>
        <id>prod</id>
        <dependencies>
            <dependency>
                <groupId>org.postgresql</groupId>
                <artifactId>postgresql</artifactId>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
    </profile>
</profiles>

Activate profile:

# Development (default)
mvn spring-boot:run
 
# Production
mvn spring-boot:run -Pprod
 
# Nhiều profile
mvn spring-boot:run -Pprod,monitoring

Gradle Dependency Management

Setup Project

Spring Boot project dùng Gradle sử dụng Spring Boot Gradle plugin:

// build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.4.3'
    id 'io.spring.dependency-management' version '1.1.7'
}
 
group = 'com.example'
version = '0.0.1-SNAPSHOT'
 
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}
 
repositories {
    mavenCentral()
}
 
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
 
    runtimeOnly 'org.postgresql:postgresql'
 
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
 
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
 
tasks.named('test') {
    useJUnitPlatform()
}

Gradle Kotlin DSL

Project hiện đại thường dùng Kotlin DSL cho type-safe build script:

// build.gradle.kts
plugins {
    java
    id("org.springframework.boot") version "3.4.3"
    id("io.spring.dependency-management") version "1.1.7"
}
 
group = "com.example"
version = "0.0.1-SNAPSHOT"
 
java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(21))
    }
}
 
repositories {
    mavenCentral()
}
 
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
 
    runtimeOnly("org.postgresql:postgresql")
 
    compileOnly("org.projectlombok:lombok")
    annotationProcessor("org.projectlombok:lombok")
 
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
 
tasks.withType<Test> {
    useJUnitPlatform()
}

Dependency Configuration Trong Gradle

Gradle dùng configuration thay vì scope của Maven:

ConfigurationMaven Tương ĐươngKhi Nào Available
implementationcompileCompile + runtime, ẩn khỏi consumer
apicompileCompile + runtime, expose cho consumer
compileOnlyprovidedChỉ lúc compile
runtimeOnlyruntimeChỉ lúc runtime
testImplementationtestTest compile + runtime
annotationProcessorN/AAnnotation processing lúc compile

implementation vs api: Dùng implementation mặc định. Chỉ dùng api khi dependency là phần public API của module. Điều này cải thiện build performance vì thay đổi implementation dependency không trigger recompilation của downstream module.

Override Version Trong Gradle

// Cách 1: Qua ext properties (giống Maven properties)
ext['jackson.version'] = '2.16.1'
ext['snakeyaml.version'] = '2.2'
 
// Cách 2: Dependency constraint
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
 
    // Force version cụ thể
    constraints {
        implementation('com.fasterxml.jackson.core:jackson-databind:2.16.1') {
            because 'CVE fix cần version mới hơn'
        }
    }
}
 
// Cách 3: Resolution strategy
configurations.all {
    resolutionStrategy {
        force 'com.google.guava:guava:33.0.0-jre'
    }
}

Xem Dependency Trong Gradle

# Full dependency tree
./gradlew dependencies
 
# Configuration cụ thể
./gradlew dependencies --configuration runtimeClasspath
 
# Tìm dependency cụ thể
./gradlew dependencyInsight --dependency jackson-databind
 
# HTML report
./gradlew htmlDependencyReport

Exclude Dependency Trong Gradle

dependencies {
    // Exclude từ dependency cụ thể
    implementation('org.springframework.boot:spring-boot-starter-web') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
    implementation 'org.springframework.boot:spring-boot-starter-jetty'
 
    // Global exclusion
    configurations.all {
        exclude group: 'commons-logging', module: 'commons-logging'
    }
}

Dependency Locking Trong Gradle

Lock version dependency cho reproducible build:

// Enable dependency locking
dependencyLocking {
    lockAllConfigurations()
}
# Generate lock file
./gradlew dependencies --write-locks
 
# Update lock
./gradlew dependencies --update-locks org.springframework.boot:*

Lệnh này tạo gradle.lockfile trong project root — commit nó vào version control.


Quản Lý Transitive Dependency

Transitive Dependency Là Gì?

Khi bạn add spring-boot-starter-web, nó không chỉ add chính nó — nó pull vào hàng chục transitive dependency:

Cách Resolve Version Conflict

Khi hai dependency yêu cầu version khác nhau của cùng một library:

Chiến lược của Maven: Nearest definition wins (đường đi ngắn nhất trong dependency tree)

A → B → C:1.0
A → D → E → C:2.0
 
Maven chọn C:1.0 (gần A hơn)

Chiến lược của Gradle: Highest version wins

A → B → C:1.0
A → D → E → C:2.0
 
Gradle chọn C:2.0 (version cao hơn)

Chẩn Đoán Version Conflict

Maven:

# Hiện verbose tree với conflict marker
mvn dependency:tree -Dverbose
 
# Phân tích dependency issue
mvn dependency:analyze
# Hiện: unused declared dependency, used undeclared dependency

Gradle:

# Hiện dependency insight với conflict resolution
./gradlew dependencyInsight --dependency jackson-databind --configuration runtimeClasspath

Output ví dụ cho conflict resolution:

com.fasterxml.jackson.core:jackson-databind:2.16.1
  Variant runtimeElements:
    Selected by rule: Spring Boot BOM
    Requested: 2.15.0 → 2.16.1
 
com.fasterxml.jackson.core:jackson-databind:2.15.0 -> 2.16.1
\--- com.some-library:some-lib:1.0.0
     \--- runtimeClasspath

Flag Optional

Đánh dấu dependency là optional khi chúng không bắt buộc cho consumer của library:

<!-- Maven -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
// Gradle
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

Optional dependency KHÔNG được include transitively. Consumer phải explicitly add chúng nếu cần.


Multi-Module Project

Với application lớn hơn, chia project thành các module:

Maven Multi-Module Structure

my-app/
├── pom.xml                          (parent POM)
├── my-app-common/
│   └── pom.xml                      (shared DTO, utility)
├── my-app-domain/
│   └── pom.xml                      (entity, repository)
├── my-app-service/
│   └── pom.xml                      (business logic)
└── my-app-web/
    └── pom.xml                      (REST controller, main app)

Parent POM (pom.xml):

<?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.4.3</version>
    </parent>
 
    <groupId>com.example</groupId>
    <artifactId>my-app</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
 
    <modules>
        <module>my-app-common</module>
        <module>my-app-domain</module>
        <module>my-app-service</module>
        <module>my-app-web</module>
    </modules>
 
    <properties>
        <java.version>21</java.version>
        <mapstruct.version>1.5.5.Final</mapstruct.version>
    </properties>
 
    <!-- Shared dependency management cho non-Spring library -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct</artifactId>
                <version>${mapstruct.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

Domain module (my-app-domain/pom.xml):

<project>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>my-app</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
 
    <artifactId>my-app-domain</artifactId>
 
    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>my-app-common</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    </dependencies>
</project>

Web module (my-app-web/pom.xml):

<project>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>my-app</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
 
    <artifactId>my-app-web</artifactId>
 
    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>my-app-service</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Gradle Multi-Module Structure

settings.gradle.kts:

rootProject.name = "my-app"
 
include("my-app-common")
include("my-app-domain")
include("my-app-service")
include("my-app-web")

Root build.gradle.kts:

plugins {
    java
    id("org.springframework.boot") version "3.4.3" apply false
    id("io.spring.dependency-management") version "1.1.7" apply false
}
 
subprojects {
    apply(plugin = "java")
    apply(plugin = "io.spring.dependency-management")
 
    group = "com.example"
    version = "0.0.1-SNAPSHOT"
 
    java {
        toolchain {
            languageVersion.set(JavaLanguageVersion.of(21))
        }
    }
 
    repositories {
        mavenCentral()
    }
 
    // Apply BOM cho tất cả subproject
    the<io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension>().apply {
        imports {
            mavenBom("org.springframework.boot:spring-boot-dependencies:3.4.3")
        }
    }
 
    dependencies {
        testImplementation("org.springframework.boot:spring-boot-starter-test")
    }
}

Web module build.gradle.kts:

plugins {
    id("org.springframework.boot")
}
 
dependencies {
    implementation(project(":my-app-service"))
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
}

Dependency Security

Tại Sao Quan Trọng

Third-party dependency có thể đưa vào security vulnerability. Một transitive dependency bị lỗ hổng duy nhất có thể expose toàn bộ application. Scan thường xuyên là điều cần thiết.

OWASP Dependency-Check

Maven:

<build>
    <plugins>
        <plugin>
            <groupId>org.owasp</groupId>
            <artifactId>dependency-check-maven</artifactId>
            <version>9.0.9</version>
            <configuration>
                <!-- Fail build khi CVSS >= 7 -->
                <failBuildOnCVSS>7</failBuildOnCVSS>
            </configuration>
        </plugin>
    </plugins>
</build>
mvn dependency-check:check

Gradle:

plugins {
    id 'org.owasp.dependencycheck' version '9.0.9'
}
 
dependencyCheck {
    failBuildOnCVSS = 7.0f
    analyzers {
        assemblyEnabled = false
    }
}
./gradlew dependencyCheckAnalyze

Tự Động Update Dependency

Dependabot (tích hợp sẵn trong GitHub):

Tạo .github/dependabot.yml:

version: 2
updates:
  - package-ecosystem: "maven"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    labels:
      - "dependencies"
    groups:
      spring-boot:
        patterns:
          - "org.springframework.boot*"
          - "org.springframework*"
      jackson:
        patterns:
          - "com.fasterxml.jackson*"

Renovate (thay thế Dependabot với nhiều feature hơn):

Tạo renovate.json:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended",
    ":automergeMinor",
    "group:springBoot"
  ],
  "packageRules": [
    {
      "matchPackagePatterns": ["org.springframework"],
      "groupName": "Spring Framework",
      "automerge": false
    }
  ]
}

Security Tích Hợp Sẵn Của Spring Boot

Spring Boot đã giảm thiểu nhiều dependency risk:

  • BOM-managed version: Tất cả dependency được test cùng nhau cho compatibility
  • CVE patching: Security patch được đưa vào nhanh chóng trong Spring Boot release
  • Dependency convergence: BOM đảm bảo version nhất quán trên toàn bộ dependency tree

Các Pattern và Recipe Phổ Biến

Recipe 1: Thêm Custom BOM

Khi dùng thêm library ecosystem (ví dụ AWS SDK, Google Cloud):

<!-- Maven -->
<dependencyManagement>
    <dependencies>
        <!-- AWS SDK BOM -->
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.24.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
 
        <!-- Google Cloud BOM -->
        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>libraries-bom</artifactId>
            <version>26.31.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
 
<dependencies>
    <!-- Không cần version — managed bởi AWS BOM -->
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3</artifactId>
    </dependency>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>dynamodb</artifactId>
    </dependency>
</dependencies>
// Gradle
dependencies {
    implementation(platform("software.amazon.awssdk:bom:2.24.0"))
    implementation("software.amazon.awssdk:s3")
    implementation("software.amazon.awssdk:dynamodb")
}

Recipe 2: Thay Embedded Server

<!-- Đổi từ Tomcat sang Undertow -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
</dependencies>

Recipe 3: Dependency Chỉ Cho Test

<!-- Maven -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>postgresql</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-testcontainers</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
// Gradle
dependencies {
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.testcontainers:postgresql")
    testImplementation("org.springframework.boot:spring-boot-testcontainers")
    testImplementation("io.rest-assured:rest-assured")
}

Recipe 4: Annotation Processor

<!-- Maven — Lombok + MapStruct cùng nhau -->
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.5.5.Final</version>
    </dependency>
</dependencies>
 
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                    </path>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.5.5.Final</version>
                    </path>
                    <!-- Lombok-MapStruct binding -->
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok-mapstruct-binding</artifactId>
                        <version>0.2.0</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
// Gradle — đơn giản hơn nhiều
dependencies {
    compileOnly("org.projectlombok:lombok")
    annotationProcessor("org.projectlombok:lombok")
 
    implementation("org.mapstruct:mapstruct:1.5.5.Final")
    annotationProcessor("org.mapstruct:mapstruct-processor:1.5.5.Final")
    annotationProcessor("org.projectlombok:lombok-mapstruct-binding:0.2.0")
}

Best Practices

1. Để BOM Quản Lý Version

<!-- ✅ Đúng — version managed bởi Spring Boot BOM -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
 
<!-- ❌ Sai — hardcode version có thể gây conflict -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.0</version>
</dependency>

Chỉ override version khi bạn có lý do cụ thể (security fix, bug fix, feature mới).

2. Dùng Đúng Scope

<!-- ✅ Đúng — PostgreSQL driver chỉ cần lúc runtime -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>
 
<!-- ❌ Sai — default compile scope expose driver API không cần thiết -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

3. Giữ Dependency Tối Thiểu

<!-- ✅ Đúng — chỉ những gì bạn cần -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
 
<!-- ❌ Sai — pull vào mọi thứ "phòng khi cần" -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

4. Audit Thường Xuyên

# Maven: tìm unused và undeclared dependency
mvn dependency:analyze
 
# Output:
# [WARNING] Unused declared dependencies:
#   org.springframework.boot:spring-boot-starter-mail:jar:3.4.3:compile
# [WARNING] Used undeclared dependencies:
#   org.springframework:spring-context:jar:6.2.3:compile
  • Unused declared: Bạn declare nhưng không dùng — xóa đi
  • Used undeclared: Bạn dùng nhưng chỉ có được qua transitive — declare rõ ràng

5. Pin Version Cho Third-Party Library

Với library KHÔNG được Spring Boot BOM quản lý:

<!-- ✅ Đúng — version rõ ràng trong properties -->
<properties>
    <springdoc.version>2.3.0</springdoc.version>
    <mapstruct.version>1.5.5.Final</mapstruct.version>
</properties>
 
<dependencies>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>${springdoc.version}</version>
    </dependency>
</dependencies>

6. Upgrade Spring Boot Có Hệ Thống

# 1. Check version hiện tại
mvn help:evaluate -Dexpression=project.parent.version -q -DforceStdout
 
# 2. Update parent version trong pom.xml (ví dụ 3.3.x → 3.4.x)
 
# 3. Check deprecated API
mvn compile -Xlint:deprecation
 
# 4. Chạy full test suite
mvn test
 
# 5. Check dependency tree cho conflict
mvn dependency:tree -Dverbose
 
# 6. Review release note cho breaking change

7. Dùng Maven Wrapper hoặc Gradle Wrapper

Luôn commit wrapper để đảm bảo build tool version nhất quán trong team:

# Maven — generate wrapper
mvn wrapper:wrapper -Dmaven=3.9.6
 
# Dùng wrapper thay vì global Maven
./mvnw clean install
./mvnw spring-boot:run
# Gradle — generate wrapper
gradle wrapper --gradle-version 8.5
 
# Dùng wrapper thay vì global Gradle
./gradlew clean build
./gradlew bootRun

Xử Lý Sự Cố Thường Gặp

"ClassNotFoundException" hoặc "NoClassDefFoundError"

Nguyên nhân: Thiếu dependency hoặc sai scope.

# Check xem class có tồn tại trong dependency tree không
mvn dependency:tree -Dincludes=*:*:*:*:*  # full tree
# Tìm package/class trong dependency

Cách sửa: Add dependency rõ ràng hoặc đổi scope từ test/provided sang compile.

"Multiple Bindings" Trong SLF4J

Nguyên nhân: Nhiều logging implementation trên classpath.

mvn dependency:tree -Dincludes=org.slf4j
mvn dependency:tree -Dincludes=ch.qos.logback
mvn dependency:tree -Dincludes=org.apache.logging.log4j

Cách sửa: Exclude logging framework không muốn:

<dependency>
    <groupId>some-library</groupId>
    <artifactId>some-artifact</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Lỗi "Bean Definition Override"

Nguyên nhân: Hai starter cung cấp cùng auto-configuration bean.

Cách sửa: Check starter nào conflict và exclude một cái, hoặc set:

# application.properties
spring.main.allow-bean-definition-overriding=true

Dependency Convergence Failure

Nguyên nhân: Version khác nhau của cùng dependency trong tree.

Maven — enforcer plugin:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-enforcer-plugin</artifactId>
            <executions>
                <execution>
                    <id>enforce-dependency-convergence</id>
                    <goals>
                        <goal>enforce</goal>
                    </goals>
                    <configuration>
                        <rules>
                            <dependencyConvergence/>
                            <requireJavaVersion>
                                <version>[21,)</version>
                            </requireJavaVersion>
                        </rules>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Tham Khảo Nhanh: Template pom.xml Hoàn Chỉnh

Đây là template pom.xml production-ready với các Spring Boot dependency phổ biến:

<?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.4.3</version>
        <relativePath/>
    </parent>
 
    <groupId>com.example</groupId>
    <artifactId>my-app</artifactId>
    <version>0.0.1-SNAPSHOT</version>
 
    <properties>
        <java.version>21</java.version>
        <springdoc.version>2.3.0</springdoc.version>
        <mapstruct.version>1.5.5.Final</mapstruct.version>
    </properties>
 
    <dependencies>
        <!-- Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
 
        <!-- Data -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-database-postgresql</artifactId>
        </dependency>
 
        <!-- Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
 
        <!-- Monitoring -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
 
        <!-- API Documentation -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
 
        <!-- Utility -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${mapstruct.version}</version>
        </dependency>
 
        <!-- Development -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
 
        <!-- Testing -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
 
    <build>
        <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>
                        </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>
    </build>
</project>

Tổng Kết

Dependency management của Spring Boot đơn giản hóa một trong những phần khó nhất của Java development — giữ cho library tương thích và cập nhật.

Những điểm chính:

✅ Dùng Spring Boot Starter để có dependency bundle được cấu hình sẵn
✅ Để BOM quản lý version — đừng hardcode
✅ Dùng mvn dependency:tree hoặc ./gradlew dependencies để debug conflict
✅ Exclude transitive dependency khi đổi implementation (ví dụ Tomcat → Jetty)
✅ Set đúng scope (runtime, test, provided) để giữ classpath sạch
✅ Scan vulnerability với OWASP Dependency-Check
✅ Tự động hóa update với Dependabot hoặc Renovate
✅ Luôn dùng Maven/Gradle wrapper cho reproducible build

Bài viết liên quan:

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.