With this post, we continue the series of one-page cheat sheet for Java developers. This time we’ll look at Maven, the most popular Java build tool and dependency manager! Almost everyone uses it, many hate it, some complain about the verbosity of the xml configuration and its inflexibility, some praise the inflexibility so their teammates have a harder time messing up the build for everyone. In any case, knowing Maven is a must have skill for any respected Java developer, and with this cheat sheet, you can have some of the most important and frequently needed information at a glance.
Last year we explored some of the topics that are universally used in software development and so we have quite a library of useful cheat sheets to please your sight and remind of the commands and options developers often forget and google.
And many more. You can see all of them on our cheat sheets page.
Getting started with Maven
According to the docs, Maven is a “software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project’s build, reporting and documentation from a central piece of information.” However, most normal people refer to it as a build tool. Its job is to build the project, convert the source code into a binary artifact, package the resources into it, and if needed automatically download and use necessary dependencies.
The complexity of using Maven comes from the fact that it tries to do multiple things at the same time. First of all it needs to describe the project, its structure, submodules, the necessary build steps and so on. Then it needs a mechanism to provide the information about what other libraries are required for the project to build. Then it has to actually assemble the project, run the tests and perhaps even push it out to your artifact repository.
Creating a project
Let’s start with the first task Maven can accomplish for you. Often the projects get created by an IDE wizard, or by cloning a template from Github, or using a generator, like JHipster for the Spring Boot Angular projects. However, the template functionality is baked into Maven itself.
Maven templates are called archetypes. And one can use them to kickstart a working Maven project. For example if you execute the following command:
mvn archetype:generate -DgroupId=org.yourcompany.project -DartifactId=application
Maven will obtain a list of all available to it archetypes, ask you for some configuration, and generate a working project. If, for example you select the maven-archetype-quickstart archetype, you’ll get the following project structure.
You can also simplify your choice by providing a archetypeArtifactId property to pick the archetype in advance. For example
-DarchetypeArtifactId=maven-archetype-webapp will help you in creating a Java web app project. You can also package your project into an archetype for the future use with the following command:
Let’s talk about the main infrastructural components involved in Maven. Maven itself is a binary on your machine. In the internet there’s a central repository that stores and distributes all available artifacts, both the dependencies and the plugins for Maven. You can configure your own remote repositories, but in a nutshell, all that sits in the cloud.
For performance purposes and so you don’t download the internet every time you invoke Maven commands, Maven caches everything that it downloads in a local repository. Think of it as a cache, if something is not yet in the local repository, but is required to execute a command, Maven checks the remote repositories.
The local repository typically resides in the ~/.m2 directory. This directory also stores the Maven configuration in the form of the settings.xml file. You can use it to configure the remote repositories, credentials to access them, and so on.
Maven command execution is separated into phases. They form the lifecycle of the build.
clean— delete target directory
validate— validate, if the project is correct
compile— compile source code, classes stored in target/classes
test— run tests
package— take the compiled code and package it in its distributable format, e.g. JAR, WAR
verify— run any checks to verify the package is valid and meets quality criteria
install— install the package into the local repository
deploy— copies the final package to the remote repository
The instructions of what to execute during every phase are detailed in plugins. Maven is a declarative build system, which means that you say what your build should accomplish, but don’t say what exactly to execute. For example, you can configure the build to use a maven-compiler-plugin to compile the Java sources, but you don’t tell Maven when to run it.
All Maven configuration comes from the pom.xml files, which declare the model for the project. They contain the configuration of the build, declaration of the dependencies and which plugins to use.
Plugins functionality is divided into goals. A goal is a build step, if you like. The goals are registered to be executed during the phases of the build.
The goals perform the actual work, during the phases that come from the lifecycle of the project.
Here’s a list of the Maven plugins that come handy, but you don’t really use or configure them all the time in a healthy project, so every time you google about what they do.
Help plugin is used to get relative information about a project or the system. Think of it as the man pages of Maven. Here are a couple of commands that you can play with.
mvn help:describe describes the attributes of a plugin. You can get information about a plugin, a specific goal and so on.
mvn help:effective-pom displays the effective POM as an XML for the current build, with the active profiles factored in. This is really useful when something goes wrong with the build and you need to verify that what you think you configured in the pom files is what Maven thinks is configured there.
Dependency plugin — provides the capability to manipulate and inspect the dependencies.
mvn dependency:analyze analyzes the dependencies of this project, prints unused, outdated dependencies, and so on. For example if you run it on a fresh Maven project, you’ll probably see something like:
[WARNING] Unused declared dependencies found: [WARNING] junit:junit:jar:3.8.1:test
mvn dependency:tree will print a tree of dependencies in the project. It is extremely useful to see the transitive dependencies (the dependencies of your dependencies), figure out the version conflicts and see what exactly you depend upon.
Compiler plugin, not surprisingly, compiles your java code. It’s pretty self-explanatory, but this one you’ll configure again and again to comply with a modern Java versions. Set language level with the following configuration:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin>
Version plugin can be used to manage the versions of artifacts in a project’s POM. If you want to compare the versions of the dependencies between two projects, or make sure you don’t use any SNAPSHOT dependencies or update the versions to use the latest releases, you can do it programmatically with the version plugin.
Wrapper plugin is an easy way to ensure a user of your Maven build has everything that is necessary. It will generate a couple of shell scripts that can download a specific Maven version, so you’ll have reproducible builds. It is also useful to bake in some configuration, like the remote repositories declaration into the maven archive. Then the wrapper will ensure that you get the correct configuration out of the box.
There’s also a Spring Boot plugin which compiles your Spring Boot app and builds an executable fat jar. It comes with a more sensible configuration that the compiler plugin’s default. Pretty handy.
The last, but not least in our list is the Exec plugin. The exec plugin is used to execute Java commands as the part of your build process. If you need to achieve something for which you don’t have a plugin yet, you can substitute it with an invocation of the exec plugin.
Useful command line options
Besides understanding what Maven lifecycle is or what plugins are available, you can also remember a list of command line options that can simplify or speed up your work with Maven. Here is a list of a few that we think are relevant to almost anyone.
-DskipTests=true compiles the tests, but skips running them
-Dmaven.test.skip=true skips compiling the tests and does not run them.
-T is the option to specify number of parallel threads involved in the build. If your project can be built in parallel, for example the modules that do not depend on each other, this option, say -T 4 (to use 4 threads) can significantly speed up your build time. Also if you’re interested, we’ve looked into speeding up the Maven build before.
--resume-from makes Maven resume the build from the specified project, if one invocation of the Maven fails, you pretty much don’t want to repeat the work that succeeded, so you can start from where the build get interrupted.
--projects makes Maven build only specified modules and not the whole project. When you’re working in one module of the multi-module project, you know that your changes are localized there, so you’d likely want to avoid doing empty work by building everything else.
--also-make makes Maven figure out what modules out target depends on and build them too. Use it together with the
-pl, if you change something other modules depend on, you’d want to rebuild the modules using the code you changed.
--offline makes Maven work offline. It uses the local repository for everything, and doesn’t check the internet.
--debug enables the debug output. When things go wrong, Maven doesn’t always print the most useful error messages, enabling the debug can get you the needed hint to what went wrong.
--update-snapshots forces a check for updated dependencies from the remote repositories. When you depend on a project that is still in development, you’ll need to depend on the SNAPSHOT versions. And Maven will cache them in the local repository as usual. However, the problem with the snapshots is that one version identifier can mean different artifacts, when a new version of the snapshot is pushed to the repository. To make Maven ignore the local cache, use this option.
In this post, we looked at the some of the Maven vocabulary: repositories, dependencies, and so on, plugins that you can find useful, and some command line options to make your life with Maven easier. The best thing is that all that information is neatly organized on a single A4 page cheat sheet that you can print out and have handy for when you find yourself explaining how Maven works to a less experienced colleague once again. Or when you find yourself googling how to configure the compiler plugin to build a Java 8 plugin. Or just because it’s colorful and packs useful information.