Maven: Essentials

This guide will explain the essentials you will need to get productive with Maven immediately. It is written with a complete beginner in mind. You should read this, if you don’t have any experience with Java package or dependency management beyond simply putting .jar files on your class path.

You might have been looking to use a library, and just couldn’t find the .jar file for it, or you’ve been told to “just use Maven.” Maybe you just switched over to Java from a different programming language, and you want to know how dependency management works.

Maven is the grand-daddy of most modern dependency management and build tools. Many modern programming languages can’t even become popular without a proper tool like it. So if you have ever used NPM with JavaScript, PIP with Python, or Bundler with Ruby, it will be pretty easy to understand what’s going on here.

While there are several other tools that try to replace Maven, they still use the Maven package repository, and therefore it is still useful to get some experience with Maven under your belt before you decide to move on.

As you may want to refer back to some parts of this guide, you can use the following table of contents to access any one of them quickly.

Installing Maven

Installing Maven can be a bit of a hassle. On Linux it is usually dead simple: Just install it from your package manager. However, if you are not on Linux, or you have to use a version that is not packaged, you will have to install it manually.

The manual installation consists of just 3 steps:

  1. Make sure you have a JDK installed (at least Java 7 for any modern version of Maven) and that the JAVA_HOME environment variable is properly set
  2. Download and extract Maven to a convenient location
  3. Add Mavens bin directory to your PATH environment variable

The following explains each step in detail.

Ensuring you have a proper JDK version

For Windows: Start a command prompt by running cmd (e.g. by pressing WIN+R and entering cmd in there).

For Linux or macOS: Open a terminal.

Now enter javac -version to see which JDK version you have. You should be seeing something along the lines of the following, which tells you that you have a JDK for Java 8 at update version 151 installed:

javac 1.8.0_151

If it says that the command was not found, then you have to properly install a JDK first. If you can run java -version but not javac -version, then you have probably installed a JRE instead, which still allows you to run Java applications, but will not allow you to compile Java programs. As you will be using Maven to compile your Java project, you have to install a JDK first.

The JAVA_HOME environment variable can be checked from the command prompt / terminal as well.

On Windows enter:

echo %JAVA_HOME%

On Linux or macOS enter:

echo $JAVA_HOME

You should see the path to your JDK installation as the result. If you don’t, but your JDK installation works as tested above, then you should set this environment variable properly using the same method as described in Adding Maven to your PATH.

Downloading and extracting Maven

You can download Maven from its download site, where you will be provided with links to the most recent release. Download the binary version, and extract it into a convenient location. It doesn’t matter if you choose the .tar.gz version or the .zip version, as their contents are identical — Windows user usually prefer .zip files, as they can be extracted without installing additional tools.

What you consider a convenient location may differ depending on your needs. If you want to install it for just your user, then the home directory may be a good idea.

Adding Maven to your PATH

It isn’t strictly required to add Maven to your path. However, if you skip this step, you will always have to use the full path to your Maven installation, which makes using it pretty tedious.

The method for adding Maven to your path depends on your operating system. The correct folder to be added to the PATH variable is the bin folder inside your Maven installation folder.

Windows users will have to go through the system control panel to do it. Use the search function to look for environment variables or directly open the dialog using WIN+R and enter "C:\Windows\system32\rundll32.exe" sysdm.cpl,EditEnvironmentVariables. On newer Windows versions you will get a list editor when editing the PATH variable, where you can easily add the proper path to the end. On older Windows versions, you will get just a line of text that you have to edit. In the latter case, you will have to add the path to Mavens bin folder prefixed with a semicolon, so the whole line looks something like this:

C:\WINDOWS\System32;...;C:\Users\username\maven\bin

It doesn’t really matter what exactly is in front, as you can leave that alone. All you have to do is add on another entry.

Linux and macOS users, will have to add it in their ~/.profile file (or .bash_profile or alternatively .bashrc). The line to do so should look something like this:

export PATH=$PATH:~/maven/bin

After changing your environment variables it is best to logout and login again or to restart the system in order to propagate the changes.

To check that you have successfully installed Maven, run mvn -v in the command promt / terminal. It should tell you about the installed version of Maven as well as some information about your Java version. You should see something along the lines of the following. Your actual values will certainly vary, but as long as you get anything apart from an outright error message, you have probably installed Maven correctly.

Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-10T17:41:47+01:00)
Maven home: C:\Users\dubs\maven\bin\..
Java version: 1.8.0_151, vendor: Oracle Corporation
Java home: C:\Program Files\Java\jdk1.8.0_151\jre
Default locale: de_DE, platform encoding: Cp1252
OS name: "windows 10", version: "10.0", arch: "amd64", family: "dos"

Given that you have installed Maven correctly, let’s move on to using it.

Setting up a project

Maven starts helping you out even before you have created your project. Aside from being a package management tool it is also a project generator, and there are a lot of different templates out there that you can use to create a new project. You are going to use the quickstart template now:

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart 

Run this command in a directory where you want to create the new project. It will download the template called maven-archetype-quickstart and use it to create your project. Then it will ask you for your groupId, artifactId, initial version and package, and use the information to set up an initial project structure for you.

Define value for property 'groupId': : tech.dubs
Define value for property 'artifactId': : example-project
Define value for property 'version':  1.0-SNAPSHOT: :
Define value for property 'package':  tech.dubs: : tech.dubs.example
Confirm properties configuration:
groupId: tech.dubs
artifactId: example-project
version: 1.0-SNAPSHOT
package: tech.dubs.example
 Y: : 

Between the colons you are provided with default values, which you can accept by pressing enter. To change the default value, you simply input your value before confirming with enter. In the example above, the default was accepted for the version property and a different value was chosen for the package property.

The convention for groupId is to use a domain name that you control in the reverse notation, just as you would do it when defining your package name. The example uses tech.dubs here, as dubs.tech is a domain I control. If you don’t own a domain name, you might want to use com.github.USERNAME with your username, or something similar to that. The artifactId is the name of your project.

With the values that are provided in the example, Maven will now create a new directory called example-project, it will contain a pom.xml file, as well as the necessary directory skeleton to get going. We will inspect the contents of the files it created shortly, but you may now want to import your newly created project into your favorite IDE. Both Eclipse and IntelliJ allow you to import Maven projects easily.

When using your IDE, you can create projects using Maven directly and still use templates and the like. However, this guide focuses on the handful of Maven commands that you should remember to be productive even without an IDE.

Updating the default settings

Let’s take a look at the files Maven created for you. The src directory contains both a main and a test directory. And both of them contain example code to get you started. Notice how the proper directory hierarchy for your packages has been created and you didn’t even have to think about it. The generated code is just a simple “Hello World”, which isn’t going to be inspected in more detail here.

The pom.xml file is where Mavens configuration lives. It contains meta-information about your project, as well as configuration on how to build it, and what dependencies your project has.

It should look like this:

<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>
  <groupId>tech.dubs</groupId>
  <artifactId>example-project</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>example-project</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

As you can see, the properties that were set during project creation can be found here and a dependency is already defined. The original quickstart archetype, that you used to create the project, needs a bit of additional setup. The quickstart archetype doesn’t set some values, which results in Maven using Java 5 as the lowest common denomitor. Your project, however should probably use at least Java 8.

Add the following inside the project tag.

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

This will tell Maven that you want to use Java 8 features in your source code and also that you will be targeting a Java runtime environment that also supports Java 8 binaries. There may be cases where you want to use just the features of an older Java version, but still target a newer one, in which case you would set up the source and target tags differently.

Let’s modernize it even further, and update the JUnit dependency to version 4.12, as that is the newest version at the time of writing. All you have to do, is to just update the value inside the version tag for the JUnit dependency to 4.12. And Maven will pick up the change the next time you compile your project.

Your pom.xml file should now look something like this:

<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>
  <groupId>tech.dubs</groupId>
  <artifactId>example-project</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>example-project</name>
  <url>http://maven.apache.org</url>


  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Adding dependencies

Now that you have setup your Maven project, let’s add some more dependencies to the project. You have already seen how to declare a simple dependency, with the automatically added JUnit. So for this example, a more complex dependency is used. Deeplearning4J is a large deep learning library, and most of the time you don’t need everything it has to offer. For this reason you have to add multiple dependencies to assemble what you need.

Add the following inside the dependencies tag:

  <dependency>
    <groupId>org.deeplearning4j</groupId>
    <artifactId>deeplearning4j-core</artifactId>
    <version>1.0.0-beta</version>
  </dependency>
  <dependency>
    <groupId>org.datavec</groupId>
    <artifactId>datavec-api</artifactId>
    <version>1.0.0-beta</version>
  </dependency>
  <dependency>
    <groupId>org.nd4j</groupId>
    <artifactId>nd4j-native-platform</artifactId>
    <version>1.0.0-beta</version>
  </dependency>

Now you can use Deeplearning4J on your CPU in your project, and utilize its datavec API to load data for it. However, that isn’t the reason why it is the chosen example. When assembling the single modules of Deeplearning4J, you have to ensure that they are all using the same version, or you may get hard to debug problems. So, if you want to update it, you will have to change all versions at the same time. As that can be a bit tricky once you have a lot of dependencies and it would open up the possibility that you overlook one of them, there is also a better way to do this.

Define a property that you can use as a variable instead. Add the following inside your project tag:

  <properties>
    <dl4j.version>1.0.0-beta</dl4j.version>
  </properties>

Using the dl4j.version tag you can now define that the Deeplearning4J version that you want to use is 1.0.0-beta. But, you still have to actually change the versions inside the dependencies, or it will have no effect. In order to do so, update the version tag of your new dependencies to ${dl4j.version}. This will insert the value that you defined using the dl4j.version tag.

There is nothing special about the dl4j.version tag, you might have called it foo.bar.baz instead, and used ${foo.bar.baz} to mark the places where you want its value inserted. You can use pretty much every name that you can think of here, however, some are already taken. So you should steer clear of using the pom, settings, scm, parent and project prefixes as well as the properties that define the project itself (i.e. name, groupId, artifactId, version, etc…).

Now that you’ve done that, your pom.xml should look something like this:

<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>
    <groupId>tech.dubs</groupId>
    <artifactId>example-project</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>example-project</name>
    <url>http://maven.apache.org</url>

    <properties>
        <dl4j.version>1.0.0-beta</dl4j.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.deeplearning4j</groupId>
            <artifactId>deeplearning4j-core</artifactId>
            <version>${dl4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.datavec</groupId>
            <artifactId>datavec-api</artifactId>
            <version>${dl4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.nd4j</groupId>
            <artifactId>nd4j-native-platform</artifactId>
            <version>${dl4j.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Finding dependencies

Most projects will tell you how to include them as a dependency in their documentation. However, maybe the one project you are looking for doesn’t do that. In that case you might want to visit https://search.maven.org where you can search through all projects that are available on the central Maven repository.

Simply enter the project name and see what comes up. By clicking on the version it will take you to a page which tells you how exactly the dependency declaration for this project at this version should look like.

Packaging

Maven is a build tool after all, so let’s build something. Using mvn package it will run your tests and then build your application. The resulting jar file will be placed inside the target directory.

If your project is a library, you can also run mvn install to directly install it in your local Maven repository. This allows you to use it as a dependency in other projects on your local computer. All of its dependencies will then be transitively loaded when you add it to your project, so you don’t have to add them manually.

However, your project probably is an application, and you want to create just a single jar file, which you can deploy somewhere or share with your friends, and they shouldn’t have to deal with Maven or any other build tool. In that case, you want to create something called an uberjar. An uberjar contains not only your application, but also all its dependencies and their dependencies. That way you can easily create a single jar file that requires nothing beside a Java runtime installation.

In order to create an uberjar simply add the maven-shade-plugin to your pom.xml file as another plugin inside the plugins tag:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>3.1.0</version>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <transformers>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>tech.dubs.example.App</mainClass>
          </transformer>
        </transformers>
      </configuration>
    </execution>
  </executions>
</plugin>

Notice that we define which mainClass to use in the case that the application is started without explicitly chosing an entry point. For example from the command line with:

java -jar filename.jar

If that wasn’t defined, it would be necessary to add the class you want to start at the command line like:

java -jar filename.jar tech.dubs.example.App

That also means that on many systems — given that Java is already installed — the application can be started by simply double clicking the jar file.

With the plugin, you still package your application by running

mvn package

But, you will now find two jar files in the target directory. One will have an original- prefix. That is the jar file that does not include any dependencies. The other one is your uberjar, and contains everything needed to run it. Another way to distinguish those two files, is that the uberjar will always be larger than the original as it contains all dependencies after all.

You can try running it from the root of your project. And you should see the output:

> java -jar target/example-project-1.0-SNAPSHOT.jar
Hello World!

At this point your whole pom.xml file should look like this:

<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>
    <groupId>tech.dubs</groupId>
    <artifactId>example-project</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>example-project</name>
    <url>http://maven.apache.org</url>

    <properties>
        <dl4j.version>1.0.0-beta</dl4j.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.deeplearning4j</groupId>
            <artifactId>deeplearning4j-core</artifactId>
            <version>${dl4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.datavec</groupId>
            <artifactId>datavec-api</artifactId>
            <version>${dl4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.nd4j</groupId>
            <artifactId>nd4j-native-platform</artifactId>
            <version>${dl4j.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-shade-plugin</artifactId>
              <version>3.1.0</version>
              <executions>
                <execution>
                  <phase>package</phase>
                  <goals>
                    <goal>shade</goal>
                  </goals>
                  <configuration>
                    <transformers>
                      <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>tech.dubs.example.App</mainClass>
                      </transformer>
                    </transformers>
                  </configuration>
                </execution>
              </executions>
            </plugin>
        </plugins>
    </build>
</project>

Scopes

You may have noticed that the JUnit dependency doesn’t quite look like the others that you have manually added:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

It contains a scope tag. The scope tag is used to specify under which conditions a dependency is required. In most cases you don’t want dependencies that you only need for testing (like JUnit) to be included in your project when you are running it. For this reason you can set the scope to one of compile, provided, runtime, test, system. Only compile, provided and test are detailed here, but if you want to know more about them take a look at the Dependency Scope section in the Maven manual.

The compile scope is the default scope if you don’t provide one. It means that the dependency is needed to compile and run your application.

The provided scope is very much like the compile scope, however, you tell Maven that this dependency will be provided by either the JDK or your application container on runtime. This is usually something that is set on dependencies like Java EE APIs.

The test scope makes a dependency only available when compiling and running tests. This means, that you will not be able to compile non-test code, which tries to use a test scoped dependency, nor will you be able to run it.

Troubleshooting

A common problem is that something about a defined dependency just isn’t quite right, and you just can’t figure out why.

The command mvn dependency:tree will output your whole dependency tree along with all of its transitive dependencies. By working through it, it is often easily possible to spot what the problem is.

One last thing

Maven has a lot of useful plugins, some of which can be directly called and that are then downloaded automatically. For example you might want to take a look at what security problems your dependencies may result in by running

mvn org.owasp:dependency-check-maven:check

It will download everything that it requires to do the job, and then run without you even having to modify your pom.xml.

Further reading

Now you know everything that is even remotely required to get started with Maven. But maybe you want to dig deeper and learn even more. If you are fine with reading on a screen, you can find a lot more information on the maven homepage, if you prefer a book Maven: The Definitive Guide may be better suited for you.

Paul Dubs

Paul Dubs
Professional software developer for over 12 years. Passionate about creating maintainable solutions and helping people to learn.

Quickstart with Deeplearning4J

This blog post shows how to get started with DL4J in no time. By using an example where the goal is to predict whether a customer will leave his bank, each step of a typical workflow is considered. Continue reading

Benchmarking ND4J and Neanderthal

Published on June 26, 2018