Dependencies & versions in Java
Transitive dependencies, conflicts, semantic versioning, vulnerability scanning.
You ship a whole tree
Each dependency brings its own dependencies, and those bring theirs. These transitive dependencies are part of what you ship. See the full tree with **mvn dependency:tree or gradle dependencies**.
When versions collide
Two paths may ask for different versions of the same library; the build must pick one. Maven: nearest wins, the declaration closest to your project in the tree. Gradle: highest wins, it upgrades to the highest requested version unless you add constraints.
Maven picks one
Your app depends on A → C 1.0 and on B → D → C 2.0. Which C does Maven put on the classpath?
C 1.0C 2.0Both versions
Show the answer
C 1.0: it's nearer (depth 2 beats depth 3), even though it's older. To get C 2.0, declare it directly or pin it in **dependencyManagement**. Gradle would pick 2.0.
Semantic versioning
SemVer is MAJOR.MINOR.PATCH. 2.4.1 → 3.0.0: breaking changes allowed. 2.4.1 → 2.5.0: new features, backward compatible. 2.4.1 → 2.4.2: bug fixes only. It's a promise from the author that helps you judge upgrade risk; your tests still have the last word.
NoSuchMethodError at runtime
Everything compiles, but a request fails with **NoSuchMethodError inside a Jackson class. Two libraries need different Jackson versions, and the one that won lacks that method: code compiled against one version is running with another. Check the tree and align versions, for example with a BOM**.
Keeping dependencies safe
<version>[1.0,)</version>Always 'latest': builds aren't reproducible and surprises arrive silently.
<version>2.17.1</version>
<!-- + Dependabot / OWASP
Dependency-Check in CI -->Exact versions, automated vulnerability scans and regular small upgrades.
Log4Shell, transitively
A scanner flags log4j-core with Log4Shell (CVE-2021-44228), but your build never mentions log4j. It arrived transitively through another dependency. mvn dependency:tree shows which library pulled it in, so you know what to upgrade or override.
Key takeaways
- Transitive dependencies are part of what you ship
- Maven: nearest wins; Gradle: highest wins
- SemVer MAJOR.MINOR.PATCH: MAJOR may break
- Inspect with mvn dependency:tree / gradle dependencies
Log4Shell received the maximum CVSS severity score of 10.0.
Practice questions
Your app depends on A -> C 1.0 and on B -> D -> C 2.0. Which C does Maven put on the classpath?
- C 2.0, because the highest version wins
- C 1.0, because the nearest declaration wins
- Both versions
- None: the build fails
Check your answer
C 1.0, because the nearest declaration wins. Maven's 'nearest wins' picks the version closest to your project in the tree (depth 2 beats depth 3), even if it is older.
A scanner flags log4j-core with Log4Shell (CVE-2021-44228), but your build file never mentions log4j. How is that possible?
- Scanners guess at random
- It arrives transitively through another dependency; inspect the dependency tree
- The JDK bundles log4j-core
- It's in your IDE, not your app
Check your answer
It arrives transitively through another dependency; inspect the dependency tree. Most of your shipped code comes in transitively. mvn dependency:tree or gradle dependencies shows which library pulled it in.