Welcome back to the Mastering Maven series. In this post we'll explore dependency resolution and its effects on a single project. As you may have seen before, dependencies must be defined inside the <dependencies> block. Each dependency must declare its GAV coordinates as minimum settings, a <scope> element may be declared to instruct Maven where to place the resolved dependency in terms of classpath configuration, however dependencies will be placed under the compile scope if no such element is explicitly defined.
Maven defines 6 scopes: compile, runtime, provided, system, test, and import. Maven defines the behavior for each scope as following (copied verbatim from the dependency management page)
Let's have a look at these scopes in action, shall we? The following POM file defines 4 dependencies, 2 of which are placed in the compile scope (1 implicit and 1 explicit), one dependency in the runtime scope, and one dependency in the test scope.
We can obtain a report of all dependencies used by the build by invoking the mvn dependency:list command, like so
As you can see the list contains all dependencies we defined, listed by scope. We can appreciate that the commons-lang3 dependency is indeed placed in the compile scope even though we didn't specify a scope for it, neat! But hold on, the list displays 5 elements even though we defined 4, what's going on here? Dependencies may have additional dependencies as well, which can be brought into the build. These are known as transitive dependencies, in our case the hamcrest-core dependency was added by one of the other 4 dependencies, but which one? The mystery can be solved by invoking the mvn dependency:tree command
This command lists all dependencies using a tree structure instead of a flat list as we saw before. This tree structure shows dependencies as they are resolved, including transitive dependencies, and now we can answer the question of who brought hamcrest-core into the build: it was in fact junit.
Moving on to the next scope, provided, you typically find this scope used on projects that expect dependencies to exist on the classpath of the hosting environment, such as an Application Server. However, the side effect of a provided dependency is that it's required for compilation but it might be omitted at runtime if the hosting environment does not provide said dependency. It is due to this "loophole" that libraries that rely on annotation processors decided to latch onto this scope, and as such you'll find dependencies such as Lombok, Dagger, AutoService, and other popular annotation-based libraries being applied on this scope.
Next we have the system scope, which I must say it's widely regarded as a scope you must avoid at all costs, a suggestion I strongly agree with, except for one particular use case: if you know what you're doing and you're aware of the consequences. Here's the thing with this scope, dependencies placed under system are resolved from the filesystem and never from a repository as other dependencies are. Builds become environment-specific especially if the dependency path is absolute instead of relative to the project. There's also the matter of missing metadata, as these dependencies do not have a companion POM file thus any transitive dependencies must be defined explicitly as well. There's one use case in my mind (and take this with a grain of salt) that makes it valid to use this scope: you're developing a feature that requires some experimental dependency that is not yet available from a repository, this feature is in flux and may or may not see the light of day, basically you may throw away the whole thing at some point and do not want to "pollute" your local/remote repositories with a dependency that may need to be removed.
Now for the final scope which has become more important in that last years with the rise of libraries and frameworks that require a specific set of dependencies to work, frameworks such as Spring Boot, Helidon, Micronaut, Quarkus to name a few; this scope deserves a post of its own as it leads into a concept known as Bill of Materials (or BOM for short) that requires further explanations.
Source code available at 04-dependencies.
Next Post