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 of 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 need not contain any code at all, merely a pom.xml.
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 for both Maven 2 & 3, 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 http://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 POM contains all necessary information about a project, as well as configurations of plugins to be used during the build process. It is, effectively, 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 effectively embed ant tasks inside of the POM. It is ultimately a declaration, however. Where as 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 will not stop the plugins that are executed from doing their magic. This is unlike a build.xml file, where tasks are almost always dependant on the lines executed before it.
<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>org.codehaus.mojo</groupId> <artifactId>my-project</artifactId> <version>1.0</version> </project>
The POM defined above is the minimum that both Maven 2 & 3 will allow. groupId:artifactId:version are all required fields (although, groupId and version need not 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.
The three elements given above point to a specific version of a project letting Maven knows who we are dealing with, and when in its software lifecycle we want them.
<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"> ... <packaging>war</packaging> ... </project>
When no packaging is declared, Maven assumes the artifact is the default: jar. The valid types are Plexus role-hints (read more on Plexus for a 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, par. These define the default list of goals which execute to each corresponding build lifecycle stage for a particular package structure.
You will sometimes see Maven print out a project coordinate as groupId:artifactId:packaging:version.
One powerful aspect of Maven is in its handling of project relationships; that 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 versions as those 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.
The cornerstone of the POM is its dependency list. Most every project depends upon others to build and run correctly, and if all Maven does for you is manage this list for you, you have gained a lot. Maven downloads and links the dependencies for you on compilation and 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" 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"> ... <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.0</version> <type>jar</type> <scope>test</scope> <optional>true</optional> </dependency> ... </dependencies> ... </project>
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.
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 install plugin will create a POM for you with the given address.
As a motivation for this element, consider for example a project that offers an artifact targeting JRE 1.5 but at the same time also an artifact that still supports JRE 1.4. The first artifact could be equipped with the classifier jdk15 and the second one with jdk14 such that clients can choose which one to use.
Another common use case for classifiers is the need 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.
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.
Dependencies' version element define version requirements, used to compute effective dependency version. Version requirements have the following syntax:
The maven coordinate is split in tokens between dots ('.'), hyphens ('-') and transitions between digits and characters. The separator is recorded and will have effect on the order. A transition between digits and characters is equivalent to hypen. 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:
Then, starting from the end of the version, the trailing "null" values (0, "", "final", "ga") are trimmed. This process is repeated at each remaining hypen from end to start.
Trimming Examples:
The version order is the lexicographical order on this sequence of prefixed tokens, the shorter one padded with enough "null" values with matching prefix to have the same length as the longer one. Padded "null" values depend on the prefix of the other version: 0 for '.', "" for '-'. The prefixed token order is:
"alpha" < "beta" < "milestone" < "rc" = "cr" < "snapshot" < "" = "final" = "ga" < "sp"
End Result Examples:
Note: Contrary to what was stated in some design documents, for version order, snapshots are not treated differently than releases or any other qualifier.
The maven distribution includes a tool to check version order. It was used to produce the examples in the previous paragraphs. Feel free to run it yourself when in doubt. You can run it like this:
java -jar ${MAVEN_HOME}/lib/maven-artifact-3.3.9.jar [versions...]
example:
$ java -jar ./lib/maven-artifact-3.3.9.jar 1 2 1.1 Display parameters as parsed by Maven (in canonical form) and comparison result: 1. 1 == 1 1 < 2 2. 2 == 2 2 > 1.1 3. 1.1 == 1.1
Exclusions explicitly tell Maven that you don't want to include the specified project that is a dependency of this dependency (in other words, its transitive dependency). For example, the maven-embedder requires maven-core, and we do not wish to use it or its dependencies, then we would add it as an exclusion.
<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"> ... <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-embedder</artifactId> <version>2.0</version> <exclusions> <exclusion> <groupId>org.apache.maven</groupId> <artifactId>maven-core</artifactId> </exclusion> </exclusions> </dependency> ... </dependencies> ... </project>
It is also sometimes useful to clip a dependency's transitive dependencies. A dependency may have incorrectly specified scopes, or dependencies that conflict with other dependencies in your project. Using wildcard excludes makes it easy to exclude all a dependency's transitive dependencies. In the case below you may be working with the maven-embedder and you want to manage the dependencies you use yourself, so you clip all the transitive dependencies:
<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"> ... <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-embedder</artifactId> <version>3.1.0</version> <exclusions> <exclusion> <groupId>*</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency> ... </dependencies> ... </project>
One powerful addition that Maven brings to build management is the concept of project inheritance. Although in build systems such as Ant, inheritance can certainly be simulated, Maven has gone the extra step in making project inheritance explicit to the project object model.
<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> <groupId>org.codehaus.mojo</groupId> <artifactId>my-parent</artifactId> <version>2.0</version> <packaging>pom</packaging> </project>
The packaging type required to be pom for parent and aggregation (multi-module) projects. These types define the goals bound to a set of lifecycle stages. For example, if packaging is jar, then the package phase will execute the jar:jar goal. If the packaging is pom, the goal executed will be site:attach-descriptor. Now we may add values to the parent POM, which will be inherited by its children. Most elements from the parent POM are inherited by its children, including:
Notable elements which are not inherited include:
<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> <parent> <groupId>org.codehaus.mojo</groupId> <artifactId>my-parent</artifactId> <version>2.0</version> <relativePath>../my-parent</relativePath> </parent> <artifactId>my-project</artifactId> </project>
Notice the relativePath element. It is not required, but may be used as a signifier to Maven to first search the path given for this project's parent, before searching the local and then remote repositories.
To see inheritance in action, just have a look at the ASF or Maven parent POM's.
Similar to the inheritance of objects in object oriented programming, POMs that extend a parent POM inherit certain values from that parent. Moreover, just as Java objects ultimately inherit from java.lang.Object, all Project Object Models inherit from a base Super POM. The snippet below is the Super POM for Maven 3.0.4.
<project> <modelVersion>4.0.0</modelVersion> <repositories> <repository> <id>central</id> <name>Central Repository</name> <url>http://repo.maven.apache.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>central</id> <name>Central Repository</name> <url>http://repo.maven.apache.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <releases> <updatePolicy>never</updatePolicy> </releases> </pluginRepository> </pluginRepositories> <build> <directory>${project.basedir}/target</directory> <outputDirectory>${project.build.directory}/classes</outputDirectory> <finalName>${project.artifactId}-${project.version}</finalName> <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory> <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory> <scriptSourceDirectory>src/main/scripts</scriptSourceDirectory> <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory> <resources> <resource> <directory>${project.basedir}/src/main/resources</directory> </resource> </resources> <testResources> <testResource> <directory>${project.basedir}/src/test/resources</directory> </testResource> </testResources> <pluginManagement> <!-- NOTE: These plugins will be removed from future versions of the super POM --> <!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) --> <plugins> <plugin> <artifactId>maven-antrun-plugin</artifactId> <version>1.3</version> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.2-beta-5</version> </plugin> <plugin> <artifactId>maven-dependency-plugin</artifactId> <version>2.1</version> </plugin> <plugin> <artifactId>maven-release-plugin</artifactId> <version>2.0</version> </plugin> </plugins> </pluginManagement> </build> <reporting> <outputDirectory>${project.build.directory}/site</outputDirectory> </reporting> <profiles> <!-- NOTE: The release profile will be removed from future versions of the super POM --> <profile> <id>release-profile</id> <activation> <property> <name>performRelease</name> <value>true</value> </property> </activation> <build> <plugins> <plugin> <inherited>true</inherited> <artifactId>maven-source-plugin</artifactId> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> <plugin> <inherited>true</inherited> <artifactId>maven-javadoc-plugin</artifactId> <executions> <execution> <id>attach-javadocs</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> <plugin> <inherited>true</inherited> <artifactId>maven-deploy-plugin</artifactId> <configuration> <updateReleaseInfo>true</updateReleaseInfo> </configuration> </plugin> </plugins> </build> </profile> </profiles> </project>
You can take a look at how the Super POM affects your Project Object Model by creating a minimal pom.xml and executing on the command line: mvn help:effective-pom
Besides inheriting certain top-level elements, parents have elements to configure values for child POMs and transitive dependencies. One of those elements is dependencyManagement.
Note that the version and scope of artifacts which are incorporated from transitive dependencies are also controlled by version specifications in a dependency management section. This can lead to unexpected consequences. Consider a case in which your project uses two dependences, dep1 and dep2. dep2 in turn also uses dep1, and requires a particular minimum version to function. If you then use dependencyManagement to specify an older version, dep2 will be forced to use the older version, and fail. So, you must be careful to check the entire dependency tree to avoid this problem; mvn dependency:tree is helpful.
A project with modules is known as a multimodule, or aggregator project. Modules are projects that this POM lists, and are executed as a group. An pom packaged project may aggregate the build of a set of projects by listing them as modules, which are relative paths to the directories or the POM files of those projects.
<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> <groupId>org.codehaus.mojo</groupId> <artifactId>my-parent</artifactId> <version>2.0</version> <packaging>pom</packaging> <modules> <module>my-project</module> <module>another-project</module> <module>third-project/pom-example.xml</module> </modules> </project>
You do not need to consider the inter-module dependencies yourself when listing the modules, i.e. the ordering of the modules given by the POM is not important. Maven will topologically sort the modules such that dependencies are always build before dependent modules.
To see aggregation in action, just have a look at the Maven or Maven Core Plugins base POM's.
Inheritance and aggregation create a nice dynamic to control builds through a single, high-level POM. You will often see projects that are both parents and aggregators. For example, the entire maven core runs through a single base POM org.apache.maven:maven, so building the Maven project can be executed by a single command: mvn compile. However, although both POM projects, an aggregator project and a parent project are not one in the same and should not be confused. A POM project may be inherited from - but does not necessarily have - any modules that it aggregates. Conversely, a POM project may aggregate projects that do not inherit from it.
Properties are the last required piece in understanding POM basics. Maven properties are value placeholder, like properties in Ant. Their values are accessible anywhere within a POM by using the notation ${X}, where X is the property. Or they can be used by plugins as default values, for example:
<project> ... <properties> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> ... </project>
They come in five different styles:
Note: While environment variables themselves are case-insensitive on Windows, lookup of properties is case-sensitive. In other words, while the Windows shell returns the same value for %PATH% and %Path%, Maven distinguishes between ${env.PATH} and ${env.Path}. As of Maven 2.1.0, the names of environment variables are normalized to all upper-case for the sake of reliability.
Beyond the basics of the POM given above, there are two more elements that must be understood before claiming basic competency of the POM. They are the build element, that handles things like declaring your project's directory structure and managing plugins; and the reporting element, that largely mirrors the build element for reporting purposes.
According to the POM 4.0.0 XSD, the build element is conceptually divided into two parts: there is a BaseBuild type which contains the set of elements common to both build elements (the top-level build element under project and the build element under profiles, covered below); and there is the Build type, which contains the BaseBuild set as well as more elements for the top level definition. Let us begin with an analysis of the common elements between the two.
Note: These different build elements may be denoted "project build" and "profile build".
<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"> ... <!-- "Project Build" contains more elements than just the BaseBuild set --> <build>...</build> <profiles> <profile> <!-- "Profile Build" contains a subset of "Project Build"s elements --> <build>...</build> </profile> </profiles> </project>
BaseBuild is exactly as it sounds: the base set of elements between the two build elements in the POM.
<build> <defaultGoal>install</defaultGoal> <directory>${basedir}/target</directory> <finalName>${artifactId}-${version}</finalName> <filters> <filter>filters/filter1.properties</filter> </filters> ... </build>
For a more comprehensive look at what filters are and what they can do, take a look at the quick start guide.
Another feature of build elements is specifying where resources exist within your project. Resources are not (usually) code. They are not compiled, but are items meant to be bundled within your project or used for various other reasons, such as code generation.
For example, a Plexus project requires a configuration.xml file (which specifies component configurations to the container) to live within the META-INF/plexus directory. Although we could just as easily place this file within src/main/resources/META-INF/plexus, we want instead to give Plexus its own directory of src/main/plexus. In order for the JAR plugin to bundle the resource correctly, you would specify resources similar to the following:
<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"> <build> ... <resources> <resource> <targetPath>META-INF/plexus</targetPath> <filtering>false</filtering> <directory>${basedir}/src/main/plexus</directory> <includes> <include>configuration.xml</include> </includes> <excludes> <exclude>**/*.properties</exclude> </excludes> </resource> </resources> <testResources> ... </testResources> ... </build> </project>
<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"> <build> ... <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <extensions>false</extensions> <inherited>true</inherited> <configuration> <classifier>test</classifier> </configuration> <dependencies>...</dependencies> <executions>...</executions> </plugin> </plugins> </build> </project>
Beyond the standard coordinate of groupId:artifactId:version, there are elements which configure the plugin or this builds interaction with it.
If your POM declares a parent, it will inherit plugin configuration from either the build/plugins or pluginManagement sections of the parent.
To illustrate, consider the following fragment from a parent POM:
<plugin> <groupId>my.group</groupId> <artifactId>my-plugin</artifactId> <configuration> <items> <item>parent-1</item> <item>parent-2</item> </items> <properties> <parentKey>parent</parentKey> </properties> </configuration> </plugin>
And consider the following plugin configuration from a project that uses that parent as its parent:
<plugin> <groupId>my.group</groupId> <artifactId>my-plugin</artifactId> <configuration> <items> <item>child-1</item> </items> <properties> <childKey>child</childKey> </properties> </configuration>
The default behavior is to merge the content of the configuration element according to element name. If the child POM has a particular element, that value becomes the effective value. if the child POM does not have an element, but the parent does, the parent value becomes the effective value. Note that this is purely an operation on XML; no code or configuration of the plugin itself is involved. Only the elements, not their values, are involved.
Applying those rules to the example, Maven comes up with:
<plugin> <groupId>my.group</groupId> <artifactId>my-plugin</artifactId> <configuration> <items> <item>child-1</item> </items> <properties> <childKey>child</childKey> <parentKey>parent</parentKey> </properties> </configuration>
You can control how child POMs inherit configuration from parent POMs by adding attributes to the children of the configuration element. The attributes are combine.children and combine.self. Use these attributes in a child POM to control how Maven combines plugin configuration from the parent with the explicit configuration in the child.
Here is the child configuration with illustrations of the two attributes:
<configuration> <items combine.children="append"> <!-- combine.children="merge" is the default --> <item>child-1</item> </items> <properties combine.self="override"> <!-- combine.self="merge" is the default --> <childKey>child</childKey> </properties> </configuration>
Now, the effective result is the following:
<configuration> <items combine.children="append"> <item>parent-1</item> <item>parent-2</item> <item>child-1</item> </items> <properties combine.self="override"> <childKey>child</childKey> </properties> </configuration>
combine.children="append" results in the concatenation of parent and child elements, in that order. combine.self="override", on the other hand, completely suppresses parent configuration. You cannot use both both combine.self="override" and combine.children="append" on an element; if you try, override will prevail.
Note that these attributes only apply to the configuration element they are declared on, and are not propagated to nested elements. That is if the content of an item element from the child POM was a complex structure instead of text, its sub-elements would still be subject to the default merge strategy unless they were themselves marked with attributes.
The combine.* attributes are inherited from parent to child POMs. Take care when adding those attributes a parent POM as this might affect child or grand-child POMs.
For example, suppose you wanted to bind the antrun:run goal to the verify phase. We want the task to echo the build directory, as well as avoid passing on this configuration to its children (assuming it is a parent) by setting inherited to false. You would get an execution like this:
<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"> ... <build> <plugins> <plugin> <artifactId>maven-antrun-plugin</artifactId> <version>1.1</version> <executions> <execution> <id>echodir</id> <goals> <goal>run</goal> </goals> <phase>verify</phase> <inherited>false</inherited> <configuration> <tasks> <echo>Build Dir: ${project.build.directory}</echo> </tasks> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
<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"> ... <build> ... <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <executions> <execution> <id>pre-process-classes</id> <phase>compile</phase> <goals> <goal>jar</goal> </goals> <configuration> <classifier>pre-process</classifier> </configuration> </execution> </executions> </plugin> </plugins> </pluginManagement> ... </build> </project>
If we added these specifications to the plugins element, they would apply only to a single POM. However, if we apply them under the pluginManagement element, then this POM and all inheriting POMs that add the maven-jar-plugin to the build will get the pre-process-classes execution as well. So rather than the above mess included in every child pom.xml, only the following is 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"> ... <build> ... <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> </plugin> </plugins> ... </build> </project>
The Build type in the XSD denotes those elements that are available only for the "project build". Despite the number of extra elements (six), there are really only two groups of elements that project build contains that are missing from the profile build: directories and extensions.
The set of directory elements live in the parent build element, which set various directory structures for the POM as a whole. Since they do not exist in profile builds, these cannot be altered by profiles.
<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"> ... <build> <sourceDirectory>${basedir}/src/main/java</sourceDirectory> <scriptSourceDirectory>${basedir}/src/main/scripts</scriptSourceDirectory> <testSourceDirectory>${basedir}/src/test/java</testSourceDirectory> <outputDirectory>${basedir}/target/classes</outputDirectory> <testOutputDirectory>${basedir}/target/test-classes</testOutputDirectory> ... </build> </project>
If the values of a *Directory element above is set as an absolute path (when their properties are expanded) then that directory is used. Otherwise, it is relative to the base build directory: ${basedir}.
Extensions are a list of artifacts that are to be used in this build. They will be included in the running build's classpath. They can enable extensions to the build process (such as add an ftp provider for the Wagon transport mechanism), as well as make plugins active which make changes to the build lifecycle. In short, extensions are artifacts that activated during build. The extensions do not have to actually do anything nor contain a Mojo. For this reason, extensions are excellent for specifying one out of multiple implementations of a common plugin interface.
<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"> ... <build> ... <extensions> <extension> <groupId>org.apache.maven.wagon</groupId> <artifactId>wagon-ftp</artifactId> <version>1.0-alpha-3</version> </extension> </extensions> ... </build> </project>
Reporting contains the elements that correspond specifically for the site generation phase. Certain Maven plugins can generate reports defined and configured under the reporting element, for example: generating Javadoc reports. Much like the build element's ability to configure plugins, reporting commands the same ability. The glaring difference is that rather than fine-grained control of plug-in goals within the executions block, reporting configures goals within reportSet elements. And the subtler difference is that a plugin configuration under the reporting element works as build plugin configuration, although the opposite is not true (a build plugin configuration does not affect a reporting plugin).
Possibly the only item under the reporting element that would not be familiar to someone who understood the build element is the Boolean excludeDefaults element. This element signifies to the site generator to exclude reports normally generated by default. When a site is generated via the site build cycle, a Project Info section is placed in the left-hand menu, chock full of reports, such as the Project Team report or Dependencies list report. These report goals are generated by maven-project-info-reports-plugin. Being a plugin like any other, it may also be suppressed in the following, more verbose, way, which effectively turns off project-info reports.
<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"> ... <reporting> <outputDirectory>${basedir}/target/site</outputDirectory> <plugins> <plugin> <artifactId>maven-project-info-reports-plugin</artifactId> <version>2.0.1</version> <reportSets> <reportSet></reportSet> </reportSets> </plugin> </plugins> </reporting> ... </project>
The other difference is the outputDirectory element under plugin. In the case of reporting, the output directory is ${basedir}/target/site by default.
It is important to keep in mind that an individual plugin may have multiple goals. Each goal may have a separate configuration. Report sets configure execution of a report plugin's goals. Does this sound familiar - deja-vu? The same thing was said about build's execution element with one difference: you cannot bind a report to another phase. Sorry.
For example, suppose you wanted to configure the javadoc:javadoc goal to link to "http://java.sun.com/j2se/1.5.0/docs/api/", but only the javadoc goal (not the goal maven-javadoc-plugin:jar). We would also like this configuration passed to its children, and set inherited to true. The reportSet would resemble the following:
<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"> ... <reporting> <plugins> <plugin> ... <reportSets> <reportSet> <id>sunlink</id> <reports> <report>javadoc</report> </reports> <inherited>true</inherited> <configuration> <links> <link>http://java.sun.com/j2se/1.5.0/docs/api/</link> </links> </configuration> </reportSet> </reportSets> </plugin> </plugins> </reporting> ... </project>
Between build executions and reporting reportSets, it should be clear now as to why they exist. In the simplest sense, they drill down in configuration. The POM must have a way not only to configure plugins, but they also must configure individual goals of those plugins. That is where these elements come in, giving the POM ultimate granularity in control of its build destiny.
Although the above information is enough to get a firm grasp on POM authoring, there are far more elements to make developer's live easier. Many of these elements are related to site generation, but like all POM declarations, they may be used for anything, depending upon how certain plugins use it. The following are the simplest elements:
<licenses> <license> <name>Apache License, Version 2.0</name> <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url> <distribution>repo</distribution> <comments>A business-friendly OSS license</comments> </license> </licenses>
Licenses are legal documents defining how and when a project (or parts of a project) may be used. Note that a project should list only licenses that may apply directly to this project, and not list licenses that apply to this project's dependencies. Maven currently does little with these documents other than displays them on generated sites. However, there is talk of flexing for different types of licenses, forcing users to accept license agreements for certain types of (non open source) projects.
Most projects are run by some sort of organization (business, private group, etc.). Here is where the most basic information is set.
<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"> ... <organization> <name>Codehaus Mojo</name> <url>http://mojo.codehaus.org</url> </organization> </project>
All projects consist of files that were created, at some time, by a person. Like the other systems that surround a project, so to do the people involved with a project have a stake in the project. Developers are presumably members of the project's core development. Note that, although an organization may have many developers (programmers) as members, it is not good form to list them all as developers, but only those who are immediately responsible for the code. A good rule of thumb is, if the person should not be contacted about the project, they need not be listed here.
<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"> ... <developers> <developer> <id>jdoe</id> <name>John Doe</name> <email>jdoe@example.com</email> <url>http://www.example.com/jdoe</url> <organization>ACME</organization> <organizationUrl>http://www.example.com</organizationUrl> <roles> <role>architect</role> <role>developer</role> </roles> <timezone>America/New_York</timezone> <properties> <picUrl>http://www.example.com/jdoe/pic</picUrl> </properties> </developer> </developers> ... </project>
Contributors are like developers yet play an ancillary role in a project's lifecycle. Perhaps the contributor sent in a bug fix, or added some important documentation. A healthy open source project will likely have more contributors than developers.
<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"> ... <contributors> <contributor> <name>Noelle</name> <email>some.name@gmail.com</email> <url>http://noellemarie.com</url> <organization>Noelle Marie</organization> <organizationUrl>http://noellemarie.com</organizationUrl> <roles> <role>tester</role> </roles> <timezone>America/Vancouver</timezone> <properties> <gtalk>some.name@gmail.com</gtalk> </properties> </contributor> </contributors> ... </project>
Contributors contain the same set of elements than developers sans the id element.
This defines the defect tracking system (Bugzilla, TestTrack, ClearQuest, etc) used. Although there is nothing stopping a plugin from using this information for something, its primarily used for generating project documentation.
<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"> ... <issueManagement> <system>Bugzilla</system> <url>http://127.0.0.1/bugzilla/</url> </issueManagement> ... </project>
Continuous integration build systems based upon triggers or timings (such as, hourly or daily) have grown in favor over manual builds in the past few years. As build systems have become more standardized, so have the systems that run the trigger those builds. Although the majority of the configuration is up to the specific program used (Continuum, Cruise Control, etc.), there are a few configurations which may take place within the POM. Maven has captured a few of the recurring settings within the set of notifier elements. A notifier is the manner in which people are notified of certain build statuses. In the following example, this POM is setting a notifier of type mail (meaning email), and configuring the email address to use on the specified triggers sendOnError, sendOnFailure, and not sendOnSuccess or sendOnWarning.
<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"> ... <ciManagement> <system>continuum</system> <url>http://127.0.0.1:8080/continuum</url> <notifiers> <notifier> <type>mail</type> <sendOnError>true</sendOnError> <sendOnFailure>true</sendOnFailure> <sendOnSuccess>false</sendOnSuccess> <sendOnWarning>false</sendOnWarning> <configuration><address>continuum@127.0.0.1</address></configuration> </notifier> </notifiers> </ciManagement> ... </project>
Mailing lists are a great tool for keeping in touch with people about a project. Most mailing lists are for developers and users.
<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"> ... <mailingLists> <mailingList> <name>User List</name> <subscribe>user-subscribe@127.0.0.1</subscribe> <unsubscribe>user-unsubscribe@127.0.0.1</unsubscribe> <post>user@127.0.0.1</post> <archive>http://127.0.0.1/user/</archive> <otherArchives> <otherArchive>http://base.google.com/base/1/127.0.0.1</otherArchive> </otherArchives> </mailingList> </mailingLists> ... </project>
SCM (Software Configuration Management, also called Source Code/Control Management or, succinctly, version control) is an integral part of any healthy project. If your Maven project uses an SCM system (it does, doesn't it?) then here is where you would place that information into the POM.
<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"> ... <scm> <connection>scm:svn:http://127.0.0.1/svn/my-project</connection> <developerConnection>scm:svn:https://127.0.0.1/svn/my-project</developerConnection> <tag>HEAD</tag> <url>http://127.0.0.1/websvn/my-project</url> </scm> ... </project>
scm:[provider]:[provider_specific]
Where provider is the type of SCM system. For example, connecting to a CVS repository may look like this:
scm:cvs:pserver:127.0.0.1:/cvs/root:my-project
The POM may have certain prerequisites in order to execute correctly. For example, perhaps there was a fix in Maven 2.0.3 that you need in order to deploy using sftp.
In Maven 3, use Maven Enforcer Plugin's requireMavenVersion rule, or other rules to check other aspects.
In Maven 2, here is where you give the prerequisites to building: if these are not met, Maven will fail the build before even starting. The only element that exists as a prerequisite in POM 4.0 is the maven element, which takes a minimum version number. It is checked with Maven 2, it is not any more in Maven 3.
<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"> ... <prerequisites> <maven>2.0.6</maven> </prerequisites> ... </project>
Repositories are collections of artifacts which adhere to the Maven repository directory layout. In order to be a Maven repository artifact, a POM file must live within the structure $BASE_REPO/groupId/artifactId/version/artifactId-version.pom. $BASE_REPO can be local (file structure) or remote (base URL); the remaining layout will be the same. Repositories exist as a place to collect and store artifacts. Whenever a project has a dependency upon an artifact, Maven will first attempt to use a local copy of the specified artifact. If that artifact does not exist in the local repository, it will then attempt to download from a remote repository. The repository elements within a POM specify those alternate repositories to search.
The repository is one of the most powerful features of the Maven community. The default central Maven repository lives on https://repo.maven.apache.org/maven2/. Another source for artifacts not yet in iBiblio is the Codehaus snapshots repo.
<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> <id>codehausSnapshots</id> <name>Codehaus Snapshots</name> <url>http://snapshots.maven.codehaus.org/maven2</url> <layout>default</layout> </repository> </repositories> <pluginRepositories> ... </pluginRepositories> ... </project>
Repositories are home to two major types of artifacts. The first are artifacts that are used as dependencies of other artifacts. These are the majority of plugins that reside within central. The other type of artifact is plugins. Maven plugins are themselves a special type of artifact. Because of this, plugin repositories may be separated from other repositories (although, I have yet to hear a convincing argument for doing so). In any case, the structure of the pluginRepositories element block is similar to the repositories element. The pluginRepository elements each specify a remote location of where Maven can find new plugins.
Distribution management acts precisely as it sounds: it manages the distribution of the artifact and supporting files generated throughout the build process. Starting with the last elements first:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> ... <distributionManagement> ... <downloadUrl>http://mojo.codehaus.org/my-project</downloadUrl> <status>deployed</status> </distributionManagement> ... </project>
Where as the repositories element specifies in the POM the location and manner in which Maven may download remote artifacts for use by the current project, distributionManagement specifies where (and how) this project will get to a remote repository when it is deployed. The repository elements will be used for snapshot distribution if the snapshotRepository is not defined.
<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>
More than distribution to the repositories, distributionManagement is responsible for defining how to deploy the project's site and documentation.
<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> ... <site> <id>mojo.website</id> <name>Mojo Website</name> <url>scp://beaver.codehaus.org/home/projects/mojo/public_html/</url> </site> ... </distributionManagement> ... </project>
<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> ... <relocation> <groupId>org.apache</groupId> <artifactId>my-project</artifactId> <version>1.0</version> <message>We have moved the Project under Apache</message> </relocation> ... </distributionManagement> ... </project>
Projects are not static; they are living things (or dying things, as the case may be). A common thing that happens as projects grow, is that they are forced to move to more suitable quarters. For example, when your next wildly successful open source project moves under the Apache umbrella, it would be good to give your users as heads-up that the project is being renamed to org.apache:my-project:1.0. Besides specifying the new address, it is also good form to provide a message explaining why.
A new feature of the POM 4.0 is the ability of a project to change settings depending on the environment where it is being built. A profile element contains both an optional activation (a profile trigger) and the set of changes to be made to the POM if that profile has been activated. For example, a project built for a test environment may point to a different database than that of the final deployment. Or dependencies may be pulled from different repositories based upon the JDK version used. The elements of profiles are as follows:
<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"> ... <profiles> <profile> <id>test</id> <activation>...</activation> <build>...</build> <modules>...</modules> <repositories>...</repositories> <pluginRepositories>...</pluginRepositories> <dependencies>...</dependencies> <reporting>...</reporting> <dependencyManagement>...</dependencyManagement> <distributionManagement>...</distributionManagement> </profile> </profiles> </project>
Activations are the key of a profile. The power of a profile comes from its ability to modify the basic POM only under certain circumstances. Those circumstances are specified via an activation element.
<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"> ... <profiles> <profile> <id>test</id> <activation> <activeByDefault>false</activeByDefault> <jdk>1.5</jdk> <os> <name>Windows XP</name> <family>Windows</family> <arch>x86</arch> <version>5.1.2600</version> </os> <property> <name>sparrow-type</name> <value>African</value> </property> <file> <exists>${basedir}/file2.properties</exists> <missing>${basedir}/file1.properties</missing> </file> </activation> ... </profile> </profiles> </project>
Activation occurs when one or more of the specified criteria have been met. When the first positive result is encountered, processing stops and the profile is marked as active.
The activation element is not the only way that a profile may be activated. The settings.xml file's activeProfile element may contain the profile's id. They may also be activated explicitly through the command line via a comma separated list after the -P flag (e.g. -P codecoverage).
To see which profile will activate in a certain build, use the maven-help-plugin.
mvn help:active-profiles
As mentioned above, the reason for the two types of build elements reside in the fact that it does not make sense for a profile to configure build directories or extensions as it does in the top level of the POM. Reguardless of in which environment the project is built, some values will remain constant, such as the directory structure of the source code. If you find your project needing to keep two sets of code for different environments, it may be prudent to investigate refactoring the project into two or more separate projects.
The Maven POM is big. However, its size is also a testament to its versatility. The ability to abstract all of the aspects of a project into a single artifact is powerful, to say the least. Gone are the days of dozens of disparate build scripts and scattered documentation concerning each individual project. Along with Maven's other stars that make up the Maven galaxy - a well defined build lifecycle, easy to write and maintain plugins, centralized repositories, system-wide and user-based configurations, as well as the increasing number of tools to make developers' jobs easier to maintain complex projects - the POM is the large, but bright, center.
Aspects of this guide were originally published in the Maven 2 Pom Demystified.