Maven getting started
Table of Contents
- What is Maven?
- Why Maven?
- Components
- Build lifecycle
- POM (Project Object Model) file
- Project coordinates
- Packaging
- Naming conventions
- Standard directory layout
- Dependencies
- Project inheritance
- Project aggregation (Multi Module)
- Properties
- Version structure
- Version resolution transitive dependencies
- Keeping POM clean
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/java | Application/Library sources |
src/main/resources | Application/Library resources |
src/main/filters | Resource filter files |
src/main/webapp | Web application sources |
src/test/java | Test sources |
src/test/resources | Test resources |
src/test/filters | Test resource filter files |
src/it | Integration Tests (primarily for plugins) |
src/assembly | Assembly descriptors |
src/site | Site |
LICENSE.txt | Project's license |
NOTICE.txt | Notices and attributions required by libraries that the project depends on |
README.txt | Project'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).
- The version in the dependency tree that is closest to one's own project
- 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
- Find out unclean dependencies:
mvn dependency:analyze
- check out https://maven.apache.org/plugins/maven-dependency-plugin/analyze-mojo.html
- Lists dependencies that:
- Used and declared (--> ok)
- Used but are not declared in their own pom (--> nok)
- Are not used but are declared (--> potentially remnant, clean up)