paint-brush
Getting Started With Maven - A Beginner's Guide to Efficient Java Build Managementby@nipunaupeksha
561 reads
561 reads

Getting Started With Maven - A Beginner's Guide to Efficient Java Build Management

by Nipuna UpekshaNovember 29th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Although Maven is considered a build tool that can be used to build artifacts from the source code by many, it is actually a project management tool that offers the build tool capabilities as a subset of its capabilities.
featured image - Getting Started With Maven - A Beginner's Guide to Efficient Java Build Management
Nipuna Upeksha HackerNoon profile picture

If you are a Java developer, one of the most frequent words that you hear during your career is Maven. So, what is Maven, and what does it do? This article gives you a basic idea about what Maven is and the important concepts associated with it.

What Is Apache Maven?

Although Maven is considered a build tool that can be used to build artifacts from the source code by many, it is actually a project management tool that offers the build tool capabilities as a subset of its capabilities.


As indicated on the official site, https://maven.apache.org,


Apache 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.


Looking at Maven comprehensively,

  • This is a project management tool with a project object model.


  • Which has a set of standards.


  • Which has a project lifecycle.


  • A dependency management system.


  • A logic that executes plugin goals during various lifecycle phases.


Having the aforementioned features, Maven can give a simple project setup that uses the best practices, and with Maven, the projects can follow a consistent structure that is independent of the IDEs(Integrated Development Environments).


In addition, one of the most important usages of Maven is that it simplifies the declaration of project dependencies.


Focusing on the last point, when you build a traditional Java application, you include the dependencies by downloading them directly and including them in the project structure. If you have already done this you know how much of a laborious work that is.


In addition, to download those dependencies, you have to keep track of their versions for your project to work. But that is where Maven comes to help.


It helps you to keep track of your dependencies and their respective versions along with downloading them when they are needed to build the project.

Installing Apache Maven

Installing Apache Maven Directly

To install Maven, you need to have JDK (Java Development Kit) installed on your machine first. You can simply download the JDK binary file, and extract it or use an installer. After that, you can go to http://maven.apache.org, and click the Download link. You can either click the Download in the side panel or the Download, Install, Run Maven in the content section.


Home page of  http://maven.apache.org/


When you click that link, you will be redirected to the downloads page, where you will see the system requirements for Maven (Always try to download the latest version of Maven). The most important requirement here is the JDK version. Right now, the latest version is Maven 3.9.5 and the JDK version required for that is JDK 1.8.


Download Page


And when you scroll down, you will see the installation instructions and the downloadable files. Here, we can select the apache-maven-3.9.5-bin.tar.gz to download. After downloading the binary zip file, extract it, and set the JAVA_HOME and MAVEN_HOME paths in the environment settings.


The following steps show how to install Maven to a MacOS that already has JDK >= 1.8 installed.


  • Go to the Downloads directory in MacOS using the terminal. → cd ~/Downloads


  • Extract the apache-maven-3.9.5-bin.tar.gz zip file. → tar -xvf apache-maven-3.9.5.bin.tar.gz


  • Copy the extracted apache-maven-3.9.5 directory to /opt/ directory. → sudo mv ~/Downloads/apache-maven-3.9.5 /opt/


  • Open the ~/.zshrc or ~/.bash_profile in your Mac (Depending on the terminal you use) → open ~/.zshrc


  • Add the following two lines to the file. →

    MAVEN_HOME="/opt/apache-maven-3.9.5"
    export PATH="$MAVEN_HOME/bin:$PATH" 
    
  • Finally, source the ~/.zshrc or ~/.bash_profile file. → source ~/.zshrc


After installing the Apache Maven, you can check whether it is working by using either of the following two commands.

mvn -v
mvn -version


The instructions on how to install Maven on Windows and Linux machines are explained in the following articles.

Installing JDK and Maven With SDKMAN

SDKMAN is a tool which allows you to download and manage SDKs. Using this, you are able to switch between multiple versions of the same SDK. To install SDKMAN, go to https://sdkman.io/.


Here, I will explain how to install it on a MacOS machine, and you can find the details on how to install it on Windows or Linux at their official site.


  • Open the terminal, and run the following →curl -s “https://get.sdkman.io” | bash


  • Follow the on-screen instructions to wrap up the installation.


  • Type source “$HOME/.sdkman/bin/sdkman-init.sh


  • After that, open your ~/.zshrc or ~/.bash_profile to verify the following is included at the end of the file.

    export SDKMAN_DIR="$HOME/.sdkman"
    [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
    
  • After that, type the following to verify whether it is working. → sdk version

Install JDK With SDKMAN

To install JDK with SDKMAN, follow the below instructions.


  • Type sdk list java to list down all the Java versions that can be used with SDKMAN.


  • Then copy the identifier of the Java version you want (e.g., 8.0.372-amzn from Corretto).


  • Then type and run sdk install 8.0.372-amzn to install that version.


  • By default, the first Java version you will install will be your default Java version.


  • If you want another Java version installed, (e.g. 11.0.20-amzn) then type and run sdk install 11.0.20-amzn


  • To change the Java version from 8.0.372-amzn to 11.0.20-amzn , type sdk use java 11.0.20-amzn.


  • If you want to remove a Java version (e.g., 8.0.372-amzn), you can simply type and run sdk uninstall java 8.0.372-amzn

Install Maven With SDKMAN

Install Maven with SDKMAN is similar to how you installed the JDK with SDKMAN.

  • Type sdk list maven to list down all the Maven versions that are available.


  • Select the preferred Maven version, and type sdk install <mvn-version>to install it.


  • Similar to JDK uninstallation, you can remove an unwanted Maven version by typing and running sdk uninstall <mvn-version>.


After installing both Java and Maven, you can check whether they are working by typing the below commands.


java -version
mvn -v


Configurations and Repository

Once you start using Maven, you will notice that Maven has created local, user-specific configuration files and a local repository in the ~/.m2 directory. Inside that ~/.m2 there are,

  • ~/.m2/settings.xml → A file containing the user-specific configurations for authentication, repositories, and other information to customize the behavior of Maven.


  • ~/.m2/repository→ This directory contains the local Maven repository. When you download a dependency from a remote Maven repository, Maven stores a copy of that dependency in your local repository.


Remote Maven repositories have the dependencies uploaded by certain organizations, and the local repository is the ~/.m2/repository in our local machine.


For instance, if we need, org.apache.spark.spark-sql in our project, we can mention that to Maven, and it will download it from a remote repository like Maven Central(https://central.sonatype.com/) or MVN Repository(https://mvnrepository.com/) and put it inside the ~/.m2/repository directory.

The Project Object Model

The project Object Model or the POM is an essential file in Maven Development since it declares the project’s identity and structure. This is usually described in XML. The POM file contains four categories of description and configuration.


  1. General Project Information → This includes a project’s name, the URL for a project, the sponsoring organization, and a list of developers and contributors along with the license for a project.


  2. Build Settings → This section can be used to customize the default Maven build. It is possible to change the location of resources and tests, add new plugins, and add new plugin goals to the lifecycle using this section.


  3. Build Environment → The build environment has profiles that can be activated for use in different environments.


  4. POM Relationships → Usually, a project depends on other projects and inherits POM settings from its parent project. These relationships are defined in this section.

The Super POM

All the Maven project POM files extend the Super POM, which defines a set of defaults shared by all projects. This file comes as a part of the installation and can be found in,


  • maven-<version>-uber.jarfile in ${M2_HOME}/libfor Maven 2.


  • maven-model-builder-<version>.jarfile in ${M2_HOME}/lib for Maven 3.


  • And, if you have installed Maven via SDKMAN you can find it on ~/.sdkman/candidates/maven/<maven-version>/lib


Since all the Maven POMs inherit the defaults from the Super POM, if you want, you can,

  • Write a simple project that produces a JAR from a source in src/main/java
  • Want to run JUnit tests src/test/java
  • Want to build a project site using mvn site


To do those things, you don’t have to customize anything. You can use a simple POM file like the one shown below.


<project>
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.mavensample.project</groupId>
	<artifactId>simplest</artifactId>
	<version>1</version>
</project>


Since this is the simplest POM file you can have, it is normally known as the Simplest POM. If you want to create a simple program that belongs to the above criteria, you can type and run mvn package, and it will produce a JAR file in /target/simples-1.jar

The Effective POM

The Simplest POM brings us over to the concept called Effective POM. The Effective POM is made of a combination of super POM and parent POM files. It is notable that the default configurations can be overridden by the parent POM files and the current project’s POM when creating an effective POM.


To find a project’s Effective POM, you’ll need to run the effective-pom goal in Maven Help plugin using, mvn help:effective-pom


This will print out an XML document showing the merges between the aforementioned POM files. To use the mvn help:effective-pom, you need to have the Maven Help plugin help goal defined. To install the Maven Help plugin, you can use the below code, and include that in your POM file.

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-help-plugin</artifactId>
      <version>3.4.0</version> <!-- Use the latest version -->
    </plugin>
  </plugins>
</build>


POM Syntax

A Maven project’s version encodes a release version number that is used to group and order releases. A Maven version has the following parts.


  • Major version
  • Minor version
  • Incremental version
  • Qualifier


These parts correspond to the following format.

<major version>.<minor version>.<incremental version>-qualifier


For example, the version 1.2.3 has a major version of 1, a minor version of 2, and an incremental version of 3. The version `1` has a major version of 1 and no minor or incremental versions. The qualifier is used to capture the milestone builds like alpha and beta releases (1.2.3-beta-01 )


When using version build numbers like 1.1.2-beta-01 and 1.1.2-beta-10you should always use left padding (01,02, etc.) to ensure Maven build number issues.


Maven versions also can contain a string literal to signify that a project is currently under active development by adding the string SNAPSHOT. This string token will be expanded to a UTC(Coordinated Universal Time Value, e.g., 20230207-230803-1). (e.g., 1.0-SNAPSHOT1.0-20230207-230803-1)


As a default setting, Maven will not check the SNAPSHOT versions on remote repositories. To depend on SNAPSHOT releases, you have to explicitly enable the ability to download them using a repository or pluginRepository element in POM.


Also, POM can include references to properties preceded by a dollar sign($) and surrounded by two curly braces.

<project> 
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.mavensample.project</groupId> 
	<artifactId>project-x</artifactId> 
	<version>1.0-SNAPSHOT</version>
	<packaging>jar</packaging>  
	<build>
		<finalName>${project.groupId}-${project.artifactId}</finalName>
	</build>
</project>


If you put the above in your pom.xml file and run mvn help:effective-pom, you will see the output contains the line, <finalName>org.mavensample.project-project-x</finalName>


Maven provides three implicit variables that can be used to access environment variables, POM information, and Maven Settings.


  • env→This exposes the environment variables defined by your operating system or shell. For example, reference to ${env.JAVA_HOME} would be replaced by the ${JAVA_HOME} environment variable.


  • project→This exposes the POM. You can use the dot-notation path references here.


  • settings→This exposes the Maven settings information. You can use dot notation to refer to the values defined in thesettings.xml file. For example, ${settings.offline} would reference the value of the offline element in ~/.m2/settings.xml.


In addition to the above three, you can reference the system properties and the arbitrary properties set in the Maven POM or build profile.


  • Java System Properties → All the properties are accessible via getProperties() defined on java.lang.System.(e.g.,${user.name}, ${java.home})


  • Arbitrary properties → These can be set with the <properties></properties> element in the POM file or the settings.xml or can be loaded from external files.

    <properties>
      <org.mavensample.project.version>1.0</org.mavensample.project.version>
    </properties>
    


Project Dependencies

Maven can manage both internal and external dependencies. An external dependency for a Java project might be a library like Spring Framework or Log4j. An internal dependency for a Java project can be a web application depending on another project that contains the service class, model objects, or persistence logic.


When you define a dependency, you are using groupId, artifactId , version , and classifier to define a unique location of that dependency. This is also done when you are creating a new Maven project. These are called Maven Coordinates and they are integral when defining the dependencies.

Dependency Scope

There are five dependency scopes. These show what phases we are using the dependency.

  • compilecompile is the default scope; all the dependencies are compile scoped if a scope is not defined. compile dependencies are available in all classpaths, and they are packaged.


  • providedprovided dependencies are used when you expect JDK or a container to provide them. For example, if you are developing a web application, you need the Servlet API available on the compile classpath to compile a servlet, but you don’t need to include the Servlet API in the packaged WAR file. In this case, provided scope should be used. These dependencies are available on the compilation(not runtime) classpath. They are not transitive, nor they are packaged.


  • runtimeruntime dependencies are required to execute and test the system, but they are not required for compilation. For example, you may need a JDBC API JAR at compile time and JDBC driver implementation only at runtime.


  • test→ These dependencies are not required during the normal operation of an application and they are only needed during the test compilation and execution phases.


  • system→ This scope is similar to provided , but must specify the explicit path to the JAR on the local file system. If you declare the scope system , you must also provide the systemPath element. This is not a recommended scope to be used.

Optional Dependencies

When you need a dependency to compile a specific project but you don’t need it to be shown up as a transitive runtime dependency for the project that uses your specific project, you can use optional dependencies. You can use the <optional>true</optional> for that.


Usually, you don’t need to use optional dependencies. Instead of one large project with a series of optional dependencies, you should always separate the project into submodules and use the specific dependency in the specific project.

Dependency Version Ranges

In Maven, you don’t need to depend on a specific version of a dependency; you can specify a range of versions that would satisfy a given dependency.


  • (,)→ exclusive quantifiers (e.g., <version>(1.0,3.0)</version>).
  • [,]→ inclusive quantifiers (e.g., <version>[1.0,3.0]</version>).
  • Mixed → (e.g., <version>(1.0,3.0]</version>).

Transitive Dependencies

A transitive dependency is a dependency of a dependency. If project-a depends on project-b, which depends on project-c, then project-c is a transitive dependency of project-a. If project-a depends on project-d, then it is also considered a transitive dependency of project-a.


The best way to look at the transitive dependencies of a project is to type and run the following command.

mvn dependency:tree

Conflict Resolution

Sometimes, you need to exclude a transitive dependency, such as when you depend on a project that depends on another project, but you would like to either exclude the dependency altogether or replace the transitive dependency with another dependency that provides the same functionality. An example is shown below.

<dependency> 
	<groupId>org.mavensample.project</groupId> 
	<artifactId>project-x</artifactId>
	<version>1.0</version>
		<exclusions>
			<exclusion> 
				<groupId>org.mavensample.project</groupId> 
				<artifactId>project-y</artifactId>
			</exclusion>
		</exclusions>
</dependency>

Dependency Management

If you have a large set of projects that use the same set of dependencies, you can define a dependency management block in your muti-module project’s top-level POM. An example is shown below of how you can use the MySQL Java connector version 5.1.2 in the top-level POM.

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId> 
			<version>5.1.2</version>
		</dependency>
	... 
	<dependencies>
</dependencyManagement>


Then you can use them in a child project, without using the version metadata or without declaring it since it is getting inherited.

The Build Lifecycle

A build lifecycle is an organized sequence of phases that can be used to give orders or goals. Those goals can be used in the project to do multiple tasks. There are three standard lifecycles in Maven.

  • clean→ Running mvn clean invokes this and the Clean plugin’s clean goal (clean:clean) is bound to this.


  • default/build→This is the general model of a build process for a software application. It contains several important phases like generate-resources,test,compile,install,deploy


  • site→ This can be used to generate a site from the Maven project by running mvn site.

Build Profiles

Profiles allow the user to customize a particular build for a particular environment. In other words, profiles enable portability between different environments. An example is shown below.

<profile>
    <id>jdk16</id>
    <activation>
	    <activeByDefault>false</activeByDefault>
	    <jdk>1.6</jdk>
	    <property>
		    <name>!environment.type</name>
	    </property>
    </activation>
	<modules>
	    <module>simple-script</module>
    </modules>
</profile>


As you can notice activeByDefault sets the default profile to be activated, and <property></property> makes sure that the profile is activated when the environment.type value is not present.


To list all the active profiles, you can use the following command.

mvn help:active-profiles


Usually, a POM can have several profiles to activate different environments like the example given below.

<profiles>
	<profile>
		<id>development</id>
		<activation>
			<activeByDefault>true</activeByDefault>
			<property>
				<name>environment.type</name>
				<value>dev</value>
			</property>
		</activation> 
		<properties>
			<database.driverClassName>com.mysql.jdbc.Driver</database.driverClassName> 
			<database.url>jdbc:mysql://localhost:3306/app_dev</database.url> 
			<database.user>dev_user</database.user> 
			<database.password>deve_password</database.password>
		</properties>
	</profile>
	<profile>
		<id>production</id>
		<activation>
			<property>
				<name>environment.type</name>
				<value>prod</value>
			</property>
		</activation>
		<properties>
		  <database.driverClassName>com.mysql.jdbc.Driver</database.driverClassName> 
		  <database.url>jdbc:mysql://master01:3306,slave01:3306/app_prod</database.url> 
		  <database.user>prod_user</database.user>
		</properties>
	</profile>
</profiles>

In such cases, you can activate the dev profile by running mvn clean install -Denvironemtn.type=dev

Common Maven Commands

When using Maven it is good to know the common Maven commands that you will be using day to day. A few of the most important ones that I am using day to day are listed below.


  • Display Maven version →

    mvn -v

    mvn --version

  • Getting help→

    mvn -h

    mvn --help

  • To build a Maven artifact → mvn clean install

  • To build a Maven artifact without running tests →

    mvn clean install -Dmaven.test.skip=true

    mvn clean install -DskipTests

  • To run Maven with a profile (e.g., production profile) → mvn clean install -Pproduction

  • Profile activation with variables → mvn clean install -Denvironment.type=dev

  • Listing the active profiles → mvn help:active-profiles

  • To run Maven with debug enabled →

    mvn clean install -X

    mvn clean install --debug

  • To view the effective POM → mvn help:effective-pom

  • To view the dependency tree → mvn dependency:tree

  • To view execution error messages with full stack trace →

    mvn -e clean install

    mvn --errors clean install

  • To only show error messages →

    mvn -q clean install

    mvn --quiet clean install

  • Run Maven in offline mode → mvn -o clean install

  • Making a subset of projects → The projects that were mentioned will be built.

    mvn --projects <project_name_1>,<project_name_2> --also-make install

    mvn -pl <project_name_1>,<project_name_2> -am install

  • Making project dependents → The mentioned projects and their dependents will be built.

    mvn --projects <project_name_1>,<project_name_2> --also-make-dependents install

    mvn -pl <project_name_1>,<project_name_2> --amd install

  • Resuming a project → If you have built a few modules of a project and need to continue with other submodules, you can use this.

    mvn --resumt-from <project_name> install

    mvn -rf <project_name> install

  • Describe a Maven plugin →

    mvn help:describe -Dplugin=help -Dfull

    mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin

  • Describe the plugin with goals → mvn help:describe -Dcmd=compiler:compiler

  • More information on the available configuration parameters → mvn help:describe -Dcmd=compiler:compile -Ddetail


So, this is it! Check out my previous article on Maven on Medium if you want to know how to use Maven with Windows! (This is the original article, this article was based upon. 🙃) If you want to know all about Maven, check out Maven: The Complete Reference by SonaType. Most of the concepts I have described here are mentioned thoroughly in this book.


I have not mentioned about plugins in this article, since it is a heavy topic. But if you are interested in learning about Maven Plugins, check out the below link.


https://maven.apache.org/guides/plugin/guide-java-plugin-development.html


Cheers!