{{announcement.body}}
{{announcement.title}}

Dependency Management and Versioning With a Maven Multi-Module Project

DZone 's Guide to

Dependency Management and Versioning With a Maven Multi-Module Project

Learn more about basic dependency management and versioning with Maven.

· Java Zone ·
Free Resource

In this article, we are going to look at how to implement a multi-module project in Maven with versioning and dependency management, as well as the best practices for building big, large-scale projects from both a developer perspective and a DevOps/management perspective.

However, if you are not familiar with Maven, I highly recommend reading this article first and getting some experience using Maven. This article will not cover Maven basics.

So, with that in mind, let's get started.

Introduction

When dealing with large-scale software, we have to communicate, work, and cooperate with a lot of people — whether in an organization or a community. Besides that, we have to deal with the environment we are working with, which may consist of many projects, large or small, external projects, libraries, shared modules, utilities, and many others. Likewise, let's not forget that we are not only working with developers but DevOps and management teams, who have to coordinate the entire process so it can run smoothly.

Below, we are going to see how Maven can make our lives a lot easier.

Master-Root POM Project

This is the desired structure we want to accomplish:

Image title

I used NetBeans 8.2, but it can be accomplished with simple mvn commands.

Now, we begin by creating a POM project company-root similar in the picture. The following are the important parts of the pom.xml:

The packaging tag defines the type of project, which is a type of POM:

<packaging>pom</packaging>


POM is basically a container of sub-modules.

The properties tag:

<properties>
  <appx.version>1.0.0</appx.version>
</properties>


Properties are value placeholders. Their values are accessible anywhere within POM using the notation ${X} where X is the property.

We are going to define versioning in the properties but will explain later.

The dependencyManagement tag:

<dependencyManagement>
    <dependencies>     
        ...
    </dependencies>
</dependencyManagement>    


A very important use of the dependency management section is to control, consolidate, and centralize the versions of artifacts used in dependencies and inherited by all children.

So, what we are going to do is define the company-root pom.xml as a base/parent project, define the dependencies that are going to be used, and set the versioning and the child projects.

A Simple Real-Life Scenario

We are going to define the usage of the JUnit framework, version, and scope for the projects below company-root. This is really useful when having dozens or more projects because you know exactly what version of dependencies they have. The projects don't define the versions themselves, rather the people who control the company-root project and 'enforce' the child projects on what to use.

So, the QA department wants everyone working in any project to use the JUnit 4 version with the test scope, which defines what is needed only in test phases and not the normal use of the app). This is done in the company-rootpom.xml

First, set the desired version for the JUnit in the properties section.

<properties>     
    <junit.version>4.12</junit.version>
</properties>


And then, the dependency:

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


With the above declaration, everyone will be using JUnit 4.12 with a test scope.

The declaration of JUnit on child projects is as simple as that.

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


Now, let's assume the DevOps engineer wants to test everything with the new JUnit 5 Framework. The only action required for him would be to change the property version on company-root pom from 4.12 to 5.0 and run the tests.

More info on the Maven dependency mechanism can be found here.

Versioning

Let's not forget the versioning numbers and the role they have to play:

Image title

Also, let's take a moment for a quick recap:

  • Bug fix: just bug fixes and related stuff.
  • Minor: Improvements, new features, deprecation notices, don't break user code (backward-compatibility). Same API.
  • Major: new features, to complete API changes.

More info for software versioning can be found here

A Common Project

In the next step, we want to create a base framework with core functionality or a library with common utilities for all of our projects to use, extend, or explore whatever the usage is.

To succeed that, we created a new Maven Java application with a name called common. If we open pom.xml of the newly created project, we observe that the parent section is missing.

Let's adjust it by concluding a parent section so commons will turn into a child (or a Leaf POM, a child with packaging other than POM) of the company-root project.

After we finish editing pom.xml, the outcome is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.protectsoft.company</groupId>
    <artifactId>commons</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>com.protectsoft</groupId>
        <artifactId>company</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

   <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

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


If you are not familiar with Maven, I highly recommend reading this article first and establishing some experience around Maven.

Two important things we need to mention:

1) The parent section. Now, this project has an inheritance.

2) The dependency section. We defined a JUnit dependency without version or scope because they are inherited. That means the usage of the JUnit framework is predefined from someone else.

Now, let's update company-root POM and add a version for commons project for others to use and keep things organized. To do this, we add the following in the company-root POM:

<properties>
    <!-- Our projects versioning -->
    <company.commons>1.0-SNAPSHOT</company.commons>    

    <!-- External dependencies versioning -->        
    <junit.version>4.12</junit.version>
</properties>

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


Creating a 3-Tier Web Application

Here, we are going to create our first web application using 3-tier architecture. But we want this application to be developed into three separate sub-projects and glued all together as one.

We are considering this stack for the 3-tier.

1) Presentation Layer

REST

2) Bussines/LogicLayer

EJB Enterprise Java Beans

3) Data Access Layer

JPA Java Persistence Object

First, in the presentation layer, we have the endpoints to be consumed by clients through HTTP. We might say it is an API for clients. In the business layer, this is where you can find all of the logic and functionality that really matters, and finally, you can also find the data access layer, which is the store and access of data through persistent storage.

The desired result we want to achieve is this:

Image title

App1 is a POM project and a child of company-root. App1-web is a web project that exposes the endpoints. App1-web is the presentation layer of our 3-tier. App1-web project contains a dependency of app1-ejb project, which is the business layer. The app1-ejb contains a dependency of app1-jpa, which is the data access layer and mostly contains entity classes and metadata information. The parent project from app1-web, app1-ejb, and app1-jpa is the app1.

After we create the app1 POM project, the important sections of pom.xml is the following:

<packaging>pom</packaging>
<parent>
  <groupId>com.protectsoft</groupId>
  <artifactId>company</artifactId>
  <version>1.0-SNAPSHOT</version>
</parent>

<dependencies>
  <dependency>
    <groupId>com.protectsoft.company</groupId>
    <artifactId>commons</artifactId>
  </dependency>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
  </dependency>
</dependencies>


This project has company-root as its parent, and in dependencies, the version is inherited.

Now, let's create the three separate sub-projects. We can do it in two different ways. One is from the NetBeans IDE, which will update parent POM with sub-modules section and add a parent to the children, and the second is by hand and we have to edit pom.xml in the parent and children.

We are going to do it from the NetBeans IDE. Eclipse or IntelliJ have the same support.

From NetBeans, we click on Modules in the app1 project and complete the 'Create new module...' option.

Image title

We do that three times for app1-jpa, app1-ejb, and app1-web.

After that, we going to edit the pom.xml for the new three projects.

We open app1-jpa POM and we see that it has app1 as a parent. This is also present in app1-ejb and app1-web. All three projects have app1 as a parent.

<parent>
  <groupId>com.protectsoft.company</groupId>
  <artifactId>app1</artifactId>
  <version>1.0-SNAPSHOT</version>
</parent>


In app1-ejb, we add the app1-jpa as a dependency:

<dependencies>      
  <dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>app1-jpa</artifactId>
  </dependency>
</dependencies>


And in app1-web, theapp1-ejb is a dependency.

<dependencies>
  <dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>app1-ejb</artifactId>
  </dependency>
</dependencies>

We have completed the relationship in the picture above.

Next, we going to define versioning and dependency management in the app1 pom.xml for the children.

<!--app1.pom will define what version of child projects/modules can be used -->
<properties>
  <app1.jpa.version>1.0-SNAPSHOT</app1.jpa.version>  
  <app1.ejb.version>1.0-SNAPSHOT</app1.ejb.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>app1-ejb</artifactId>
      <version>${app1.ejb.version}</version>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>app1-jpa</artifactId>
      <version>${app1.jpa.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>

<!-- app1 has this dependencies to be used be the sub-modules -->
<dependencies>
  <dependency>
    <groupId>com.protectsoft.company</groupId>
    <artifactId>commons</artifactId>
  </dependency>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
  </dependency>
</dependencies>


Reactor

We also notice this new section in the app1 pom.xml

<modules>
  <module>app1-ejb</module>
  <module>app1-jpa</module>
  <module>app1-web</module>
</modules>


Meaning that the app1 POM project also has the role of the aggregator. That means the app1 project will build all sub-modules/projects defined in the modules section with a specific order that is analyzed by the Reactor.

Indeed, if we choose to build from app1 or run the command mvn package from the app1 folder, we notice this:

Image title

That means that the reactor analyzed all the projects in modules and their dependencies and a specific build order is generated. Also, cyclic dependencies are not allowed. In the above picture, first, we have app1-jpa as a result of having no dependencies; next, the app1-ejb can be built afterapp1-jpa because it is using it, and finally, the app1-web takes place after app1-ejb is completed.

Reactor summary prints the success output.

Image title

Reactor and project aggregation are very useful in packaging and testing environments.

Using a Different Version for Different Projects

What to do in a situation where a version X of a library must be used in the project app1 and version Y in the project app2?

We are in a situation where QA management decided that the usage of a reporting library will be version 1 for app1 and version 2 for app2. App2 needs some extra features that only version 2 provides.

Image title


Image title

We cannot set the version in the company-root as one property because only one version will take place.

One solution to this new requirement is to define dependency management and versioning at the app1 and app2 projects.

So, app1 POM can include this for version 1.

<properties>          
<report.version>1.0.0</report.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.protectsoft.company</groupId>
      <artifactId>jasper-report-ejb</artifactId>
      <version>${report.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>


And app2 POM includes the following:

<properties>          
<report.version>2.0.0</report.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.protectsoft.company</groupId>
      <artifactId>jasper-report-ejb</artifactId>
      <version>${report.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>


App1 and App2 are responsible and in control of the context of the sub-module projects. Furthermore, changes can take place more easily.

Last But Not Least, the Profile Section

At this point, we have to mention the profile section and its use. A lot of the staff being said above can be grouped into different profiles. For example, we can have one profile for the X version of our commons library and another profile for the Y version of commons. Another example would be two have different profiles for the environments like test environment and a demo environment.

We are going to modify the parent root pom.xml and add two profiles. One is versioning for Java EE 7 and the other for Java EE 8.

<profiles>
   <profile>
      <id>java-ee-7-profile</id>
      <properties>
        <javax.version>7.0</javax.version>  
      </properties>
    </profile>
    <profile>
      <id>java-ee-8-profile</id>
      <properties>
        <javax.version>8.0</javax.version>  
      </properties>
    </profile>
</profiles>


And with Maven, we can run:

  • mvn clean install -P java-ee-7-profile OR
  • mvn clean install -P java-ee-7-profile,java-ee-8-profile for both profiles.

Also according to Maven's introduction to profiles. The profile section can declare and modify the following elements.

  • <repositories>
  • <pluginRepositories>
  • <dependencies>
  • <plugins>
  • <properties> (not actually available in the main POM, but used behind the scenes)
  • <modules>
  • <reporting>
  • <dependencyManagement>
  • <distributionManagement>
  • a subset of the <build> element, which consists of:
    • <defaultGoal>
    • <resources>
    • <testResources>
    • <finalName>

Conclusion

When following these steps, or setting up a similar architecture, be sure none of this is taken for granted. Rather, one should study and research what the needs and requirements really are, and take actions accordingly. This article is meant to provide a more general look at building multi-module projects with Maven, providing ideas and solutions to more general problems.

The complete git repo is available here.

Topics:
java ,maven ,versioning ,project management ,version ,dependency ,devops ,integration ,tutorial ,dependency management

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}