# Maven 101 - Basics

# Introduction

`Maven` is used to manage and build any Java-based project. It provides an opinionated way of the project structure, managing dependencies, building projects and deploying the artifacts.

# Project Object Model (POM)

`POM` stands for `Project Object Model`. This is the core of `Maven` and it is declared using `XML`.

Let's take a look at a sample `pom.xml`.

```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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<!-- Required -->
	<modelVersion>4.0.0</modelVersion>

	<!-- This 3 properties also known as Coordinates -->
	<groupId>com.bwgjoseph</groupId>
	<artifactId>maven-101-basic</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<!-- Optional -->
	<name>maven-101-basic</name>
	<description>Maven 101 Basics</description>

</project>
```

It contains the following key components:

```markdown
- modelVersion
- Coordinates
  - groupId, artifactId, version
- name
- description
```

`pom` file always exists at the root of the project directory.

```bash
root/
├── .mvn
├── src/
│   ├── main
│   └── test
├── mvnw
├── mvnw.cmd
└── pom.xml
```

## Simple POM

The above is what is normally referred to as the `Simple POM`.

`modelVersion` and `Coordinates` are the *two minimum declarations* required for a `pom` file to work. And it inherits all of the `Super POM` declarations by default.

## Super POM

Maven comes with `Super POM` as the default POM which defines all the default configurations of a project. As mentioned above, every project POM extends Super POM, and thus, inherits all the configuration.

A sample of Super POM can be found [here](https://maven.apache.org/ref/3.6.3/maven-model-builder/super-pom.html). It contains the following

```markdown
- modelVersion
- repositories
- pluginRepositories
- build
- reporting
- profile
```

## Effective POM

This is the final actual POM after merging the values with Super POM.

We can see the final or effective POM by running the command below

```bash
./mvnw help:effective-pom
```

The output is pretty long so I will not be attaching the logs here, but if you do run it. You will notice that it is a combination of Super and Simple POM.

> This is useful for debugging when you want to know what exactly forms up your POM, especially in a fairly complex project.

# Goal and Phase

I believe the best place to understand it is through official [Maven Docs](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html). So make sure to run through those. Here, I will add some additional notes that aid me in my understanding.

## Lifecycle

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693730273554/7261c0f6-fd7a-4775-8d4e-0358c7797aea.png align="center")

This is a bird's eye view of the `Maven Lifecycle`. To see the complete lifecycle view, refer to the [complete lifecycle](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#lifecycle-reference).

## Phase

At each lifecycle, it comes with a whole set of the phases that it runs. For example, for `clean`, it has `pre-clean, clean and post-clean`.

What's important to note is that `build lifecycle` needs to run on its own, but `default lifecycle` should run based on what you want to achieve as it will be (and I quote) "executed in the order given up to the point of the one specified".

> ./mvnw \[phase\]

For example, if I want to ensure my project can compile, I can either run `./mvnw clean compile` or `./mvnw compile`.

Running `./mvnw compile` will not trigger `clean phase` unlike running `compile` which will ensure `validate phase` is run first.

```bash
./mvnw compile

❌ clean
✔ validate > compile
❌ clean > validate > compile

./mvnw clean compile

✔ clean
✔ validate > compile
❌ clean > validate > compile
```

### What happens in each phase

* **validate**: validate the project
    
* **compile**: compile source code
    
* **test**: test the compiled source code
    
* **package**: take the compiled source code and package it into JAR
    
* **verify**: verify package is valid
    
* **install**: install the package into the local repository
    
* **deploy**: deploy the package into the remote repository
    

## Goal

Up to this point, I have only talked about phase. What about `goal`? It is important to note that for each `phase`, there will usually be at least one `goal` (tasks) that is attached which is defined by `Maven plugin`.

> Refer to [goal-binding](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#built-in-lifecycle-bindings) to know each phase is bound by which goal

Remember when I mentioned about `phase` where running one will automatically invoke the previous phases? If you run a particular goal, it will not invoke any previous phase.

> ./mvnw \[plugin\]:\[goal\]

What it means is that if I run `./mvnw package`, it will run the following phase in order

```bash
validate > compile > test > package
```

But if I run `./mvnw jar:jar`, it will execute only the `jar` goal and not anything before that. So if you don't have necessary resources (i.e./target/classes, etc.), it will throw a warning

```bash
> ./mvnw jar:jar
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.bwgjoseph:maven-101-basic >--------------------
[INFO] Building maven-101-basic 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- jar:3.3.0:jar (default-cli) @ maven-101-basic ---
[WARNING] JAR will be empty - no content was marked for inclusion!
[INFO] Building jar: Z:\Development\workspace\github\bwgjoseph\tutorials\maven-101-basic\target\maven-101-basic-0.0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.682 s
[INFO] Finished at: 2023-09-03T16:05:46+08:00
[INFO] ------------------------------------------------------------------------
```

# Plugin

Maven relies on `plugin` to perform any type of work. Take a look [here](https://maven.apache.org/plugins/index.html) for the list of plugins provided by Maven.

Each plugin provides at least 1 goal to it and has no upper-bound limit (AFAIK).

To understand what goals are available, it is usually described in the various [help plugin docs](https://maven.apache.org/plugins/maven-clean-plugin/plugin-info.html), and [compiler plugin docs](https://maven.apache.org/plugins/maven-compiler-plugin/plugin-info.html).

One could also use the `help plugin` to list down in the console.

```bash
# using groupId with details
./mvnw help:describe -DgroupId="org.apache.maven.plugins" -DartifactId=maven-clean-plugin -Ddetail=true
# using groupId with without details
./mvnw help:describe -DgroupId="org.apache.maven.plugins" -DartifactId=maven-clean-plugin
# using plugin with version and details
./mvnw help:describe -Dplugin="org.apache.maven.plugins:maven-clean-plugin:3.3.1" -Ddetail=true
# using plugin without version and details
./mvnw help:describe -Dplugin="org.apache.maven.plugins:maven-clean-plugin"
```

## Binding plugin goal to phase

If you want to bind a plugin goal to a particular phase, you can do it via the `execution` tag even if the plugin has already bound to some phase.

Let's use [spotless-maven](https://github.com/diffplug/spotless/tree/main/plugin-maven#binding-to-maven-phase) as an example.

```xml
<plugin>
  <groupId>com.diffplug.spotless</groupId>
  <artifactId>spotless-maven-plugin</artifactId>
  <version>${spotless.version}</version>
  <configuration>
    <!-- omitted -->
  </configuration>
</plugin>
```

As mentioned in the docs, a `spotless:check is bound to verify the maven phase`, and it means that when running `./mvnw verify` it will trigger `spotless:check` goal. If it was not bounded by the plugin, I would have to write something like this

```xml
<plugin>
  <groupId>com.diffplug.spotless</groupId>
  <artifactId>spotless-maven-plugin</artifactId>
  <version>${spotless.version}</version>
  <configuration>
    <!-- omitted -->
  </configuration>
  <executions>
	<execution>
	  <phase>verify</phase>
	  <goals>
	    <goal>check</goal>
	  </goals>
	</execution>
  </executions>
</plugin>
```

And if I want to bound to other phases like `compile`

```xml
<plugin>
  <groupId>com.diffplug.spotless</groupId>
  <artifactId>spotless-maven-plugin</artifactId>
  <version>${spotless.version}</version>
  <configuration>
    <!-- omitted -->
  </configuration>
  <executions>
	<execution>
	  <id>spotless-compile-check</id>
	  <phase>compile</phase>
	  <goals>
	    <goal>check</goal>
	  </goals>
	</execution>
  </executions>
</plugin>
```

Optionally, I can also give it a unique id which is used to identify (and show) in the console.

```bash
> ./mvnw compile
// omitted
[INFO] --- spotless:2.39.0:check (spotless-compile-check) @ maven-101-basic ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.429 s
[INFO] Finished at: 2023-09-03T16:13:55+08:00
[INFO] ------------------------------------------------------------------------
```

# Dependencies

One of the most important parts of using build tools like `Maven` is to allow it to manage the dependencies of the project. Similar to how other build tools such as `npm` do it as well.

One would specify the dependencies (and version) the project requires, and `Maven` will download and manage it according to how you defined it.

Below is an example of adding `Lombok` dependency on the project.

```xml
<dependencies>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.28</version>
		<scope>provided</scope>
	</dependency>
</dependencies>
```

There is a lot more to this such as

* classifier
    
* optional
    
* version requirement
    
* exclusion
    

But I will not be covering those. Refer to [here](https://maven.apache.org/pom.html#dependencies) if you are interested to know more. In most cases, all those are not required (including scope), only `groupId, artifactId, and version` are required.

# Model Interpolation

Maven provides a way to replace/define value via `${...}` syntax. Some of the more common ones are

```xml
${project.version}
${project.basedir}
${user.home}
${java.home}
${env.PATH}
```

This allows us to define it like this

```xml
<groupId>com.bwgjoseph</groupId>
<artifactId>maven-101-basic</artifactId>
<version>${project.version}</version>
```

## Model Properties

If I define it under `<properties>` tag in the `pom.xml`, I can use it. This is especially useful and commonly used for defining dependency/plugin versions.

```xml
<properties>
	<java.version>17</java.version>
	<spotless.version>1.2.3</spotless.version>
</properties>

<plugin>
  <groupId>com.diffplug.spotless</groupId>
  <artifactId>spotless-maven-plugin</artifactId>
  <version>${spotless.version}</version>
</plugin>
```

When working with `Spring Boot` project, it is also quite common for us to override the dependency version in `<properties>` tag like

```xml
<properties>
	<java.version>17</java.version>
    <!-- Override dependency version -->
	<lombok.version>1.18.24<lombok.version>
</properties>
```

Since `Lombok` dependency is declared and managed via `spring-boot-starter-parent`, there is no need to re-declare the dependencies in our `pom.xml` except to declare to the version.

# Maven Wrapper

Traditionally, to run `mvn` command, it is required to download `Maven` binary, extract, define environment variable, etc. This makes the developer experience less ideal, and more involved.

> `Maven wrapper` was originally a 3rd party project until quite recently that it was officially released as part of `Apache Maven Project`

Using `Maven Wrapper` provides an easy way to build any Maven project without having to pre-install `Maven` on the machine. It has a few key components.

## Maven Wrapper Jar

`Maven Wrapper Jar` is responsible for downloading (then installing) of `Maven` binary, specified in `/.mvn/wrapper/maven-wrapper.properties` under `distributionUrl`. And to invoke the targetted maven distribution whenever a command is triggered.

The maven binary will be downloaded, and extracted to `%USERPROFILE%/.m2/wrapper/dists` (Windows) or `~/.m2/wrapper/dists` (Linux)

## Maven Wrapper Distribution

`Maven Wrapper Distribution` provides

* `mvnw`
    
* `mvnw.cmd`
    
* `.mvn/wrapper/maven-wrapper.properties`
    

When invoking `mvnw[.cmd]`, its job is to trigger/download `maven-wrapper` depending on the argument passed in. However, the default is to use the wrapper in `.mvn/wrapper/maven-wrapper.jar` which is why in projects that have these setup, you only run commands like `./mvnw clean install`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693673639763/c5ecf9cd-bd4a-47d2-9dd8-f932c94a6676.png align="center")

## Using Maven Wrapper Config

If you are using `Maven Wrapper`, then you can specify and configure the following within `.mvn` directory

* `extensions.xml`
    
* `maven.config`
    
* `jvm.config`
    

This means that all these configurations are applicable per project basis. What's interesting is `maven.config` where you can pass in just about any maven configuration and it will be "injected" to any command you run.

The structure would be such

```bash
root/
├── .mvn/
│   ├── wrapper/
│   │   ├── maven-wrapper.jar
│   │   └── maven-wrapper.properties
│   ├── maven.config
│   ├── settings.xml
│   └── toolchains.xml
└── pom.xml
```

If you have custom settings per project, define them within `settings.xml`

```xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
    <servers>
        <server>
            <id>internal-artifactory</id>
            <username>${env.ARTIFACTORY_USER}</username>
            <password>${env.ARTIFACTORY_PASSWORD}</password>
        </server>
    </servers>
</settings>
```

And in `maven.config`, we specify the argument

```xml
--settings="./.mvn/settings.xml"
```

> Remember that since Maven 3.9.0, each argument starts in a new line

So when you run any `maven` command within the project through the wrapper, it will always append `--settings="./.mvn/settings.xml"` as part of the command

# Others

## Repositories

Define additional repositories to search for artifacts. The default repository is [repo.maven](https://repo.maven.apache.org/maven2/).

What happens is that when you run any maven command, and it looks for dependency, it will first search locally in `%USERPROFILE%\.m2` directory. If it cannot find, it will then attempt to locate it remotely via the default repository, or the additional repositories the project defines.

```xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  ...
  <repositories>
    <repository>
      <releases>
        <enabled>false</enabled>
        <updatePolicy>always</updatePolicy>
        <checksumPolicy>warn</checksumPolicy>
      </releases>
      <snapshots>
        <enabled>true</enabled>
        <updatePolicy>never</updatePolicy>
        <checksumPolicy>fail</checksumPolicy>
      </snapshots>
      <name>Nexus Snapshots</name>
      <id>snapshots-repo</id>
      <url>https://oss.sonatype.org/content/repositories/snapshots</url>
      <layout>default</layout>
    </repository>
  </repositories>
  <pluginRepositories>
    ...
  </pluginRepositories>
  ...
</project>
```

> Sample pom copied from [Maven](https://maven.apache.org/pom.html#repositories)

In most cases, you don't have to specify any, but some organization does have their internal repository hosted using `jFrog` or `Nexus` for various reasons.

## Distribution Management

This is where we define where to distribute the project artifact during release when it is deployed.

```xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  ...
  <distributionManagement>
    <repository>
      <uniqueVersion>false</uniqueVersion>
      <id>corp1</id>
      <name>Corporate Repository</name>
      <url>scp://repo/maven2</url>
      <layout>default</layout>
    </repository>
    <snapshotRepository>
      <uniqueVersion>true</uniqueVersion>
      <id>propSnap</id>
      <name>Propellors Snapshots</name>
      <url>sftp://propellers.net/maven</url>
      <layout>legacy</layout>
    </snapshotRepository>
    ...
  </distributionManagement>
  ...
</project>
```

Sample pom copied from [Maven](https://maven.apache.org/pom.html#repository)

## Settings

In other words, the `configuration` file for `Maven`. There are 3 types of settings

* Global: `${maven.home}/conf/settings.xml`
    
* User: `${user.home}/.m2/settings.xml`
    
* Project: `${project.basedir}/.mvn/settings.xml`
    

The first two (Global and User) are the official and default ones. The last (Project) is what can be done if you are using `Maven Wrapper` (as mentioned above).

## Toolchains

The whole idea of using `Toolchains` are to allow a project to be built independently from the one Maven is relying on.

It is recommended to keep it in `.m2` directory but I prefer to place it into `.mvn` as part of the repository. This way, it removes yet another step for the developer to copy and paste. While I feel that it might be good for the project, but not quite applicable for the general public usage as a whole as every machine configuration pointing to the various JDK paths will be different, and the developer could be working with pointing to a different JDK version in `JAVA_HOME`

```xml
<?xml version="1.0" encoding="UTF-8"?>
<toolchains>
  <!-- JDK toolchains -->
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>17</version>
    </provides>
    <configuration>
      <jdkHome>${env.JAVA_HOME}</jdkHome>
    </configuration>
  </toolchain>
</toolchains>
```

## Profile

Using a profile is a way to pre-define some settings/configuration so it can be activated using `-P` command

```bash
./mvnw clean package -P default
```

Sometimes, the command can get a little bit long, or it is not used frequently, hence, easy to forget the actual syntax. Using `Profile` might be a good way to overcome this issue.

```xml
<profiles>
	<profile>
		<id>skip-test</id>
		<properties>
			<skipTests>true</skipTests>
		</properties>
	</profile>
</profiles>
```

The above defined a Profile with `skip-test` as the id, and denotes that test should be skipped when running this Profile.

So, rather than running the following

```bash
./mvnw package -DskipTests
```

We run the following as a replacement

```bash
./mvnw package -P skip-test
```

Granted, this example doesn't showcase the need to have a Profile, but if you have a more complicated setup, this is where it shines, or if you need to run different settings across different environments.

# Conclusion

I hope this is a good enough introduction to `Maven` and I will follow up with another blog post that covers topics around multi-modules such as the different types of POM (i.e. Aggregate/Parent), using `dependencyManagement, pluginManagement`.

Personally, my biggest takeaway from this is on `Binding plugin goal to Phase` where I finally got my "that's how it works" moment as I have always been confused about how it exactly works.

Let me know if any part is confusing!

# Source Code

As usual, the full source code is available on [**GitHub**](https://github.com/bwgjoseph/tutorials/tree/main/maven-101-basics)

# References

* [https://stackoverflow.com/questions/2619598/differences-between-dependencymanagement-and-dependencies-in-maven](https://stackoverflow.com/questions/2619598/differences-between-dependencymanagement-and-dependencies-in-maven)
    
* [https://stackoverflow.com/questions/38723833/what-is-the-purpose-of-mvnw-and-mvnw-cmd-files](https://stackoverflow.com/questions/38723833/what-is-the-purpose-of-mvnw-and-mvnw-cmd-files)
    
* [https://stackoverflow.com/questions/16205778/what-are-maven-goals-and-phases-and-what-is-their-difference](https://stackoverflow.com/questions/16205778/what-are-maven-goals-and-phases-and-what-is-their-difference)
    
* [https://tomgregory.com/maven-phases-vs-goals/](https://tomgregory.com/maven-phases-vs-goals/)
    
* [https://books.sonatype.com/mvnref-book/reference/lifecycle-sect-structure.html](https://books.sonatype.com/mvnref-book/reference/lifecycle-sect-structure.html)
    
* [https://maven.apache.org/ref/3.9.3/maven-model-builder/#Model\_Interpolation](https://maven.apache.org/ref/3.9.3/maven-model-builder/#Model_Interpolation)
    
* [https://maven.apache.org/configure.html](https://maven.apache.org/configure.html)
