image-blog-vcs-maven-dependency-management
April 11, 2019

How to Use Maven for Dependency Management

Integration
Version Control

Using a Maven repository for dependency management can help you more easily build and maintain a Java project. In this blog, we share:

What Is Maven Dependency Management?

One of the core features of Maven is dependency management. It allows teams to manage dependencies for multi-module projects and applications. These can consist of hundreds or even thousands of modules. Using Maven can help teams define, create, and maintain reproducible builds.

Why Dependency Management Is Important

Dependency management is important because the product you work on is built of over 50 components, produced by several teams working on different schedules. Some of these components are more or less aligned on your product schedule. Others are produced almost independently and you treat them like an external library. You need to make sure you have the right versions of all of these components in your workspace in order to develop, build, test, and release your product.

Dependency management is one of the most difficult parts of developing software and related products.

You have two options for dependency management:

  1. Create your own.
  2. Use Maven for dependency management.

How to Set Up MVN Repository With Helix Core For Dependency Management

Here's an example how to set up your MVN repository with Helix Core for dependency management.

1. Prepare to Switch to Maven

Before Maven, dependencies were managed using an Ant build script. It compiled, packaged, and ran the program. The compilation task needed a single log4j JAR file and all the JARs included in the classpath. To set up the project, we needed to download the libraries and check them in to a 3rdParty folder in our software depot.

The Ant script declares two properties to reference the location of these libraries, and then includes them in the classpath for compilation:

<property name="log4j" location="../../../3rd_party/log4j/log4j-1.2.15.jar"/>
<property name="birtlib" location="../../../3rd_party/BIRT/birt-runtime-2_6_1/ReportEngine/lib"/>

<javac srcdir="${src}"
            destdir="${bin}">
            <classpath>
                <pathelement location="${log4j}"/>                 <fileset dir="${birtlib}">                     <include name="**/*.jar"/>                 </fileset>             </classpath>
</javac>

After implementation, some issues surfaced:

  • The relative paths to the libraries in the depot are fragile because not everyone maps things into their workspace in the same place. 
  • Information was embedded at the end of the two property declarations: the versions of log4j and BIRT (1.2.15 and 2.6.1 respectively).

2. Use Stream Paths For Managing Dependencies

Perforce Streams looked like it could help manage the dependencies on log4j and BIRT. Streams paths could more easily be updated. 

share ...
import lib/log4j/... //3rdParty/log4j/1.2.15/...
import lib/BIRT/... //3rdParty/BIRT/2.6.1/...

Although this worked a little better, there were still issues with dependencies.

  • It didn't scale well and impacted performance.
  • By default, changes in the paths in the main stream flowed down to all child streams instantly, without any throttling.
  • Dependency trees were tough to manage. For example, BIRT itself actually depends on other libraries like commons-codec. Using the straight directory path approach, we would need to explicitly add these other dependences to the stream paths or just bundle them up into the BIRT folder.
  • Although you could use the spec depot and other tools to keep track of how stream paths changed over time, the versioning of stream definitions isn't quite as powerful or easy to use as the versioning on regular files.

3. Set Up a Maven Project

To address the issues with Streams, we set up a skeletal Maven project by running mvn archetype:generate and picking a generic template. This laid out a predictable directory structure for a project that used Java source code that packages it into a JAR file. It also generated the POM (project model definition) for the project.

Since the POM is just an XML file, we could keep it in the project folder and check it in and out. It was able to be fully versioned in Helix Core. We could see the POM file in the Time-Lapse View to check how the dependencies change over time. And we could use Revision Graph to see how the file was branched and merged.

If the POM in the main branch is changed, a colleague working in a development branch can see that a change was available as a pending merge. This person can choose how and when to accept the dependency changes in their branch.

4. Declare log4j Dependency

In my Maven POM, we declared a dependency on log4j:

<dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.15</version>
</dependency>

log4j –– like a lot of open source tools –– is hosted a public Maven repository that's available by default. When we need to update to a newer version, all that is necessary is to update the version declaration.

5. Make a Local Maven Repository by Implementing BIRT

Implementing BIRT wasn't quite as easy. There are no public repositories that host BIRT in a way that's easy to consume. For this project, we came up with our own repository using Archiva.

Setting up a private repository is something that most Maven users want to do eventually. It lets you publish your own libraries as artifacts available to other teams in your company. 

To use Archiva, we added a little snippet to the Maven settings file, declaring that this repository is the default mirror.

<mirror>
      <id>archiva.default</id>
      <url>http://my.maven.repo:8080/archiva/repository/internal/</url>
      <mirrorOf>*</mirrorOf>
</mirror>

Then we imported all of the BIRT library JAR files into Archiva, In this case, we just used the web interface. In order to make the POM simpler, we also installed a standalone BIRT project with it's own POM that declares all of the BIRT library files and their dependencies. The following example shows the artifact ID and a couple of the dependencies.

<groupId>org.eclipse.birt</groupId>
<artifactId>birt-container</artifactId>
<version>2.6.1</version>
<packaging>pom</packaging>
<dependency>
            <groupId>birt</groupId>
            <artifactId>coreapi</artifactId>
            <version>2.6.1</version>
</dependency>
<dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.0</version>
</dependency>

By installing this POM, we can easily reference BIRT, and all of its libraries and dependencies, as a single logical artifact:

<dependency>
        <groupId>birt</groupId>
        <artifactId>birt</artifactId>
        <version>2.6.1</version>
        <type>pom</type>
</dependency>

6. Build With Maven

Now that we have the POM referencing the log4j and BIRT libraries, and we have BIRT installed on the local repository, we can build and run this program by invoking normal Maven goals:

mvn package

The POM is short and sweet. Maven can automatically pull dependencies from the repository as necessary. Maven helped us manage dependencies in this simple project. We were able to:

  • Create a simple text file with all of the dependency declarations. This file is versioned, which means we can branch, merge, view the history, and roll back as necessary.
  • We can quickly update dependency versions by changing a single line the POM. Downstream streams or branches can choose when and how to accept POM changes.
  • Maven automatically injects dependencies where necessary for building, testing, and running a project.
  • Maven scales to handle complex dependency trees via dependency inheritance.

Why Maven Is Used: FAQ

Here are some of the most frequently asked questions we get about why Maven is used.

Can Maven Be Used For Non-Java Projects?

Yes. There are hundreds of archetypes for Maven, ranging from Java web application development to eBook publishing. There's also a well regarded extension for building C, C++, and Fortran.

Can Maven Handle Transitive and Circular Dependencies?

Maven will inspect the POM of a dependency artifact and follow its dependencies. It will also validate the POM to make sure there are no circular dependencies.

Does Maven Allow for Granular or Coarse Version Dependency?

You can use Maven version fields to have more flexibility when selecting the version of a dependency. For example, you can just declare the major and minor versions and Maven will automatically use the latest available in that set. If I declare that I want to use log4j 1.2, I'd get the latest in the 1.2 series.

What Else Does Maven Do?

Maven does a lot more than dependency management.

  • Maven provides transparency about projects. It can provide reports for unit tests, code coverage, and other key metrics.
  • Maven provides uniformity in the build environment since projects are declared in POM files (no more cryptic scripts).
  • Maven makes continuous integration builds and releases (publishing artifacts to Maven repositories) very easy to automate.

Dependency Management Needs Version Control

There are of course a lot of tools out there to manage dependencies. The tool you choose will depend on your projects, environment, and development teams.

But not all version control systems allow you to version everything. Helix Core stores all your digital assets, giving you the platform to build and customize your toolset. By implementing Maven dependency management with Helix Core, you can help you easily scale and grow.

Get started with Helix Core for free for up to 5 users and 20 workspaces.

GET HELIX CORE

 

This blog was originally published on April 11, 2012 and has been updated for accuracy and comprehensiveness.

Related Content