<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>example</artifactId>
<version>1.1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
Anytime you add a <dependency>
, Maven will try to fetch it from defined/configured repositories and use it
within the build lifecycle. You have to define a <version>
(note exception below), but <scope>
is optional for
compile
. (See Maven docs: Dep. Scope)
During fetching, Maven will analyze all transitive dependencies (see graph above) and, if necessary, fetch those too.
Everything downloaded once is cached locally by default, so nothing needs to be fetched again and again, as long as the
dependency definition does not change.
Rules to follow:
You should only use direct dependencies for things you are actually using in your code.
When declaring a direct dependency with its version managed by <dependencyManagement>
, a BOM or parent POM, you
may not provide one unless you want to explicitly override!
Clean up direct dependencies no longer in use. It will bloat the deployment package otherwise!
Care about the scope :
Do not include “testing only” dependencies in the final package - it will hurt you in IDEs and bloat things.
There is scope test
for this!
Make sure to use the runtime
scope when you need to ensure a library is present on our classpath at runtime.
An example is the SLF4J JUL bridge: we want to route logs from SLF4J into java.util.logging
, so it needs to be
present on the classpath, although we aren’t using SLF4J unlike, some of our dependencies.
Some dependencies might be provided
by the runtime environment. Good example: everything from Jakarta EE!
We use the Payara BOM to ensure using the same version during development and runtime.
Avoid using different dependencies for the same purpose, e. g. different JSON parsing libraries.
Refactor your code to use Jakarta EE standards as much as possible.
When you rely on big SDKs or similar big cool stuff, try to include the smallest portion possible. Complete SDK
bundles are typically heavyweight and most of the time unnecessary.
Don’t include transitive dependencies.
Exception: if you are relying on it in your code (see Z in the graph above), you must declare it. See below
for proper handling in these (rare) cases.
Maven is comfortable for developers; it handles recursive resolution, downloading, and adding “dependencies of dependencies”.
However, as life is a box of chocolates, you might find yourself in version conflict hell sooner than later without even
knowing, but experiencing unintended side effects.
When you look at the topmost graph above, imagine B and TB rely on different versions of TC. How does Maven
decide which version it will include? Easy: the version of the dependency nearest to our project (“Your Code)” wins. The following graph gives an example:
In this case, version “2.0” will be included. If you know something about semantic versioning, a red alert should ring
in your mind right now. How do we know that B is compatible with Z v2.0 when depending on Z v1.0?
Another scenario getting us in trouble: indirect use of transitive dependencies. Imagine the following: we rely on Z
in our code, but do not include a direct dependency for it within the POM. Now assume B is updated and removed its
dependency on Z. You definitely don’t want to head down that road.
Follow the rules to be safe:
Do not use transitive deps implicitly: add a direct dependency for transitive deps you re-use in your code.
On every build, check that no implicit usage was added by accident.
Explicitly declare versions of transitive dependencies in use by multiple direct dependencies.
On every build, check that there are no convergence problems hiding in the shadows.
Do special tests on every build to verify these explicit combinations work.
Maven can manage versions of transitive dependencies in four ways:
For more complex transitive dependencies, reuse a “Bill of Materials” (BOM) within <dependencyManagement>
.
Many bigger projects provide them, making the POM much less bloated compared to adding every bit yourself.
Better Avoid or Don’t
Use <optional>
or <exclusion>
tags on direct dependencies that request the transitive dependency.
Last resort, you really should avoid this. Not explained or used here, but sometimes unavoidable.
See Maven docs.
Make a transitive-only dependency not used in your code a direct one and add a <version>
tag.
Typically a bad idea; don’t do that.
Note: when the same transitive dependency is used in multiple Maven modules of a software project, it might be added
to a common <dependencyManagement>
section of an inherited parent POM instead. (Overrides are still possible.)
A reduced example, only showing bits relevant to the above cases and usage of an explicit transitive dep directly:
62