Do you want to fully understand the difference between Maven phases and goals, and how to execute each of them? OK, let’s start at the low-level and work our way upwards.
At its core Maven is a Java build tool which takes your project from some source files and resources to a shiny jar file ready to run.
The way Maven achieves this, just like anything worthwhile doing in life, is by executing some code.
Let’s think about all the individual build tasks needed to generate a jar file:
-
compiling classes
-
running tests
-
packaging everything up
There’s a juicy snippet of Maven code to do each of these!
OK, that’s not exactly the technical term. The proper name for each code snippet is a goal.
What are Maven goals?
A goal is actually just a Java method which does some specific task within your build.
Maven by default bundles many of these goal methods for you to use in your build. Here are some well known ones that you might encounter.
You can browse the source code for any of these goals over on GitHub. Here’s how the code for generating a jar file looks:
/\*\*
\* Generates the JAR.
\* @throws MojoExecutionException in case of an error.
\*/
public void execute()
throws MojoExecutionException
{
if ( useDefaultManifestFile )
{
throw new MojoExecutionException( "You are using 'useDefaultManifestFile' which has been removed"
+ " from the maven-jar-plugin. "
+ "Please see the >>Major Version Upgrade to version 3.0.0<< on the plugin site." );
}
if ( skipIfEmpty && ( !getClassesDirectory().exists() || getClassesDirectory().list().length < 1 ) )
{
getLog().info( "Skipping packaging of the " + getType() );
}
else
{
File jarFile = createArchive();
...
A goal method is always named execute and has a void return type.
Now that you’ve got the jist of goals, let’s go slightly higher level to understand how goals are made accessible to a Maven build.
Maven plugins
A goal method is contained within a class called a Mojo, which stands for Maven plain Old Java Object. Yeah, Maven was written a long time ago before people knew how to name things properly!
Anyway, each Mojo class has a single execute method and represents one goal.
For a goal to be made available to a Maven build, the corresponding Mojo has to be packaged inside what’s called a Maven plugin. A plugin is a jar file packaged up in a way that makes the goals contained inside available to Maven builds. And a plugin can contain one or more goals.
Going back to our jar goal, can you guess what the name of the plugin that contains it is called? Yep, it’s the jar plugin! 😲
For many plugins you can query its goals on the command line with mvn <plugin-name>:help
.
Here’s the result of running mvn jar:help
.
So the jar plugin has 5 goals in total, including the jar goal itself.
Here are the well-known goals we saw earlier, and the corresponding plugins that contain them. All of these plugins are bundled by default with Maven.
Of course, you can use 3rd party plugins which provide other useful behaviour also using goals. We’ll look at one of them later.
Executing a plugin’s goal
You can actually execute a goal on the command line using mvn <plugin name>:<goal name>
.
When we do that in this example Maven project using mvn jar:jar
(the jar plugin’s jar goal), we get a warning saying the jar will be empty.
But this application we’re running this command against does have code, it’s just that it hasn’t been compiled yet! Yep, we tried to package nothing into a jar file. You’ll see how to fix that shortly.
Maven phases
Going one level higher, Maven offers a neat way to group multiple goals from multiple plugins. This group of goals is called a phase.
A phase can contain a single goal, multiple goals, or no goals at all.
Here are the corresponding phases for the well-known goals and plugins we saw earlier.
For example, the jar goal is part of the package phase. Or in Maven terminology, the jar goal is bound to the package phase.
Likewise, the install goal is bound to the install phase.
Maybe you’re wondering what’s the point of having this additional grouping of goals in phases?
Well the answer is that the Maven team wanted to make the system as configurable as possible. But before I explain that fully, we need to go all the way up to the highest level concept which is the build lifecycle.
Maven build lifecycles
A build lifecycle is simply an ordered sequence of phases. The idea is that the lifecycle contains everything required for a full build.
The default Maven lifecycle contains 23 phases, covering everything from validation and initialization, to installation and deployment. And remember, each of these phases corresponds to zero, one, or more goals contained within plugins.
If this sounds a bit overwhelming, don’t worry because you only need to know about a handful of phases. These are the most important ones which you might interact with, so we can ignore the others:
-
compile
-
test
-
package
-
verify
-
install
-
deploy
These 6 phases are part of the default Maven lifecycle. The lifecycle orders the phases, from compile to deploy.
When you pass a phase to the Maven command, first all previous phases in the lifecycle get executed, followed by the phase you specified.
As well as the default lifecycle, there are 2 others:
-
clean is invoked whenever you run
mvn clean
-
site is used to generate project documentation
But let’s concentrate on the default lifecycle. This lifecycle means that if we want to create a jar file, we can run the package phase and the code will first automatically get compiled and tested.
That should fix the warning from earlier saying that our jar file was empty. So, we’ll pass the package phase to the Maven command with mvn package
.
Inspecting the generated jar file shows it now contains the compiled class. Awesome!
So what happened? Well, Maven executed 3 phases:
-
compile phase and the bound compile goal from the compiler plugin
-
test phase and the bound test goal from the surefire plugin
-
package phase and the bound jar goal from the jar plugin
install phase & goal
Let’s try another phase of the default lifecycle, install, which installs the jar file to our local Maven repository.
First I’ll clean the project by passing the clean phase to the Maven command with mvn clean
.
Remember that this phase is part of a different lifecycle, the clean lifecycle, so none of the phases from the default lifecycle get executed.
Now let’s run the install goal directly. It’s part of the install plugin, so we run mvn install:install
. We get an error because no jar file exists to install to the local Maven repository.
Any ideas how to fix that? 🤔
Yep, instead of running the install goal, we need to run the install phase which will include all previous phases in the default lifecycle. When we do that, our code is compiled, tested, packaged, then installed successfully to the local Maven repository.
Why some phases don’t have a goal
Not all phases in the default Maven lifecycle contain a goal. For example, here the verify phase has no arrow pointing to a goal.
This means that when this phase is executed it doesn’t do anything.
Maven provides such phases for convenience, so that plugins you might want to add to your build can bind their goals to them.
For example, you can apply the failsafe plugin in your pom.xml file to enable integration testing.
This plugin has a verify goal that it automatically binds to the verify phase to check the results of the integration tests.
Final thoughts
Hopefully now you can see why goals and phases exist.
I recommend always passing a phase to the Maven command and not a goal.
Although goals and phases provide some level of configurability in Maven, I find the Gradle build tool much easier to customise. From my testing it’s much faster too. To learn about the benefits of switching to Gradle, check this Maven vs. Gradle in-depth comparison.
Stop reading Gradle articles like these
This article helps you fix a specific problem, but it doesn't teach you the Gradle fundamentals you need to actually help your team succeed.
Instead, follow a step-by-step process that makes getting started with Gradle easy.