Maven is a build automation tool primarily used for Java projects, but with a little configuration, Maven can also be utilized to build and manage projects written in Kotlin, Groovy, Scala and other programming languages. Maven is so versatile, you can leverage it to automate tasks such as managing and downloading dependencies, putting additional jars on a classpath, compiling source code into bytecode, running tests, packaging compiled code into deployable artifacts such as JAR, WAR, and deploying these artifacts to an application server or repository.
At this time of writing, Maven is the most popular build automation tool for Java developers. Mastering Maven is a must for any aspiring Java developer.
In this blog, we’re going to explore Maven coordinates, repositories, Project Object Model (pom, effective-pom), dependencies (direct & transitive), Build Lifecycle, last but not least we’ll introduce the notion of multi-module projects.
2. Couple reasons to use Apache Maven
Why use Maven? Here are some reasons to consider using it:
Has roughly 70% of the build market for Java applications, so it has a hugely supportive community
All popular IDEs for the Java platform and beyond support Apache Maven with numerous features out of the box
Very common to utilize in large companies and Open Source Projects (ie String Framework, Spring Boot, etc)
You have Quick Project Setup (Maven brings conventions over configuration, thus reducing setup time)
Mature Dependency Management and Project Build Lifecycle
Robust plugin community; Beside standard Maven plugins, a number of projects provide their own plugins for Maven. For example, Spring-Boot has a Maven plugin that provides features and functionality in the build process
All of your dependencies required by the project can be brought up automatically just by reading the pom.xml file.
Last but not least continuous builds, integration, and testing can be easily handled by using maven.
3. Maven Fundamentals
3.1. Maven Coordinates
Maven Coordinates are used to identify artifacts. Together they identify an “address or location” in a Maven repository. Maven Coordinates are:
<groupId>org.example</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
groupId — This element indicates the unique identifier of the organization or group that created the project. Often the organization’s reverse domain is used.
artifactId — This element indicates the unique base name of the primary artifact being generated by this project. Typically the project name.
version — refers to a specific version of the project.
3.2. Maven Repositories
A repository in Maven holds build artifacts and dependencies of varying types. There two types of repositories:
Local — Repositories local to your machine. Stored in/.m2/
Remote — locations can be public (Maven community, JBoss, Oracle, Atlassian, Google Android, …) or private (hosted by companies for internal artifacts)
Now one important thing to note here with Maven repositories is when your project calls for a dependency, what’s gonna happen is: first maven gonna look inside the local repository (under /.m2/), and if it’s not found there Maven is gonna go out and look to external repositories depending on your configuration. By default, it’s gonna look at central, but if you’ve configured others, it’s gonna also check there. This diagram illustrates that process:
Now once that gets brought down to your local repository, if it is a release it’s gonna stay there permanently. If it’s a snapshot repository maven will check every day for the availability of a release repository in the remote repository.
3.3. Maven Project Object Model
Maven Project Object Model or pom for short is an XML document that describes a maven project. The document must comply with the maven-4.0.0.xsd. If you’re not familiar with XML, XSD is an XML Schema document and that schema defines the “rules” of what can be in the document, expected element, type of element for the XML document.
One thing to note is POMs can inherit properties from parent POM. Now because of inheritance one thing that’s very important is what’s called the “Effective POM” which is the merge between the Super POM and the POM from The Simplest POM. If you want to see the Effective POM for a maven project run this command:
mvn help:effective-pom
Or if you’re using an IDE like IntelliJ IDEA just right-click in pom.xml choose Maven | Show Effective POM
For example, running the command with this pom:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
Generates this effective pom:
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
</pluginRepository>
</pluginRepositories>
<build>
<sourceDirectory>/Users/hamzabelmellouki/Documents/Projects/demo/src/main/java</sourceDirectory>
<scriptSourceDirectory>/Users/hamzabelmellouki/Documents/Projects/demo/src/main/scripts</scriptSourceDirectory>
<testSourceDirectory>/Users/hamzabelmellouki/Documents/Projects/demo/src/test/java</testSourceDirectory>
<outputDirectory>/Users/hamzabelmellouki/Documents/Projects/demo/target/classes</outputDirectory>
<testOutputDirectory>/Users/hamzabelmellouki/Documents/Projects/demo/target/test-classes</testOutputDirectory>
<resources>
<resource>
<directory>/Users/hamzabelmellouki/Documents/Projects/demo/src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>/Users/hamzabelmellouki/Documents/Projects/demo/src/test/resources</directory>
</testResource>
</testResources>
<directory>/Users/hamzabelmellouki/Documents/Projects/demo/target</directory>
<finalName>demo-1.0-SNAPSHOT</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-5</version>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
</plugin>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.3</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>default-clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>default-testResources</id>
<phase>process-test-resources</phase>
<goals>
<goal>testResources</goal>
</goals>
</execution>
<execution>
<id>default-resources</id>
<phase>process-resources</phase>
<goals>
<goal>resources</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<executions>
<execution>
<id>default-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<executions>
<execution>
<id>default-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>default-install</id>
<phase>install</phase>
<goals>
<goal>install</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>default-deploy</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.3</version>
<executions>
<execution>
<id>default-site</id>
<phase>site</phase>
<goals>
<goal>site</goal>
</goals>
<configuration>
<outputDirectory>/Users/hamzabelmellouki/Documents/Projects/demo/target/site</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</execution>
<execution>
<id>default-deploy</id>
<phase>site-deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
<configuration>
<outputDirectory>/Users/hamzabelmellouki/Documents/Projects/demo/target/site</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</execution>
</executions>
<configuration>
<outputDirectory>/Users/hamzabelmellouki/Documents/Projects/demo/target/site</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<outputDirectory>/Users/hamzabelmellouki/Documents/Projects/demo/target/site</outputDirectory>
</reporting>
</project>
Don’t worry about all these XML elements. we’ll cover some of them in this blog and we’ll cover the others in the upcoming blogs. But what I want you to understand is that every pom inherits from a super pom even if it is not specified explicitly.
3.4. Maven Dependencies
A dependency is an artifact which your Maven project depends upon. This typically can be a jar or a pom. Dependencies can be categorized into two categories: Direct dependencies (dependency that you specify in your pom) and Transitive Dependencies(dependency of an artifact that your project depends on).
Although transitive dependencies can implicitly include desired dependencies, it is a best practice to explicitly specify the dependencies you are directly using in your own source code. This best practice proves its value especially when the dependencies of your project change their dependencies.
for example, if you want to add a JUnit Jupiter dependency to your project classpath you’d add this to section:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
Dependency Mediation is leveraged when Maven wants to decide what version of an artifact will be chosen when multiple versions are encountered as dependencies. The documentation has very specific phrasing on that:
Maven picks the “nearest definition”. That is, it uses the version of the closest dependency to your project in the tree of dependencies. You can always guarantee a version by declaring it explicitly in your project’s POM. Note that if two dependency versions are at the same depth in the dependency tree, the first declaration wins.
Dependency management is one of the most powerful features of Maven, it allows developers to specify the versions of the artifacts to be used. This feature is very handy if you have a multi-module project consisting of many submodules: It helps you avoid specifying dependency versions in every sub-module by encapsulating all the dependency versions in the parent module. Note that the dependencies defined in the dependencyManagement
section are not included at all, If you want to include them in your project you should put them in dependencies
section.
Dependency Scope is very important as far as transitive dependencies are concerned, It can affect the project’s classpath by limiting the transitivity of a dependency. There are 6 scopes available:
compile — Default. Available on all classpaths(compile classpath — runtime classpath — test classpath) of a project. Also, propagated downstream projects.
Provided — Like Compile, but expected to be provided by JDK or a container at runtime.
Runtime — not required for compilation, but needed for runtime. They’ll be available to runtime and test classpaths, not compile.
Test — Only available on test classpath so when our tests are running they’ll be available to the test classpath, and these are not transitive.
System — Similar to provided, but JAR is added to the system explicitly. The dependency is always available and is not looked up in a repository. You should note that the system scope is actually deprecated and should be avoided, consider other alternatives.
Import — When you specify import scope what happens is that all the dependencies defined in the
dependencyManagement
section of the specified dependency will be included in thedependencyManagement
section of this pom. You can then reference these dependencies in thedependency
section of your POM (and all of its child POMs) without having to include aversion
etc. This scope is only supported on a dependency of type pom in thedependencyManagement
section.
4. Maven Build Lifecycle
Maven is based on the concept of build lifecycles. A lifecycle is a pre-defined group of build steps called phases. Each phase can be bound to one or more plugin goals (keep in mind that all work done in Maven is done by plugins!). So basically the lifecycle provides the framework to call plugin goals in a specific sequence. This is really the gist of how Maven is working, so the entire framework of Maven is designed to do these builds using plugins.
Out of the box, Maven’s gonna have three predefined lifecycles:
Clean - Does a clean of the project, remove all build artifacts from the working directory and this is by default defined with plugin binding. The diagram below illustrates the clean lifecycle. As you can see this lifecycle has three phases, those phases are run sequentially. By default, there is no plugin bound to pre-clean or to post-clean phases. However, for the clean phase, we have a plugin bound called maven-clean-plugin and the goal is clean.
Default - Does the build and deployment of your project, so it’s gonna do the compiling, testing, integration testing, packaging, and deploying up to a deployment target (if it is defined). Default build lifecycle defines phases but bindings are gonna be defined for each packaging type whether it’s a jar, war, ear or pom, depending on the packaging type that we’ve defined for the pom, that’s what’s going to drive the plugin bindings. There are about 22 phases in the Default lifecycle, but these are the important ones:
*Validate *- Verify project is correct (standard directory structure, proper use of XML in pom, …)
Compile - Compile the code down to bytecode
Test - Test compiled code
Package - Package compiled files to packaging type
Verify - Run Integration Tests
Install - Install to local Maven Repository
Deploy - Deploy to shared Maven repository
Site - Create a website for your project. It is defined with plugin bindings. If you take a look at Maven websites they all are built using the Maven site lifecycle.
The key takeaway here is to understand that we do have three standard lifecycles within Maven and those lifecycles have multiple phases in it, and in those multiple phases, we can associate them with goals from a variety of different Maven plugins to do work inside of our build.
5. Multi-Module Builds
In a multi-module project you’ll use a parent pom.xml for defining general settings for all modules and then in each module there will only be specific settings. It is important to remember each module is effectively a Maven project, so you can run build lifecycle phases against that module.
5.1 The Reactor
To process projects with multiple modules Maven leverages what’s called The Reactor, the Reactor collects all the available modules to build, determines the build order of the modules, and then runs the selected build lifecycle against each module in order.
Word of caution: Don’t overuse modules, having multiple modules will slow down your build.
6. Conclusion
This blog was a quick overview of Maven, we talked about some of the most commonly utilized features in Maven. In the next blog, we’ll talk about Maven profiles. Stay tuned!
Let us know what you think in the comments below and don’t forget to share!
Top comments (0)