Dependency Management
In the previous chapter, we created a simple Java project and build it with Maven. In this chapter, we use Maven Dependency Management feature to add external modules to the project.
Maven can manage both internal and external dependencies. For a Java project, external dependencies might be libraries such as JUnit, Apache Commons, Log4J etc., Internal dependencies are modules of a related project. We will cover internal dependencies in a later chapter when we deal with multi module projects.
Project Dependencies
The simple app developed earlier is doesn’t use any dependency. Let’s add Apache Commons Lang library to the project and use it to format and display the date. Replace the App.java contents with the following code.
simple-app/src/main/java/com/xyz/App.java
package com.xyz;
import java.util.Date;
import org.apache.commons.lang.time.DateFormatUtils;
public class App {
public static void main(String<a href="http://books.sonatype.com/mvnref-book/reference/pom-relationships-sect-project-dependencies.html#pom-relationships-sect-dependency-scope" target="_blank">] args) {
System.out.println("Hello World! Today is " + getToday());
}
public static String getToday() {
String today = DateFormatUtils.format(new Date(), "dd-MMM-yyyy");
return today;
}
}
Nothing big, except that we add an external dependency to our project. Let’s try to build the project.
$ mvn compile
As expected, build fails since complier is unable to find Commons Lang library.
Let’s delegate the dependency management to Maven and to do that, add dependency details to POM. Modify pom.xml with following contents.
simple-app/pom.xml
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- project coordinates -->
<groupId>com.xyz</groupId>
<artifactId>simple-app</artifactId>
<version>1.0</version>
<!-- project dependencies -->
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
</project>
The dependency is defined using <dependencies>/<dependency>. The element <dependencies> can contain multiple <dependency> and in each dependency, we provide the coordinates of the library (groupId, artifactId and version) we wish to add as dependency to our project and the above declaration adds commons-lang version 2.4.
Run command mvn compile
and Maven downloads commons lang version 2.4
from the central repository and save it in local repository, and then
compiles the project. This time, javac finds the library in build path
and compilation goes through without any error.
However, we can’t run the app directly using the java command. Java
throws NoClassDefFoundError
as it is unable to find commons-lang in
runtime classpath. There are multiple ways to handle this:
Brute force method is add commons-lang.jar to classpath. Maven caches the commons-lang.jar in the local repository and we find its location and add the location to the runtime classpath.
$ java -cp target/simple-app-1.0.jar:$HOME/.m2/repository/commons-lang/commons-lang/2.4/commons-lang-2.4.jar com.xyz.App
But, the better and suggested approach is to use Maven to run the app.
$ mvn exec:java -Dexec.mainClass="com.xyz.App"
We will explain the meaning of this command in greater detail in Plugins chapter and for the time being, it is sufficient to know that we use Maven Exec Plugin to run a class.
The command uses exec-maven-plugin to execute the class com.xyz.App. It is important to note that, the plugin builds the runtime classpath from the dependencies listed in pom.xml, by adding the location of commons-lang-2.4 jar, before executing the app.
Net Connection
Whenever a new dependency is added to pom.xml or a new plugin is required, Maven downloads the dependency or plugin artifact from its central repository, if they are not available in the local repository. Hence, ensure that Internet connection is enabled whenever you add a new dependency or plugin to pom.xml or a new plugin is required to run the command.
When connection is down and Maven triggers build failure, you can always rerun command after enabling the connection.
Unit Tests and JUnit Dependency
Next, let’s add a unit test to test the getToday() method.
In Maven Java project, the test source files are placed in
src/test/java
directory. To, test getToday(), we also need a package
com.xyz to add test files. Create these directories as shown here.
$ cd simple-app
$ mkdir -p src/test/java
$ mkdir -p src/test/java/com/xyz
The mkdir creates directories to hold test files and for the package
com.xyz. If you observe the directory structure is same as the one we
created to hold source file src/main/java/com/xyz
, except that
main directory is replaced with test directory. Next, add test
file AppTest.java with following code.
simple-app/src/test/java/com/xyz/AppTest.java
package com.xyz;
import static org.junit.Assert.assertEquals;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.junit.Test;
public class AppTest {
@Test
public void testGetToday() {
String actual = App.getToday();
String expected = new SimpleDateFormat("dd-MMM-yyyy")
.format(new Date());
assertEquals(expected, actual);
}
}
Try to run tests with command mvn test
but build fails as Maven
requires JUnit 4 in the build classpath to compile the test files. Edit
pom.xml and modify <dependencies> element to look as shown in the next
snippet.
simple-app/pom.xml
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
Apart from the coordinates of the Junit (groupId, artifactId, version), the dependency contains a new item - scope. It tells Maven to add JUnit dependency to the classpath only during test compile phase and test phase. With this scope, JUnit will not added to classpath during source compile. Scope controls which dependencies are available in which classpath, and which are included with an application. Maven provides five dependency scopes - compile, provided, runtime, test and system. Refer Maven Reference - Dependency Scope to know more about them.
To run tests, use command mvn test
. Maven compiles Java source files
and then compile test files and finally, run the tests. Maven uses a
plugin called Maven Surefire Plugin (maven-surefire-plugin) to handle tests and to output the test results.
To run tests, use command mvn test. Maven compiles Java source files and then compile test files and finally, run the tests. Maven uses a plugin called Maven Surefire Plugin (maven-surefire-plugin) to handle tests and to output the test results. The test result is shown in the screenshot.
Get Maven Dependency Coordinates
Now, the question is how to find the coordinates of library or module which we want to add to the pom.xml.
When an organization deploys the module to Maven central repository they define the coordinates and from the central repository we can easily find the coordinates of any library or module. As an example, let’s find out the coordinates of commons-lang, from the repository. Go to Maven Central Repository at http://search.maven.org/ and search for commons lang which yields all matching commons module. In the list, go the commons-langs row and select All in Latest Version column. It displays the GroupId, ArtifactId and Version for each release. From these values, we can construct the dependency element.
Alternatively, we can even get the complete dependency snippet from MvnRepository. In the repository, search for commons lang and from the search result, select commons-lang link, which shows the available versions.
Click on 2.6 or whatever version you are interested. Repositories hosts modules for Maven and also other build systems like Ivy, Grape etc. Select Maven tab and there you get the exact dependency element as required by Apache Maven as shown in the next screenshot. Cut and paste it to pom.xml.
Another thing about this particular dependency is that the groupId is mentioned as commons-lang whereas it should have been apache.org. For some reasons, while deploying the module to the repository, developer (the build guy) has mentioned it as commons-lang instead of apache.org and so it is continuing like that. From version 3 onwards groupId is corrected as apache.org.
Instead of guessing, it is much easier to search in the Maven repository and copy the dependency element from there.
Maven Artifacts
In general software terms, an Artifact is something produced by the software development process, whether it be an executable file or documentation.
In Maven terminology, anything built and deployed by Maven to its repository is an artifact.
Some of the examples of Maven Artifacts:
dependencies - libraries such as commons-io, commons-lang, JUnit, Log4j etc.,
Maven plugins - even, the JAR of Maven Plugins such as maven-clean-plugin, maven-compiler-plugin etc., are also artifacts since they, too, built and deployed by Maven.
documentation jars - Javadoc archive of the projects.
source jars - source code archive of the projects.
The screenshot shows the layout of the local repository that contains artifacts of two projects - Apache Commons Exec and Maven Jar Plugin. As we can see, each contains a JAR (the artifact), POM file and SHA key files.
To use an artifact (either as dependency or plugin) in a Maven project, we refer it by its coordinates. The Maven coordinates is combination of <groupId>:<artifactId>:<version>.
groupId - name of the organization which owns the project and usually a reversed domain name, for example org.example.foo.
artifactId - name of the project, library, plugin or a software component.
version - a version string.
Maven uses the coordinates to construct the layout repository and also,
to locate the artifacts in the repository. For example, the coordinates
of commons-exec is org.apache.commons (groupId), commons-exec
(artifactId), 1.3 (version) and in the repository, it lands in
org/apache/commons/commons-exec/1.3
folder.
In the next chapter, we look at Maven Lifecycle and Phases.