POM Reference

Introduction

The POM 4.0.0 XSD and descriptor reference documentation.

What is the POM?

POM stands for “Project Object Model”. It is an XML representation of a Maven project held in a file named pom.xml. When in the presence of Maven folks, speaking of a project is speaking in the philosophical sense, beyond a mere collection of files containing code. A project contains configuration files, as well as the developers involved and the roles they play, the defect tracking system, the organization and licenses, the URL of where the project lives, the project's dependencies, and all the other little pieces that come into play to give code life. It is a one-stop-shop for all things concerning the project. In fact, in the Maven world, a project does not need to contain any code at all, merely a pom.xml.

Quick Overview

This is a listing of the elements directly under the POM's project element. Notice that <modelVersion> contains 4.0.0. That is currently the only supported POM version, and is always required.

<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">
  <modelVersion>4.0.0</modelVersion>

  <!-- The Basics -->
  <groupId>...</groupId>
  <artifactId>...</artifactId>
  <version>...</version>
  <packaging>...</packaging>
  <dependencies>...</dependencies>
  <parent>...</parent>
  <dependencyManagement>...</dependencyManagement>
  <modules>...</modules>
  <properties>...</properties>

  <!-- Build Settings -->
  <build>...</build>
  <reporting>...</reporting>

  <!-- More Project Information -->
  <name>...</name>
  <description>...</description>
  <url>...</url>
  <inceptionYear>...</inceptionYear>
  <licenses>...</licenses>
  <organization>...</organization>
  <developers>...</developers>
  <contributors>...</contributors>

  <!-- Environment Settings -->
  <issueManagement>...</issueManagement>
  <ciManagement>...</ciManagement>
  <mailingLists>...</mailingLists>
  <scm>...</scm>
  <prerequisites>...</prerequisites>
  <repositories>...</repositories>
  <pluginRepositories>...</pluginRepositories>
  <distributionManagement>...</distributionManagement>
  <profiles>...</profiles>
</project>

The Basics

The POM contains all necessary information about a project, as well as configurations of plugins to be used during the build process. It is the declarative manifestation of the “who”, “what”, and “where”, while the build lifecycle is the “when” and “how”. That is not to say that the POM cannot affect the flow of the lifecycle - it can. For example, by configuring the maven-antrun-plugin, one can embed Apache Ant tasks inside the POM. It is ultimately a declaration, however. Whereas a build.xml tells Ant precisely what to do when it is run (procedural), a POM states its configuration (declarative). If some external force causes the lifecycle to skip the Ant plugin execution, it does not stop the plugins that are executed from doing their magic. This is unlike a build.xml file, where tasks are almost always dependent on the lines executed before it.

<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.codehaus.mojo</groupId>
  <artifactId>my-project</artifactId>
  <version>1.0</version>
</project>

Maven Coordinates

The POM defined above is the bare minimum that Maven allows. groupId:artifactId:version are all required fields (although, groupId and version do not need to be explicitly defined if they are inherited from a parent - more on inheritance later). The three fields act much like an address and timestamp in one. This marks a specific place in a repository, acting like a coordinate system for Maven projects:

  • <groupId>: This is generally unique amongst an organization or a project. For example, all core Maven artifacts do (well, should) live under the groupId org.apache.maven. Group ID's do not necessarily use the dot notation, for example, the junit project. Note that the dot-notated groupId does not have to correspond to the package structure that the project contains. It is, however, a good practice to follow. When stored within a repository, the group acts much like the Java packaging structure does in an operating system. The dots are replaced by OS specific directory separators (such as ‘/’ in Unix) which becomes a relative directory structure from the base repository. In the example given, the org.codehaus.mojo group lives within the directory $M2_REPO/org/codehaus/mojo.

  • <artifactId>: The artifactId is generally the name that the project is known by. Although the groupId is important, people within the group will rarely mention the groupId in discussion (they are often all be the same ID, such as the MojoHaus project groupId: org.codehaus.mojo). It, along with the groupId, creates a key that separates this project from every other project in the world (at least, it should :) ). Along with the groupId, the artifactId fully defines the artifact's relative path within the repository. In the case of the above project, my-project lives in $M2_REPO/org/codehaus/mojo/my-project.

  • <version>: This is the last piece of the naming puzzle. groupId:artifactId denotes a single project, but it does not indicate which incarnation of that project we are talking about. Do we want the junit:junit of 2018 (version 4.12), or of 2007 (version 3.8.2)? In short: code changes, those changes should be versioned, and this element keeps those versions in line. It is also used within an artifact's repository to separate versions from each other. my-project version 1.0 files live in the directory structure $M2_REPO/org/codehaus/mojo/my-project/1.0.

The three elements given above point to a specific version of a project, letting Maven know who we are dealing with, and when in its software lifecycle we want them.

Packaging

Now that we have our address structure of groupId:artifactId:version, there is one more standard label to give us a really complete what: that is the project's packaging. In our case, the example POM for org.codehaus.mojo:my-project:1.0 defined above will be packaged as a jar. We could make it into a war by declaring a different packaging:

<project xmlns="http://maven.apache.org/POM/4.0.0">
  ...
  <packaging>war</packaging>
  ...
</project>

When no packaging is declared, Maven assumes the packaging is the default: jar. The valid types are Plexus role-hints (read more on Plexus for an explanation of roles and role-hints) of the component role org.apache.maven.lifecycle.mapping.LifecycleMapping. The current core packaging values are: pom, jar, maven-plugin, ejb, war, ear, rar. These define the default list of goals which execute on each corresponding build lifecycle stage for a particular package structure: see Plugin Bindings for default Lifecycle Reference for details.

POM Relationships

One powerful aspect of Maven is its handling of project relationships: this includes dependencies (and transitive dependencies), inheritance, and aggregation (multi-module projects).

Dependency management has a long tradition of being a complicated mess for anything but the most trivial of projects. “Jarmageddon” quickly ensues as the dependency tree becomes large and complicated. “Jar Hell” follows, where versions of dependencies on one system are not equivalent to the versions developed with, either by the wrong version given, or conflicting versions between similarly named jars.

Maven solves both problems through a common local repository from which to link projects correctly, versions and all.

Dependencies

The cornerstone of the POM is its dependency list. Most projects depend on others to build and run correctly. Maven downloads the dependencies on compilation, as well as on other goals that require them. As an added bonus, Maven brings in the dependencies of those dependencies (transitive dependencies), allowing your list to focus solely on the dependencies your project requires.

<project xmlns="http://maven.apache.org/POM/4.0.0">
  ...
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <type>jar</type>
      <scope>test</scope>
      <optional>true</optional>
    </dependency>
    ...
  </dependencies>
  ...
</project>
  • <groupId>, <artifactId>, <version>: You will see these elements often. This trinity is used to compute the Maven coordinate of a specific project in time, demarcating it as a dependency of this project. The purpose of this computation is to select a version that matches all the dependency declarations (due to transitive dependencies, there can be multiple dependency declarations for the same artifact). The values should be:

  • <groupId>, <artifactId>: directly the corresponding coordinates of the dependency,

  • <version>: a <dependency version requirement specification>, that is used to compute the dependency's effective version.

Since the dependency is described by Maven coordinates, you may be thinking: “This means that my project can only depend upon Maven artifacts!” The answer is, “Of course, but that's a good thing.” This forces you to depend solely on dependencies that Maven can manage.

There are times, unfortunately, when a project cannot be downloaded from the central Maven repository. For example, a project may depend upon a jar that has a closed-source license which prevents it from being in a central repository. There are three methods for dealing with this scenario.

  1. Install the dependency locally using the Maven Install Plugin. The method is the simplest recommended method. For example:

    mvn install:install-file -Dfile=non-maven-proj.jar -DgroupId=some.group -DartifactId=non-maven-proj -Dversion=1 -Dpackaging=jar
    

    Notice that an address is still required, only this time you use the command line and the Maven Install Plugin will create a POM for you with the given address.

  2. Create your own repository and deploy it there. This is a favorite method for companies with an intranet and need to be able to keep everyone in synch. There is a Maven goal called deploy:deploy-file which is similar to the install:install-file goal (read the plugin's goal page for more information).

  3. Set the dependency scope to system and define a systemPath. This is not recommended, however, but leads us to explaining the following elements:

  • <classifier>: The classifier distinguishes artifacts that were built from the same POM but differ in content. It is some optional and arbitrary string that - if present - is appended to the artifact name just after the version number.

    As a motivation for this element, consider for example a project that offers an artifact targeting Java 11 but at the same time also an artifact that still supports Java 1.8. The first artifact could be equipped with the classifier jdk11 and the second one with jdk8 such that clients can choose which one to use.

    Another common use case for classifiers is to attach secondary artifacts to the project's main artifact. If you browse the Maven central repository, you will notice that the classifiers sources and javadoc are used to deploy the project source code and API docs along with the packaged class files.

  • <type>: Corresponds to the chosen dependency type. This defaults to jar. While it usually represents the extension on the filename of the dependency, that is not always the case: a type can be mapped to a different extension and a classifier. The type often corresponds to the packaging used, though this is also not always the case. Some examples are jar, ejb-client and test-jar: see default artifact handlers for a list. New types can be defined by plugins that set extensions to true, so this is not a complete list.

  • <scope>: This element refers to the classpath of the task at hand (compiling and runtime, testing, etc.) as well as how to limit the transitivity of a dependency. There are five scopes available:

  • <compile> - this is the default scope, used if none is specified. Compile dependencies are available in all classpaths. Furthermore, those dependencies are propagated to dependent projects.

  • <provided> - this is much like compile, but indicates you expect the JDK or a container to provide it at runtime. It is only available on the compilation and test classpath, and is not transitive.

  • <runtime> - this scope indicates that the dependency is not required for compilation, but is for execution. It is in the runtime and test classpaths, but not the compile classpath.

  • <test> - this scope indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases. It is not transitive.

  • <system> - this scope is similar to provided except that you have to provide the JAR which contains it explicitly. The artifact is always available and is not looked up in a repository.

  • <systemPath>: is used only if the dependency scope is system. Otherwise, the build will fail if this element is set. The path must be absolute, so it is recommended to use a property to specify the machine-specific path (more on properties below), such as ${java.home}/lib. Since it is assumed that system scope dependencies are installed a priori, Maven does not check the repositories for the project, but instead checks to ensure that the file exists. If not, Maven fails the build and suggests that you download and install it manually.

  • <optional>: Marks a dependency optional when this project itself is a dependency. For example, imagine a project A that depends upon project B to compile a portion of code that may not be used at runtime. In that case, if project X adds project A as its own dependency, project X does not need project B on the classpath and Maven does not need to install project B at all. Symbolically, if => represents a required dependency, and --> represents optional, although A=>B may be the case when building A X=>A-->B would be the case when building X.

    In the shortest terms, optional lets other projects know that, when you use this project, you do not require this dependency in order to work correctly.

Dependency Management

Dependencies can be managed in the dependencyManagement section to affect the resolution of dependencies which are not fully qualified or to enforce the usage of a specific transitive dependency version. Further information in Introduction to the Dependency Mechanism.

Dependency Version Requirement Specification

Dependencies' version elements define version requirements, which are used to compute dependency versions. Soft requirements can be replaced by different versions of the same artifact found elsewhere in the dependency graph. Hard requirements mandate a particular version or versions and override soft requirements. If there are no versions of a dependency that satisfy all the hard requirements for that artifact, the build fails.

Version requirements have the following syntax:

  • 1.0: Soft requirement for 1.0. Use 1.0 if no other version appears earlier in the dependency tree.
  • [1.0]: Hard requirement for 1.0. Use 1.0 and only 1.0.
  • (,1.0]: Hard requirement for any version <= 1.0.
  • [1.2,1.3]: Hard requirement for any version between 1.2 and 1.3 inclusive.
  • [1.0,2.0): 1.0 <= x < 2.0; Hard requirement for any version between 1.0 inclusive and 2.0 exclusive.
  • [1.5,): Hard requirement for any version greater than or equal to 1.5.
  • (,1.0],[1.2,): Hard requirement for any version less than or equal to 1.0 than or greater than or equal to 1.2, but not 1.1. Multiple requirements are separated by commas.
  • (,1.1),(1.1,): Hard requirement for any version except 1.1; for example because 1.1 has a critical vulnerability.

Maven picks the highest version of each project that satisfies all the hard requirements of the dependencies on that project. If no version satisfies all the hard requirements, the build fails.

Version Order Specification

If version strings are syntactically correct Semantic Versioning 1.0.0 version numbers, then in most cases version comparison follows the precedence rules outlined in that specification. These versions are the commonly encountered alphanumeric ASCII strings such as 2.15.2-alpha. More precisely, this is true if both version strings to be compared match the “valid semver” production in the BNF grammar for semantic versioning and both version strings only use lower case letters. Maven does not consider any semantics implied by Semantic Versioning.

Important: This is only true for Semantic Versioning 1.0.0. The Maven version order algorithm is not compatible with Semantic Versioning 2.0.0. In particular, Maven does not special case the plus sign or consider build identifiers.

Important: Maven compares version strings using case-insensitive rules. Semver is case-sensitive. Thus in Semver, 3.2-ALPHA1 compares greater than 3.2-alpha1. In Maven, 3.2-ALPHA1 compares equal to 3.2-alpha1.

When version strings do not follow semantic versioning, a more complex set of rules is required. Each Maven version string is split into tokens between dots (‘.’), hyphens (‘-’), underscores (‘_’), and transitions between ASCII digits and characters. The separator is recorded and will have effect on the order. A transition between ASCII digits and characters is equivalent to a hyphen. Empty tokens are replaced with ‘0’. This gives a sequence of version numbers (numeric tokens) and version qualifiers (non-numeric tokens) with ‘.’, ‘_’ or ‘-’ prefixes.

Splitting and Replacing Examples:

  • 1-1.foo-bar1baz-.1 -> 1-1.foo-bar-1-baz-0.1

Then, starting from the end of the version, the trailing “null” values (0, "", “final”, “ga”) are trimmed. This process is repeated at each remaining hyphen from end to start.

Trimming Examples:

  • 1.0.0 -> 1
  • 1.ga -> 1
  • 1.final -> 1
  • 1.0 -> 1
  • 1. -> 1
  • 1- -> 1
  • 1_ -> 1
  • 1.0.0-foo.0.0 -> 1-foo
  • 1.0.0-0.0.0 -> 1

Following tokenization and trimming, the shorter token sequence is padded with enough “null” values with matching prefix to have the same length as the longer one. Padded “null” values depend on the separator of the other version: 0 for ‘.’, ‘’ for ‘-’, ‘_’ and a transition between ASCII digits and characters.

Then the two sequences are compared token by token from beginning to end (left-to-right in the original strings). If each token in one sequence compares equal to the corresponding token in the other sequence, then the two version strings are equal. Otherwise, the result is the comparison of the tokens from the first position in the sequences where they are not equal: less than if the first non-matching token in the first string is less than the corresponding token in the second string, greater than if the first non-matching token in the first string is greater than the corresponding token in the second string.

Individual tokens are compared according to the following rules:

  • Tokens comprised of the ASCII digits 0-9 are called “numeric tokens”. Tokens comprised of any other characters, including non-ASCII digits, are called “qualifiers”.

  • If the separator is the same, then compare the token:

    • Numeric tokens have the usual ordering of integers.

    • Qualifiers are first converted to lower case in the English locale. Then they are ordered as by the compareTo() method of java.lang.String, except for the following tokens which come first in this order:

      alpha < beta < milestone < rc = cr < snapshot < = final = ga = release < sp

    • the alpha, beta and milestone qualifiers can respectively be shortened to a, b and m when directly followed by a number.

    • Alphabetic tokens other than the special cases described above come before numeric tokens.

    • Alphabetic tokens are compared in a case-insensitive fashion in the English locale. For example, A and a are treated the same as are i and I and é and É.

  • else .qualifier = -qualifier < -number < .number

  • alpha < a1 < beta < b1 < milestone < m1 < rc = cr < snapshot < = final = ga = release < sp

Following semver rules is encouraged, and some qualifiers are discouraged:

  • Prefer alpha, beta, and