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
- Setting up a project
- Adding dependencies
- Packaging
- Scopes
- Troubleshooting
- Further reading
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:
- 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
- Download and extract Maven to a convenient location
- 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:
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.
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:
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:
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:
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:
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:
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:
Scopes
You may have noticed that the JUnit dependency doesn’t quite look like the others that you have manually added:
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.