Maven

Summary

Notes on the Maven build system.

Modes

Daemon

This build mode relies on the Maven daemon, itself inspired by the Gradle daemon:

The Maven Daemon process does not reload classes unless they are changed.

The daemon is called with mvnd instead of mvn, for instance

$ mvnd clean install

The daemon uses multiple threads by default, with $N_{cores}-1$.

Artifacts

Über JARs

There are many ways to build über JARs, but we will talk about two, maven-assembly-plugin and maven-shade-plugin.

Assembly

The maven-assembly-plugin 1 adds all dependencies inside the final fat JAR and can be used by adding the following to your pom.xml:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-assembly-plugin</artifactId>
  <configuration>
    <descriptorRefs>
      <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
    <archive>
      <manifest>
        <mainClass>{your.package.main.class}</mainClass>
      </manifest>
    </archive>
  </configuration>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>single</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Maven plugins should be specified inside a <plugins></plugins> scope, which itself must be inside <build></build> scope. As you can see from execution phase, the Über JAR is built when calling mvn package.

Shade

The maven-shade-plugin 2 also adds all dependencies inside the final fat JAR, but additionally executes shading. Add the following to your pom.xml:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <configuration>
    <transformers>
      <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
        <mainClass>{your.package.main.class}</mainClass>
      </transformer>
    </transformers>
  </configuration>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>shade</goal>
      </goals>
    </execution>
  </executions>
</plugin>

The shade plugin also allows to specify dependency inclusion/exclusion using a variety of specifiers. The following example from the official docs3 illustrates this:

<project>
...
<build>
  <plugins>
	<plugin>
	  <groupId>org.apache.maven.plugins</groupId>
	  <artifactId>maven-shade-plugin</artifactId>
	  <version>3.3.0</version>
	  <executions>
		<execution>
		  <phase>package</phase>
		  <goals>
			<goal>shade</goal>
		  </goals>
		  <configuration>
			<artifactSet>
			  <excludes>                    
				<exclude>classworlds:classworlds</exclude>
				<exclude>junit:junit</exclude>
				<exclude>jmock:*</exclude>
				<exclude>*:xml-apis</exclude>
				<exclude>org.apache.maven:lib:tests</exclude>
				<exclude>log4j:log4j:jar:</exclude>
			  </excludes>
			</artifactSet>
		  </configuration>
		</execution>
	  </executions>
	</plugin>
  </plugins>
</build>
...
</project>

Dependencies

Scopes

In Maven, dependencies are configured in the pom.xml file, and each dependency can have a specific scope. The scope of a dependency indicates how Maven should use the dependency with respect to different phases of the project lifecycle (like compilation, testing, deployment). Here are the main scopes available in Maven:

  1. Compile:

    • Meaning: This is the default scope if none is provided. Dependencies with compile scope are available in all classpaths of the project. They are used during the compilation, testing, and run phases.
    • Use Cases: Typically used for all dependencies necessary to compile the project code.
  2. Provided:

    • Meaning: Indicates that the dependency is provided by the runtime environment or container at which the project is aimed, and therefore should not be included in the project’s artifact (like a WAR or JAR file).
    • Use Cases: Common in web applications (where the servlet container provides certain libraries) or in environments where certain APIs are provided by the platform (e.g., Java EE APIs).
  3. Runtime:

    • Meaning: Dependencies with runtime scope are not required for compilation but are for execution. They are included in the runtime and test classpaths, but not in the compile classpath.
    • Use Cases: Typically used for dependencies required for execution but not for compilation, such as JDBC drivers.
  4. Test:

    • Meaning: Dependencies with the test scope are not required for normal use of the application but are necessary for testing purposes.
    • Use Cases: Used for dependencies that are only required for test compilation and execution phases, such as JUnit or Mockito.
  5. System:

    • Meaning: Similar to provided scope, but specifies that the dependency is available in a specific location on the system. This scope requires you to provide an explicit path to the library.
    • Use Cases: It is generally discouraged to use this scope as it makes the build dependent on the local machine configuration. It’s used for dependencies that are not available in a repository and are hosted in a specific path on the system.
  6. Import (Only applicable in Maven 2.0.9 or later):

    • Meaning: This scope is used only with dependency type pom in the <dependencyManagement> section. It indicates that the specified POM’s dependency list should be replaced with the dependencies in the current project’s <dependencyManagement> section.
    • Use Cases: Primarily used in multi-module projects to manage dependency versions in a central place (commonly known as a “bill of materials” or BOM).

Tree

The dependency-tree plugin4 allows to display a project’s dependency tree. At it’s simplest running mvn dependency:tree will print a tree with the project’s depency hierarchy.

Testing

Excluding tests

To exclude test from Maven use the following notation5

# Exclude specific class with (!)
$ mvn test -Dtest=!ExcludedClass
# Exclude specific method 
$ mvn test -Dtest=!ExcludedClass#excludedMethod
# Exclude more than one method
$ mvn test -Dtest=!ExcludedClass#excludedMethod+excludedMethod2
# Exclude a package with a wildcard (*)
$ mvn test -Dtest=!dev.ruivieira.test.Excluded*