Quick Overview of Maven Dependency Management

Jon Bake
4 min readMar 11, 2018

--

I have XMLitis. It is a condition where I break out in hives and become extremely anxious any time I open a file that starts with <?xml. Apparently, it is very common among software developers.

As a software developer, there is most likely going to come a point in your career where you have to accept the hives and open an XML file (it can’t be all sunshine and roses after all). One of those times may be when the project you are working on is using the project management tool Maven.

One of Maven’s primary role is a dependency manager. It can be used for other functions (primarily through plugins), like, for instance, as a build tool (through the Maven Compiler Plugin). But, at its heart, it’s a dependency manager.

Similar to a package.json in Node/NPM or a Gemfile in Ruby/Bundler, Maven specifies dependencies through a pom.xml file. This post explores the nuances of specifying dependencies in Maven via pom.xml files.

Breaking Down a Pom

To get a baseline understanding, let’s just quickly break down the basic pom.xml. Hopefully, you don’t break out in hives:)

Identifying an Artifact

An artifact is identified by groupId, artifactId and version. For example:

<groupId>com.jonbake</groupId>
<artifactId>barebones-example</artifactId>
<version>1.0-SNAPSHOT</version>

Version is either a snapshot, i.e. still in development and changing, or a release. When another project wants to pull in this artifact as a dependency, they simply have to specify these coordinates.

Packaging

Packaging basically describes what form the artifact will take when built. Some common packagings are jar, ear, and war. A packaging of pom doesn’t perform any packaging and keeps the artifact simply as a descriptor of dependency versions. This is useful for BOMs, which are described below. Example:

<packaging>pom</packaging>

Modules

Maven supports multi-module projects, meaning it is possible to have a root pom.xml and sub-directories with different pom.xml. The benefit being that sub-modules inherit dependency info from the parent POM. Example of specifying modules in the parent:

<modules>
<module>barebones-example-module</module>
</modules>

Properties

Properties allow one to declare both build and project properties. Properties can then be referenced from elsewhere in the POM. This is a nice way to keep all version information in a single place. Example:

<properties>
<junit.version>3.8.1</junit.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

Dependency Management

The Dependency Management section of the pom.xml is the useful when modules are specified. It allows specifying version and scope information in a single place — in the root POM. Modules POMs still have to specify the dependency, but they can leave off version. This is useful for ensuring consistent versions across modules. Example:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${ junit.version }</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

Dependencies

The dependencies section is the meat and potatoes of the POM. It is where the module’s dependencies are specified (hence, the name). To keep with pattern of giving an example:

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>

Besides plugins, that is basically what a POM is composed of.

Understanding Scope

One of the most important concepts to understand about Maven Dependency management is scope. Like the example above, every dependency has a scope. There are six types of scope, but the three of the most commonly used ones are compile, provided, and test.

Compile scope is the default scope. It tells Maven to package the dependency within the artifact declaring the dependency. For example, if you have a jar-packaged artifact and declare junit as a compile-scoped dependency then junit will be packaged within the lib directory of the built jar.

Provided scope tells Maven that the dependency does not need to be packaged because it is provided by the runtime environment. For example, if the artifact , e.g. war or ear, is being deployed to Tomcat or JBoss, the java-ee-api artifact is provided.

Test scope tells Maven that the dependency should only be available to test classes.

There is danger in accidentally marking a provided dependency as compiled-scoped, especially if there is a version mismatch. It can lead to weird class loading issues.

Using BOMs (Bill of Materials)

BOMs are useful if you want to pull in all the dependency versions of another project. For example, if you are deploying to JBoss, you probably want to use all of its provided dependency versions. To use a BOM, add its POM as a import-scoped dependency to the dependencyManagement section. For example:

<dependencyManagement>
<dependency>
<groupId>org.jboss.bom</groupId>
<artifactId>eap-runtime-artifacts</artifactId>
<version>7.0.9.GA</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>

Then one can use the provided dependencies without the need to specify versions and the versions will always be consistent with the environment being deployed to. For example:

<dependencies>
<dependency>
<groupId>hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

Wrap Up

That’s a very quick and dirty overview of Maven dependency management. Hope you found it useful!

--

--