Maven Dependency Magic: Why Adding 2 Dependencies Downloads 50+ JARs (And Where They Go)
Have you ever added a single dependency to your pom.xml and wondered why Maven seems to download dozens of other libraries? Or why sometimes Maven doesn't download anything at all? This article demystifies Maven's dependency management, transitive dependencies, and the local cache system.
The Puzzleβ
You just added two simple dependencies to your Spring Boot project:
<!-- pom.xml -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
You run mvn clean install and... nothing downloads? But when you check the dependency tree, you see 50+ JAR files are available!
What's happening? π€
The Mystery: You Added 2, But Got 50+β
Let's visualize what actually happened when you added those dependencies:
What YOU Added to pom.xmlβ
<dependencies>
<!-- Original dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- NEW: You added these two -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
Your action: Added 2 dependencies (5 total)
Maven's action: Made ~50+ JAR files available to your project
How? Through the magic of transitive dependencies! π
Understanding Maven Dependency Typesβ
Direct Dependencies (Explicit)β
These are dependencies YOU explicitly add to pom.xml:
Your pom.xml (5 dependencies):
βββ spring-boot-starter-web β YOU added this
βββ spring-boot-devtools β YOU added this
βββ spring-boot-starter-test β YOU added this
βββ h2 β YOU added this
βββ spring-boot-starter-data-jpa β YOU added this
Transitive Dependencies (Implicit)β
These are dependencies that YOUR dependencies need:
What Maven downloads automatically:
βββ h2 brings:
β βββ h2-2.2.224.jar (just 1 JAR)
β
βββ spring-boot-starter-data-jpa brings:
βββ hibernate-core-6.3.1.jar β JPA engine
βββ hibernate-commons-annotations β Hibernate utilities
βββ spring-data-jpa-3.2.0.jar β Repository magic
βββ jakarta.persistence-api.jar β JPA standard
βββ jakarta.transaction-api.jar β Transaction management
βββ spring-aop.jar β Aspect programming
βββ aspectjweaver.jar β AOP weaving
βββ byte-buddy.jar β Code generation
βββ jandex.jar β Annotation indexing
βββ jaxb-runtime.jar β XML binding
βββ ... (10+ more libraries!)
The Complete Dependency Treeβ
Let's examine exactly what happens when you add JPA:
mvn dependency:tree
π¦ H2 Database (Simple)β
[INFO] +- com.h2database:h2:jar:2.2.224:runtime
Analysis:
- β Just ONE jar file
- β No transitive dependencies
- β Self-contained database engine
π¦ JPA Starter (Complex Gift Box)β
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:3.2.0
[INFO] | +- org.springframework.boot:spring-boot-starter-aop:jar:3.2.0
[INFO] | | +- org.aspectj:aspectjweaver:jar:1.9.20.1
[INFO] | +- jakarta.transaction:jakarta.transaction-api:jar:2.0.1
[INFO] | +- jakarta.persistence:jakarta.persistence-api:jar:3.1.0
[INFO] | +- org.hibernate.orm:hibernate-core:jar:6.3.1.Final
[INFO] | | +- org.jboss.logging:jboss-logging:jar:3.5.3.Final
[INFO] | | +- org.hibernate.common:hibernate-commons-annotations:jar:6.0.6.Final
[INFO] | | +- io.smallrye:jandex:jar:3.1.2
[INFO] | | +- net.bytebuddy:byte-buddy:jar:1.14.9
[INFO] | | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.1
[INFO] | | +- jakarta.inject:jakarta.inject-api:jar:2.0.1
[INFO] | +- org.springframework.data:spring-data-jpa:jar:3.2.0
[INFO] | | +- org.springframework.data:spring-data-commons:jar:3.2.0
[INFO] | | +- org.springframework:spring-orm:jar:6.1.1
[INFO] | | +- org.springframework:spring-tx:jar:6.1.1
[INFO] | +- org.springframework:spring-aspects:jar:6.1.1
Analysis:
- π ONE starter = 15+ libraries
- π Hierarchical dependency chain
- β‘ All coordinated versions (no conflicts!)
Visual Breakdown: The Gift Box Analogyβ
π¦ spring-boot-starter-data-jpa (The Gift Box)
β
βββ π Hibernate Package (Database ORM)
β βββ hibernate-core ................. The main JPA implementation
β βββ hibernate-commons-annotations ... Helper annotations
β βββ jandex ......................... Annotation indexer
β βββ byte-buddy ..................... Runtime code generation
β βββ jboss-logging .................. Logging framework
β
βββ π Spring Data Package (Repository Layer)
β βββ spring-data-jpa ................ Repository abstraction
β βββ spring-data-commons ............ Common data utilities
β βββ spring-orm ..................... ORM support
β βββ spring-tx ...................... Transaction management
β
βββ π JPA Standard Package (Interfaces)
β βββ jakarta.persistence-api ........ JPA specification
β βββ jakarta.transaction-api ........ Transaction API
β βββ jakarta.inject-api ............. Dependency injection
β
βββ π AOP Package (Cross-cutting Concerns)
βββ spring-aop ..................... Spring AOP support
βββ spring-aspects ................. AspectJ integration
βββ aspectjweaver .................. Aspect weaving
Each library has a purpose:
| Library | Purpose | Why Needed |
|---|---|---|
hibernate-core | JPA implementation | Actual database ORM engine |
spring-data-jpa | Repository abstraction | Magical repository interfaces |
jakarta.persistence-api | JPA standard | Interface definitions |
spring-aop | Aspect-oriented programming | Transaction proxies |
byte-buddy | Code generation | Create dynamic proxies at runtime |
aspectjweaver | Aspect weaving | Enable @Transactional magic |
The Maven Caching Mysteryβ
Why No Download Messages?β
You added dependencies and ran mvn clean install, but saw no download activity. Let's investigate:
# Check if H2 is cached locally
ls -lh ~/.m2/repository/com/h2database/h2/2.2.224/
Output:
total 5144
-rw-r--r-- 1 user staff 188B May 26 17:10 _remote.repositories
-rw-r--r-- 1 user staff 2.5M Sep 18 2023 h2-2.2.224.jar
-rw-r--r-- 1 user staff 6.8K Sep 18 2023 h2-2.2.224.pom
-rw-r--r-- 1 user staff 40B Sep 18 2023 h2-2.2.224.pom.sha1
π― AHA! Found it!
h2-2.2.224.jar 2.5M Downloaded: Sep 18, 2023
Last used: May 26, 17:10
What this tells us:
- β H2 was downloaded September 18, 2023 (probably for another project)
- β Last checked: May 26 (Maven verified it's still valid)
- β
When you ran
mvn clean installtoday, Maven said: "I already have this!"
Maven's Smart Caching Workflowβ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β You: Add dependency to pom.xml β
βββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Maven: Parse pom.xml and read dependencies β
βββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Maven: Check local cache (~/.m2/repository/) β
βββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββ΄βββββββββ
β β
ββββββββββββ ββββββββββββ
β FOUND β βNOT FOUND β
ββββββ¬ββββββ ββββββ¬ββββββ
β β
βββββββββββββββ ββββββββββββββββ
β Use cached β βDownload from β
β JAR file β βMaven Central β
ββββββββ¬βββββββ ββββββββ¬ββββββββ
β β
ββββββββββ¬βββββββββ
β
ββββββββββββββββββββββ
β Add to classpath β
ββββββββββββββββββββββ
The Local Maven Repository Structureβ
~/.m2/repository/
βββ com/
β βββ h2database/
β β βββ h2/
β β βββ 2.2.224/
β β βββ h2-2.2.224.jar β Actual library
β β βββ h2-2.2.224.pom β Dependency metadata
β β βββ h2-2.2.224.pom.sha1 β Checksum
β β βββ _remote.repositories β Download history
β β
β βββ fasterxml/
β βββ jackson/
β βββ core/
β βββ jackson-databind/
β βββ 2.15.3/
β βββ jackson-databind-2.15.3.jar
β
βββ org/
β βββ springframework/
β β βββ boot/
β β β βββ spring-boot-starter-data-jpa/
β β β βββ 3.2.0/
β β β βββ spring-boot-starter-data-jpa-3.2.0.jar
β β β βββ spring-boot-starter-data-jpa-3.2.0.pom
β β β
β β βββ data/
β β βββ spring-data-jpa/
β β βββ 3.2.0/
β β βββ spring-data-jpa-3.2.0.jar
β β
β βββ hibernate/
β βββ orm/
β βββ hibernate-core/
β βββ 6.3.1.Final/
β βββ hibernate-core-6.3.1.Final.jar
β
βββ jakarta/
βββ persistence/
βββ jakarta.persistence-api/
βββ 3.1.0/
βββ jakarta.persistence-api-3.1.0.jar
Structure Rules:
- Path format:
groupId/artifactId/version/ - Example:
com.h2database:h2:2.2.224βcom/h2database/h2/2.2.224/ - Each version has:
.jar,.pom,.sha1,_remote.repositories
Deep Dive: What Each File Doesβ
The JAR File (h2-2.2.224.jar)β
# View contents of a JAR
jar tf ~/.m2/repository/com/h2database/h2/2.2.224/h2-2.2.224.jar | head -10
META-INF/
META-INF/MANIFEST.MF
org/h2/
org/h2/Driver.class
org/h2/engine/
org/h2/engine/Database.class
org/h2/store/
org/h2/store/FileStore.class
...
Purpose: The actual compiled code (bytecode) of the library
The POM File (h2-2.2.224.pom)β
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<dependencies>
<!-- H2 has no dependencies! Self-contained. -->
</dependencies>
</project>
Purpose: Metadata about the library and its dependencies
The Checksum File (h2-2.2.224.jar.sha1)β
a2c7a2b7cc1f2a5e3c5f3c5d3e5f3c5d3e5f3c5a
Purpose: Verify file integrity (prevents corrupted downloads)
The Remote Repositories File (_remote.repositories)β
h2-2.2.224.jar>central=
h2-2.2.224.pom>central=
Purpose: Track which Maven repository the files came from
Real-World Examplesβ
Example 1: Brand New Dependencyβ
# Add a new dependency you've never used
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
# Run Maven
mvn clean install
Output:
[INFO] Downloading from central: https://repo.maven.apache.org/.../guava-32.1.3-jre.jar
[INFO] Downloaded from central: https://repo.maven.apache.org/.../guava-32.1.3-jre.jar (2.7 MB at 1.5 MB/s)
Result: Downloaded and cached for future use
Example 2: Previously Used Dependencyβ
# Same dependency, different project
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
# Run Maven
mvn clean install
Output:
[INFO] --- maven-dependency-plugin:3.6.1:tree ---
[INFO] +- com.google.guava:guava:jar:32.1.3-jre:compile
Result: Used cached version, no download! β‘
Example 3: Checking Cache Sizeβ
# See how much disk space Maven cache uses
du -sh ~/.m2/repository/
Typical output:
2.5G /Users/username/.m2/repository/
What this means:
- ποΈ Gigabytes of libraries cached locally
- β‘ Instant access across all projects
- πΎ Trade-off: Disk space for speed
Understanding Spring Boot Startersβ
What is a "Starter"?β
A starter is a curated set of dependencies bundled together for a specific purpose.
spring-boot-starter-data-jpa = "Everything you need for JPA"
Starter Anatomyβ
<!-- What you write -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- What Spring Boot's POM contains -->
<dependencies>
<!-- Core JPA -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<!-- JPA API -->
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
<!-- Transaction Management -->
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
</dependency>
<!-- AOP Support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<!-- And more... -->
</dependencies>
Common Spring Boot Startersβ
| Starter | Purpose | Brings In |
|---|---|---|
spring-boot-starter-web | Web applications | Tomcat, Spring MVC, Jackson |
spring-boot-starter-data-jpa | Database ORM | Hibernate, Spring Data JPA |
spring-boot-starter-security | Authentication | Spring Security |
spring-boot-starter-test | Testing | JUnit, Mockito, AssertJ |
spring-boot-starter-validation | Input validation | Hibernate Validator |
Managing Maven Cacheβ
Viewing Cache Contentsβ
# List all cached artifacts
ls -R ~/.m2/repository/
# Count total JAR files
find ~/.m2/repository -name "*.jar" | wc -l
# Find largest JARs
find ~/.m2/repository -name "*.jar" -exec ls -lh {} \; | sort -k5 -hr | head -10
Clearing the Cacheβ
# Nuclear option: Delete entire cache
rm -rf ~/.m2/repository/
# Safer: Delete specific artifact
rm -rf ~/.m2/repository/com/h2database/h2/2.2.224/
# Maven will re-download on next build
mvn clean install
Cache Verificationβ
# Force Maven to re-download everything
mvn clean install -U
# -U flag = "update snapshots and check for newer releases"
Dependency Version Managementβ
Explicit Versionβ
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version> β Explicit version
</dependency>
Inherited Version (Spring Boot)β
<!-- No version specified! -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!-- Version comes from spring-boot-starter-parent -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
Benefit: Spring Boot manages compatible versions automatically!
Troubleshooting Common Issuesβ
Issue 1: Dependency Not Foundβ
[ERROR] Failed to execute goal on project myapp:
Could not resolve dependencies for project com.example:myapp:jar:1.0.0:
Could not find artifact com.h2database:h2:jar:2.2.224
Solutions:
# 1. Check internet connection
ping repo.maven.apache.org
# 2. Clear cache and retry
rm -rf ~/.m2/repository/com/h2database/
mvn clean install
# 3. Check Maven settings
cat ~/.m2/settings.xml
Issue 2: Version Conflictsβ
[WARNING] The POM for org.hibernate:hibernate-core:jar:6.3.1.Final is missing
Solution: Use dependency management
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.3.1.Final</version>
</dependency>
</dependencies>
</dependencyManagement>
Issue 3: Slow Downloadsβ
# Use Maven mirror (e.g., Aliyun in China)
# Add to ~/.m2/settings.xml
<mirrors>
<mirror>
<id>aliyun</id>
<mirrorOf>central</mirrorOf>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
</mirrors>
Best Practicesβ
β Do Thisβ
1. Let Spring Boot manage versions
<!-- Good: No version needed -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
2. Use dependency:tree to understand transitive dependencies
mvn dependency:tree > dependencies.txt
3. Exclude unwanted transitive 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>
4. Periodically clean old versions
# Remove all but latest versions
mvn dependency:purge-local-repository
β Avoid Thisβ
1. Hardcoding versions when using Spring Boot
<!-- Bad: Might conflict with Spring Boot's managed version -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.0</version> β Avoid this!
</dependency>
2. Ignoring dependency conflicts
[WARNING] Some problems were encountered while building the effective model
3. Committing .m2/repository to Git
# This is ALWAYS in .gitignore
~/.m2/repository/
Verification Commands Cheat Sheetβ
# View dependency tree
mvn dependency:tree
# Show only direct dependencies
mvn dependency:tree -Dverbose=false
# Find specific dependency
mvn dependency:tree | grep h2
# Analyze dependency conflicts
mvn dependency:analyze
# List all dependencies (including transitive)
mvn dependency:list
# Copy all dependencies to target/dependency/
mvn dependency:copy-dependencies
# Show classpath
mvn dependency:build-classpath
# Verify all dependencies are in cache
mvn dependency:resolve
# Check for dependency updates
mvn versions:display-dependency-updates
Summaryβ
Key Takeawaysβ
- π Starters are gift boxes - One starter brings many libraries
- ποΈ Maven caches everything -
~/.m2/repository/stores all downloads - β‘ Cache = Speed - No re-download if already cached
- π Transitive dependencies - Dependencies have dependencies
- π Use dependency:tree - Understand what you're actually using
The Maven Dependency Lifecycleβ
Add to pom.xml
β
Check cache (~/.m2/repository/)
β
Download if missing
β
Store in cache
β
Add to classpath
β
Available in your code
What You've Learnedβ
- β Why adding 2 dependencies brings 50+ JARs
- β How Maven's local repository works
- β Why sometimes nothing downloads (cache hit!)
- β What Spring Boot starters actually contain
- β How to inspect and manage dependencies
Mental Modelβ
Maven is like a smart librarian:
- π Keeps a local copy of every book (cache)
- π Checks the local shelf first (cache lookup)
- π₯ Only orders new books if missing (download)
- π¦ When you borrow one book, brings all related books (transitive dependencies)
Further Readingβ
Next time you add a dependency, remember:
- One dependency can bring dozens of others
- Maven caches everything locally
mvn dependency:treeis your friend- Spring Boot starters save you from dependency hell!
Tags: #maven #spring-boot #dependencies #java #build-tools #caching #transitive-dependencies
