Maven getting started

Table of Contents

What is Maven?

  • Declarative build management tool for automated builds
  • Standardized directory structure
  • Declarative dependency management
  • Works with an XML control file (POM - Project Object Model)
  • Open source, written in Java, for Java projects
  • Pure command line tool
  • IDEs offer integration

Why Maven?

  • Uniform build in the project, but also across multiple projects.
  • Dependency management
  • Independent of an IDE
  • Basis for rapid familiarization of new developers
  • Basis for Continuous Integration/Continuous Deployment (DevOps)
  • Industry standard for Java applications

Components

Build lifecycle

  • Each Build Phase has its own Plugin. Maven delegates the work of each build phase to plugins.
  • Plugins provide Goals, which do the actual work
  • Goals can be assigned to one or more build phases
  • Certain plugins/goals are assigned by Maven by default
  • You can assign plugins/goals to specific build phases in the project object model itself

POM (Project Object Model) file

  • The POM declaratively describes the build including all dependencies.
  • POM is an XML file that describes the Maven project.
  • Maven project usually creates an artifact (jar, war, ear, ...)

Minimal POM example file:

<?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>com.test</groupId>
    <artifactId>test</artifactId>
    <version>1.0-SNAPSHOT</version>
</project>

Project coordinates

The project coordinates consists of groupId, artifactId and the version

  • Maven project coordinates form a kind of address for a Maven artifact
  • They consist of:
    <groupId>com.test</groupId>
    <artifactId>my-app</artifactId>
    <version>1.0.0</version>
  • The combination of the three parts shall be globally unique!
  • Using the project coordinates, the dependencies are fetched from the repository fetched
  • All three parts are mandatory

Packaging

  • Packaging defines the shape of the generated artifact
  • Packaging:
    <packaging>jar</packaging>
  • Not specified: default is jar.
  • Other possible values: pom, maven-plugin, ejb, war, ear, rar

Naming conventions

groupId

  • Identifies the project globally → unique namespace required.
  • The Java package naming conventions are used.
  • Reverse Internet domain name, which one has under one's own control
    has, e.g. com.example
  • After that, freely selectable, uniquely within domain, e.g. com.example.dev

artifactId

  • Identifies the concrete build artifact (jar, war, ear, ...).
  • E.g. todoapp

Standard directory layout

Legend to the layout:

src/main/javaApplication/Library sources
src/main/resourcesApplication/Library resources
src/main/filtersResource filter files
src/main/webappWeb application sources
src/test/javaTest sources
src/test/resourcesTest resources
src/test/filtersTest resource filter files
src/itIntegration Tests (primarily for plugins)
src/assemblyAssembly descriptors
src/siteSite
LICENSE.txtProject's license
NOTICE.txtNotices and attributions required by libraries that the project depends on
README.txtProject's readme

Dependencies

  • Dependency management is the central function of Maven
  • Management of the required libraries
  • Dependencies required by libraries themselves are added automatically (without specifying them) → transitive dependencies.
  • Maven automatically downloads dependencies and transitive dependencies.
  • The downloaded dependencies are available for compilation.
    available
  • The downloaded dependencies are packed into the artifacts

example of a dependency declaration in a POM file:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-jdbc</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

You can also list all dependencies with this command: 

# mvn dependency:tree                                                                                                          
[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.example:demo >--------------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-dependency-plugin:3.3.0:tree (default-cli) @ demo ---
[INFO] com.example:demo:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.5:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.7.5:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.5:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.11:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.11:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.17.2:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.36:compile
[INFO] |  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.30:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-json:jar:2.7.5:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.13.4.2:compile
[INFO] |  |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.13.4:compile
[INFO] |  |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.13.4:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.4:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.4:compile
[INFO] |  |  \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.4:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.7.5:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.68:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.68:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.68:compile
[INFO] |  +- org.springframework:spring-web:jar:5.3.23:compile
[INFO] |  |  \- org.springframework:spring-beans:jar:5.3.23:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:5.3.23:compile
[INFO] |     +- org.springframework:spring-aop:jar:5.3.23:compile
[INFO] |     +- org.springframework:spring-context:jar:5.3.23:compile
[INFO] |     \- org.springframework:spring-expression:jar:5.3.23:compile
[INFO] +- org.springframework.boot:spring-boot-devtools:jar:2.7.5:runtime
[INFO] |  +- org.springframework.boot:spring-boot:jar:2.7.5:compile
[INFO] |  \- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.5:compile
[INFO] \- org.springframework.boot:spring-boot-starter-test:jar:2.7.5:test
[INFO]    +- org.springframework.boot:spring-boot-test:jar:2.7.5:test
[INFO]    +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.7.5:test
[INFO]    +- com.jayway.jsonpath:json-path:jar:2.7.0:test
[INFO]    |  +- net.minidev:json-smart:jar:2.4.8:test
[INFO]    |  |  \- net.minidev:accessors-smart:jar:2.4.8:test
[INFO]    |  |     \- org.ow2.asm:asm:jar:9.1:test
[INFO]    |  \- org.slf4j:slf4j-api:jar:1.7.36:compile
[INFO]    +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test
[INFO]    |  \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test
[INFO]    +- org.assertj:assertj-core:jar:3.22.0:test
[INFO]    +- org.hamcrest:hamcrest:jar:2.2:test
[INFO]    +- org.junit.jupiter:junit-jupiter:jar:5.8.2:test
[INFO]    |  +- org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test
[INFO]    |  |  +- org.opentest4j:opentest4j:jar:1.2.0:test
[INFO]    |  |  +- org.junit.platform:junit-platform-commons:jar:1.8.2:test
[INFO]    |  |  \- org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO]    |  +- org.junit.jupiter:junit-jupiter-params:jar:5.8.2:test
[INFO]    |  \- org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test
[INFO]    |     \- org.junit.platform:junit-platform-engine:jar:1.8.2:test
[INFO]    +- org.mockito:mockito-core:jar:4.5.1:test
[INFO]    |  +- net.bytebuddy:byte-buddy:jar:1.12.18:test
[INFO]    |  +- net.bytebuddy:byte-buddy-agent:jar:1.12.18:test
[INFO]    |  \- org.objenesis:objenesis:jar:3.2:test
[INFO]    +- org.mockito:mockito-junit-jupiter:jar:4.5.1:test
[INFO]    +- org.skyscreamer:jsonassert:jar:1.5.1:test
[INFO]    |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO]    +- org.springframework:spring-core:jar:5.3.23:compile
[INFO]    |  \- org.springframework:spring-jcl:jar:5.3.23:compile
[INFO]    +- org.springframework:spring-test:jar:5.3.23:test
[INFO]    \- org.xmlunit:xmlunit-core:jar:2.9.0:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.256 s
[INFO] Finished at: 2022-10-27T19:47:12+02:00
[INFO] ------------------------------------------------------------------------

Scope

The scope of a dependency specifies in which classpaths it is provided by Maven.
(classpaths change depending on the build phase):

  • compile: Default Scope, Dependencies are available in all classpaths
  • test: Only during test
  • provided: Only available in compile and test classpaths, dependency must be provided at runtime (e.g., by the servlet container)
  • runtime: Only during runtime and test, but not for compile
  • other (not so important)

Transitive dependencies

  • Dependencies required by the dependencies we declare.
  • Are automatically downloaded by Maven and added to the classpath
  • Attention: You can also use these transitive dependencies directly in the code.
    → please do not do this!
  • Explicitly enter each dependency that you use yourself in the code as a dependency in the POM

Finding out if you are using a transitive dependency in your code:

# mvn dependency:analyze                                                                                                             
[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.example:demo >--------------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] >>> maven-dependency-plugin:3.3.0:analyze (default-cli) > test-compile @ demo >>>
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/Dave/Downloads/demo/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] skip non existing resourceDirectory /Users/Dave/Downloads/demo/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.10.1:testCompile (default-testCompile) @ demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/Dave/Downloads/demo/target/test-classes
[INFO] 
[INFO] <<< maven-dependency-plugin:3.3.0:analyze (default-cli) < test-compile @ demo <<<
[INFO] 
[INFO] 
[INFO] --- maven-dependency-plugin:3.3.0:analyze (default-cli) @ demo ---
[WARNING] Used undeclared dependencies found:
[WARNING]    org.springframework.boot:spring-boot:jar:2.7.5:compile
[WARNING]    org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test
[WARNING]    org.springframework.boot:spring-boot-test:jar:2.7.5:test
[WARNING]    org.springframework.boot:spring-boot-autoconfigure:jar:2.7.5:compile
[WARNING] Unused declared dependencies found:
[WARNING]    org.springframework.boot:spring-boot-starter-web:jar:2.7.5:compile
[WARNING]    org.springframework.boot:spring-boot-devtools:jar:2.7.5:runtime
[WARNING]    org.springframework.boot:spring-boot-starter-test:jar:2.7.5:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.113 s
[INFO] Finished at: 2022-10-27T19:50:29+02:00
[INFO] ------------------------------------------------------------------------

Project inheritance

POMs an also inherit, this is for example needed for a Spring Boot-Application. This allows you for example, to standardize the libraries used and their versions across multiple projects or Maven modules.

Project aggregation (Multi Module)

Also a POM can aggregate multiple POMs as Modules

<groupId>com.test</groupId>
<artifactId>example-maven-multimodule</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<modules>
	<module>module1</module>
	<module>module2</module>
	<module>module3</module>
</modules>

Properties

Properties are placeholders and can be addressed via ${<propertyname>}.

Example:

<properties>
	<java.version>1.8</java.version>
	<commons-pool2.version>2.6.0</commons-pool2.version>
</properties>
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-pool2</artifactId>
	<version>${commons-pool2.version}</version>
</dependency>

Version structure

  • A version must always be specified
  • Maven basically leaves the structure of a version free
  • Recommended approach: semantic versioning (https://semver.org/)
    • <Major>.<Minor>.<Bugfix>-<Qualifier>-<Buildnumber>
    • Major: increment when API changes.
    • Minor: increment if new features are included, but same API.
    • Bugfix: increment, if only something was fixed
    • Qualifier: Optional.
    • Build number: Optional.
    • Example: <version>2.4.18</version>

SNAPSHOT Version

  • The qualifier SNAPSHOT is used during the development of a version
    is used
  • Example: 1.0-SNAPSHOT → means that the 1.0 version of the software is being
    is being developed
  • A version with SNAPSHOT qualifier is always considered to be older than
    the same one without the qualifier
  • Special rules apply for version resolution, Maven usually fetches a snapshot version once per day from the repository, unless you specify "-U".
  • Example: mvn -U clean install also fetches the most recent snapshot
    versions from the repository

Version resolution transitive dependencies

  • Multiple transitive dependencies can contain the same artifact in different
    different versions, but can only have exactly one version of a transitive dependency in the classpath.
  • Resolution is performed according to the "nearest definition" principle:
    • The version in the dependency tree that is closest to one's own project
      Tree is taken
    • If two versions are equally close, the one found first is taken
    • Example: Dependency D appears twice in the dependency tree:
      • A → B → C → D (version 2.0)
      • A → E → D (version 1.0)
      • Version 1.0 of D is taken, since closer to own project (A).
  • One can explicitly specify a particular version of a transitive dependency
    specify:
    • Include it explicitly as a dependency in your own POM.
    • Via Dependency Management

Keeping POM clean