LearnAI ToolsCareerPractice BuildsPlayContact
Dependencies in SpringIntermediate~2 hours

Dependency Audit

Review a sample pom.xml, identify redundant or missing dependencies, and justify each change.

Dependency ManagementBOMVersion Conflicts

Overview

Every other lesson in this course starts from a clean starter and adds it correctly. Real `pom.xml` files are rarely clean — they accumulate an explicit version pinned during some old bug fix, a second library added "just in case" that duplicates one already there, and a dependency the code quietly started needing that nobody added. This course is a categorized reference to what each dependency does; this project is about the other half of the skill, reading somebody else's dependency list and telling which entries are actually justified.

You will audit a small `reviewservice` project's `pom.xml` against the code that actually depends on it, find three real, common problems — an explicit version fighting the Spring Boot BOM, a redundant duplicate-purpose dependency, and a dependency the code needs but the `pom.xml` never declares — and produce a cleaned-up `pom.xml` with a comment justifying every dependency that survives. Along the way you will use Maven's own tooling, `dependency:tree` and `dependency:analyze`, to prove each diagnosis instead of guessing at it.

What You'll Build
  • A messy sample `pom.xml` for a small `reviewservice` project, read alongside the controller code it supports.
  • A working understanding of Maven's "nearest wins" dependency mediation and what `spring-boot-starter-parent`'s BOM actually manages.
  • A diagnosis of each problem using `mvn dependency:tree`, `mvn dependency:analyze`, and a plain `mvn compile` failure.
  • Three targeted fixes: un-pin a version, delete a redundant dependency, and add a missing one.
  • A final, cleaned-up `pom.xml` with a justification comment on every dependency it keeps.

Prerequisites

  • This course's Introduction to Spring Dependency Management and Maven vs Gradle for Spring Projects lessons.
  • This course's Understanding Spring Boot Starters lesson, and a general sense of what a BOM (Bill of Materials) is.
  • Comfort reading a `pom.xml` and running `mvn` commands from the command line.
  • Basic Jakarta Bean Validation (`@Valid`, `@NotBlank`) — this course's Validation Dependencies lesson if you have not seen it.
  • No new dependency knowledge required beyond what this course's lessons already cover — this project is about applying it, not learning a new starter.

Project Structure

The project under audit is small on purpose: `src/main/java/com/programinds/reviewservice/` holds `ReviewRequest` (a DTO) and `ReviewController` (a REST controller that accepts it), and the `pom.xml` at the project root is the actual subject of this tutorial. Nothing about `ReviewRequest` or `ReviewController` changes across this project — every step below only ever touches `pom.xml`.

The three problems are deliberately spread across the two ways a dependency problem actually surfaces: one shows up only if you go looking for it with `mvn dependency:tree` (the version conflict), one shows up with `mvn dependency:analyze` (the unused dependency), and one shows up the moment anybody tries to build the project at all (the missing one, as a compile failure). That spread matters — a real audit rarely finds every problem with a single command.

Step 1: Read the Messy Sample pom.xml

Before touching anything, read the `pom.xml` below alongside the DTO it is meant to support. `ReviewRequest` uses `@NotBlank` and `@Min`/`@Max` — Jakarta Bean Validation annotations — and `ReviewController` uses `@Valid` to enforce them. Keep that in mind while reading the dependency list: nothing in it is named `validation` at all.

pom.xml (before audit)
<?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.2.5</version>
<relativePath/>
</parent>
<groupId>com.programinds</groupId>
<artifactId>reviewservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
</properties>
<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>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Added months ago to work around a since-fixed Jackson bug. Nobody has
revisited it since — is it still needed? See Step 2 and Step 3. -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
<!-- Added "just in case" a teammate wanted a second JSON option. -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
dto/ReviewRequest.java
package com.programinds.reviewservice.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
// Every annotation here needs a Jakarta Bean Validation provider on the classpath —
// check the pom.xml above for what actually supplies one. (It doesn't, yet.)
public class ReviewRequest {
@NotBlank(message = "Comment must not be blank")
private String comment;
@Min(value = 1, message = "Rating must be at least 1")
@Max(value = 5, message = "Rating must be at most 5")
private int rating;
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public int getRating() {
return rating;
}
public void setRating(int rating) {
this.rating = rating;
}
}
controller/ReviewController.java
package com.programinds.reviewservice.controller;
import com.programinds.reviewservice.dto.ReviewRequest;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/reviews")
public class ReviewController {
@PostMapping
public ResponseEntity<String> create(@Valid @RequestBody ReviewRequest request) {
// Nothing here imports com.google.gson at all — every JSON conversion in this
// project, request and response, goes through Jackson via spring-boot-starter-web.
return ResponseEntity.status(HttpStatus.CREATED).body("Review recorded");
}
}

Step 2: Understand Maven Mediation and the Spring Boot BOM

`spring-boot-starter-parent`'s `<dependencyManagement>` section is a BOM — a giant, pre-tested list of `artifactId` → version pairs for a given Spring Boot release. Any dependency declared without its own `<version>` tag, like `spring-boot-starter-web` above, inherits its version from that list automatically, which is why almost nothing in a typical Spring Boot `pom.xml` specifies a version at all.

Maven resolves conflicting versions of the same artifact with "nearest wins": if `jackson-databind` shows up at two different depths in the dependency graph, Maven keeps whichever declaration is closer to your own `pom.xml`, regardless of which one is newer or better tested. A version you write directly in your `<dependencies>` block is distance zero — as close as a declaration can be — so it beats the BOM-managed version every time, even though the BOM version is the one Spring Boot's own team tested against every other starter in the project. Writing that explicit `<version>` is not "pinning" the dependency to something more stable; it is un-pinning it from the tested set everything else in the project was built against.

The Same Idea in Gradle

Maven imports the BOM through the `spring-boot-starter-parent` parent POM. A Gradle project gets the same tested version set from the `io.spring.dependency-management` plugin, or by declaring `implementation platform('org.springframework.boot:spring-boot-dependencies:3.2.5')` in the `dependencies {}` block. The mediation rule differs too: Gradle's default conflict resolution picks the highest version found anywhere in the graph, not the nearest one — worth remembering if you move a project, or your own habits, between the two build tools this course covers.

Step 3: Diagnose Each Problem

Start with the version conflict. `mvn dependency:tree` filtered to `jackson-databind` shows only one entry — the pinned `2.13.0` — because Maven already resolved the conflict in its favor and dropped the BOM-managed candidate from the visible tree. Adding `-Dverbose` brings the losing candidate back into view, along with the reason it lost.

Diagnosing the Version Conflict

Click Run to see what this code prints.

Next, the redundant dependency. `mvn dependency:analyze` inspects the compiled bytecode for what is actually imported and compares it against what is declared, flagging any declared dependency the code never references.

Diagnosing the Redundant Dependency

Click Run to see what this code prints.

Finally, the missing dependency does not need a special command at all — a plain build fails outright, because `jakarta.validation.constraints` is not on the classpath. `spring-boot-starter-web` alone does not pull in a Bean Validation provider; only `spring-boot-starter-validation` does.

Diagnosing the Missing Dependency

Click Run to see what this code prints.

Step 4: Apply the Fixes

Each fix targets exactly the problem Step 3 proved, and nothing else. Delete the `<version>` tag on `jackson-databind` — do not delete the dependency itself, since Spring Boot's own auto-configuration expects Jackson on the classpath, just at the version the BOM already manages. Delete the `gson` dependency block entirely, since `mvn dependency:analyze` confirmed nothing imports it. Add `spring-boot-starter-validation`, with no explicit version, letting the BOM manage that one too.

Fix 1: un-pin jackson-databind
<!-- Before: an explicit version fighting the BOM -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
<!-- After: no <version> at all. spring-boot-starter-web already brings jackson-databind
in transitively at the BOM-managed 2.15.2 — this project never needed to declare
it directly in the first place, let alone pin it to an older version. -->
<!-- (dependency removed entirely) -->
Fix 2: delete the redundant gson dependency
<!-- Before: a second JSON library the code never imports -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- After: deleted. Jackson, from spring-boot-starter-web, is the only JSON library
this project's code actually touches — confirmed by mvn dependency:analyze in Step 3. -->
<!-- (dependency removed entirely) -->
Fix 3: add the missing validation starter
<!-- Added: ReviewRequest's @NotBlank/@Min/@Max and ReviewController's @Valid need
a Jakarta Bean Validation provider on the classpath — this starter supplies it. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Step 5: Verify With the Dependency Tree

Re-running the exact commands from Step 3 against the fixed `pom.xml` confirms each fix independently: the build compiles, `jackson-databind` resolves to the single BOM-managed version with nothing left for mediation to arbitrate, and `dependency:analyze` no longer has a redundant dependency to flag.

Verification

Click Run to see what this code prints.

Complete Code

Here is the audited, cleaned-up `pom.xml`, with a justification comment kept on every dependency — not just the ones that changed. `ReviewRequest` and `ReviewController` are unchanged from Step 1; the entire audit lived in the dependency list.

pom.xml (after audit)
<?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.2.5</version>
<relativePath/>
</parent>
<groupId>com.programinds</groupId>
<artifactId>reviewservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring MVC + embedded Tomcat + Jackson (transitively, at the BOM-managed
version — see the jackson-databind note below). -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA. Kept as-is: ReviewController's persistence layer (omitted
here for brevity) depends on it directly. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Runtime-only JDBC driver for the in-memory database this project develops
against. runtime scope: nothing in src/main/java imports an H2 class directly. -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- FIX: jackson-databind is no longer declared directly at all. It still arrives
transitively through spring-boot-starter-web, at whatever version Spring Boot
3.2.5's BOM manages (2.15.2) — the version this project is actually tested
against, not a stale pin left over from a long-fixed bug. -->
<!-- FIX: the gson dependency has been deleted entirely. mvn dependency:analyze
confirmed nothing in this project imports com.google.gson; every JSON
conversion here already goes through Jackson via spring-boot-starter-web. -->
<!-- FIX: added. ReviewRequest's @NotBlank/@Min/@Max and ReviewController's @Valid
need a Jakarta Bean Validation provider on the classpath — without this starter
the project does not even compile (see Step 3's mvn compile failure). -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

Sample Run

Sample Run

Click Run to see what this code prints.

Extend This Project

  • Run `mvn versions:display-dependency-updates` to see which BOM-managed versions have newer upstream releases, without touching `pom.xml` at all.
  • Translate the cleaned `pom.xml` to `build.gradle`, using `implementation platform('org.springframework.boot:spring-boot-dependencies:3.2.5')` in place of the Maven parent's BOM import, per this course's Maven vs Gradle lesson.
  • Deliberately reintroduce a pinned version on a different transitive dependency and use `mvn dependency:tree -Dverbose` to watch nearest-wins mediation pick it over the BOM again.
  • Notice what `spring-boot-starter-test` itself pulls in — JUnit 5, Mockito, AssertJ — before adding a standalone mocking library that might already be on the classpath.
  • Wire `mvn dependency:analyze` into CI so a redundant or missing dependency like the ones audited here fails the build automatically, instead of waiting for a human review to catch it.

Summary

You treated a `pom.xml` the way you would treat any other code under review: cross-referenced its claims against what the project actually does, proved each suspicion with a specific Maven command rather than a guess, and fixed exactly what the evidence supported. The underlying idea — that `spring-boot-starter-parent`'s BOM manages a tested version set, and an explicit `<version>` tag silently opts a dependency out of it — is the single fact behind most real-world "why did upgrading one starter break something unrelated" incidents, which is exactly why this course dedicates a foundational lesson to dependency management before any category-specific one.