Maven: POM Fundamentals
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Apache Maven 3.10.0 (released on 27 September 2026). Every example marked as run was executed on JDK 26
on 2026-10-16, on macOS, in a scratch directory, with Maven 3.10.0 unless the text names another version; the 3.9.16 and 4.0.0-rc-7
(release candidate) runs are called out where their behaviour differs. Output is shown as printed, with the long scratch path shortened to
<scratch> or <dir>, with ... on a line of its own where lines were left out, and with the lines made only of dashes and some empty [INFO] lines dropped without a marker. The examples use the running scenario of this
section, tidepool (see the section landing page): a parent POM
io.tidepool:tidepool-parent:1.0.0 that aggregates tidepool-model, tidepool-engine and tidepool-cli. Several examples need a
parent and a child, or a repository, to show a behaviour; those are small throw-away projects and are described where they appear.
The POM is the one file every Maven project has, and almost every other page of this section is about one of its elements. This page is the map: what a POM is, the smallest one that works and what Maven adds around it, the groups of elements, and then the parts that are not covered elsewhere: coordinates and packaging as they appear in the file, properties and the variables Maven substitutes into the file (interpolation), the descriptive and environmental information about a project, and the model version. The big structural elements have their own pages: parents, aggregation and the effective POM, dependencies, dependencyManagement, plugins and the lifecycle, and profiles. The ideas behind the names are on Philosophy, Coordinates & Directory Layout.
What a POM is
POM stands for Project Object Model. It is an XML representation of a Maven project held in a file named pom.xml. The Maven site calls
it a one-stop shop for everything about the project: the dependencies, the plugins and goals that can run, the build profiles, and also
the version, the description, the developers, the mailing lists, the licenses, the issue tracker and the organization. In the Maven world
a project does not even need to contain code, only a pom.xml; the parent POM of a multi-module build is exactly that.
The POM is declarative. The Maven documentation puts it as: the POM is the "who", "what" and "where" of a project, while the build
lifecycle is the "when" and "how". A POM states its configuration; it does not list the steps. That difference from an Ant build.xml
(a procedural list of targets and tasks, see Ant: Build File Structure)
has a practical consequence the Maven site points out: if something outside the POM makes the lifecycle skip a plugin execution, the
plugins that do run still do their work, whereas in an Ant file a task usually depends on the lines that ran before it. The POM can still
affect the flow of the build (that is what binding a plugin goal to a phase does), but it does so by declaration.
Source: Maven: POM Reference, The Basics
The minimal POM
The minimum that Maven accepts is the project element with four children: modelVersion (set to 4.0.0), groupId, artifactId
and version. Those three coordinates together form the project’s fully qualified artifact name, groupId:artifactId:version. Everything
else has a default. This is the whole of the tidepool minimal project:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-minimal</artifactId>
<version>1.0.0</version>
</project>
With no sources and no other file in the directory, mvn -B -ntp package on Maven 3.10.0 builds (lines about the JAR; the lines for
resources, compiler and surefire say that there is nothing to do):
[INFO] --- compiler:3.16.0:compile (default-compile) @ tidepool-minimal ---
[INFO] No sources to compile
...
[INFO] --- surefire:3.6.0:test (default-test) @ tidepool-minimal ---
[INFO] No tests to run.
[INFO]
[INFO] --- jar:3.5.1:jar (default-jar) @ tidepool-minimal ---
[WARNING] JAR will be empty - no content was marked for inclusion!
[INFO] Building jar: <scratch>/min/target/tidepool-minimal-1.0.0.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
Six lines of XML produced a build that ran the whole default lifecycle for the jar packaging, because the omitted packaging is jar,
and the lifecycle mapping of jar supplies the plugins. That is convention over configuration in its most compact form, and it is also a
useful test when a build behaves oddly: reduce the POM to this and add elements back until the behaviour reappears.
What Maven adds around the minimal POM
The effective POM is the POM as Maven actually uses it, after inheritance from the super POM, interpolation and the default lifecycle
bindings have been applied. help:effective-pom prints it. For the six-line POM above:
mvn -B -ntp -q help:effective-pom -Doutput=effective.xml
wc -l effective.xml
printed 219 lines on Maven 3.10.0, 234 on Maven 3.9.16 and 172 on Maven 4.0.0-rc-7 (release candidate). The start of the 3.10.0 file
shows the three kinds of thing that are added (a ... marks the lines that were left out, here the plugin executions):
<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>io.tidepool</groupId>
<artifactId>tidepool-minimal</artifactId>
<version>1.0.0</version>
<properties>
<project.build.outputTimestamp>1980-02-01T00:00:00Z</project.build.outputTimestamp>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>
...
<build>
<sourceDirectory><dir>/src/main/java</sourceDirectory>
<scriptSourceDirectory><dir>/src/main/scripts</scriptSourceDirectory>
<testSourceDirectory><dir>/src/test/java</testSourceDirectory>
<outputDirectory><dir>/target/classes</outputDirectory>
<testOutputDirectory><dir>/target/test-classes</testOutputDirectory>
<resources>
<resource>
<directory><dir>/src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory><dir>/src/test/resources</directory>
</testResource>
</testResources>
<directory><dir>/target</directory>
<finalName>tidepool-minimal-1.0.0</finalName>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>default-clean</id>
<phase>clean</phase>
...
-
The super POM contributes the Central repository (for dependencies and for plugins), the directory layout and the default
finalName. The standard layout is not a convention written somewhere in the documentation; it is<build>elements with default values. -
The packaging contributes the plugin versions and the executions, with the
default-execution ids (see Build Lifecycle). The defaultjaris not written back to the effective POM, so apackagingelement is absent from the file. -
The three
<properties>are new in Maven 3.10.0: its super POM sets the source and reporting encodings and a fixedproject.build.outputTimestamp, which makes archives reproducible by default. The 3.10.0 release notes list these changes, together with the removal of the<pluginManagement>section and of therelease-profilefrom the super POM. The 3.9.16 effective POM has no<properties>at all and does manage plugin versions. The consequence for encodings is visible in a build: with noproject.build.sourceEncoding, Maven 3.9.16 printedFile encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!when compiling andUsing platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!for resources, while 3.10.0 printed neither andhelp:evaluatereportedUTF-8for the expression. Maven 4.0.0-rc-7 (release candidate) also reportedUTF-8.
The rest of the difference between the three Maven versions (the 3.9.16 super POM’s <pluginManagement>, for instance) is explained on
Inheritance, Aggregation & the Effective POM.
When a required element is missing
A POM that leaves out a required element does not build. Each row below is the minimal POM with the one element removed; the message is
from mvn -B -ntp validate on Maven 3.10.0, and the last column says what 4.0.0-rc-7 (release candidate) did with the same file.
| Element removed | Maven 3.10.0 message | Maven 4.0.0-rc-7 (release candidate) |
|---|---|---|
|
|
builds: |
|
|
fails with 2 problems, the same message with |
|
|
fails with 2 problems |
|
|
fails with 2 problems |
|
|
fails with 2 problems (the same message twice) |
The 3.10.0 output for the missing artifactId, in full, shows the shape of every such failure (the first block of lines names the problem, the
[Help 1] lines at the end are the generic pointer to the Maven wiki):
[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[FATAL] 'artifactId' is missing. @ line 1, column 52
@
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]
[ERROR] The project io.tidepool:[unknown-artifact-id]:1.0.0 (<dir>/pom.xml) has 1 error
[ERROR] 'artifactId' is missing. @ line 1, column 52
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
groupId and version may be omitted when a <parent> supplies them (they are inherited; artifactId and packaging never are), which
is why a module POM is usually shorter than the minimal one. A misspelt element name is a different failure: a POM with <dependecies>
instead of <dependencies> stops with Malformed POM <dir>/pom.xml: Unrecognised tag: 'dependecies' on 3.9.16 and 3.10.0, and with
Unable to read model: Unrecognised tag: '{http://maven.apache.org/POM/4.0.0}dependecies' on 4.0.0-rc-7. Maven does not validate against the
XSD, though: the same POM with no xmlns at all built on all three versions. The namespace and the xsi:schemaLocation matter for editors
and tools that validate and complete, not for Maven itself.
The elements under project
The POM reference lists the elements that may appear directly under project, in five groups. Knowing the groups is enough to find where
a topic lives:
| Group | Elements | Covered on |
|---|---|---|
The basics |
|
this page |
|
Dependencies, Scopes & Mediation, Dependency Management & BOMs |
|
|
Inheritance, Aggregation & the Effective POM, Multi-Module Builds & the Reactor |
|
|
this page |
|
Build settings |
|
Plugins & Their Configuration, Resources & Filtering, Site & Reporting |
More project information |
|
this page |
Environment settings |
|
this page |
|
||
|
this page (relocation, status), Install, Deploy & Distribution Management |
|
Profiles |
|
The POM reference page says that modelVersion "is currently the only supported POM version" and is always required. That sentence
predates Maven 4, which adds model version 4.1.0; the last section of this page runs both.
Coordinates and packaging in the file
The three coordinates act, in the words of the POM reference, like an address and a timestamp in one: they mark a specific place in a
repository. groupId is generally unique among an organization or a project, and does not have to match the Java package structure
(though following it is good practice); in a repository the dots become directory separators. artifactId is the name the project is
known by and, with the groupId, the key that separates it from every other project. version separates incarnations of the same
project, and is also a directory in the repository. A project with groupId org.codehaus.mojo, artifactId my-project and version
1.0 lives in $M2_REPO/org/codehaus/mojo/my-project/1.0. The reasoning behind the scheme, classifiers and the type-versus-extension
difference are on the Philosophy page; the
format of versions and ranges is on Versions, SNAPSHOTs & Ranges.
packaging
packaging is the fourth coordinate of the project’s identity. When it is absent it is jar. Its value is not free text: it is the
role-hint of a lifecycle mapping, which fixes the list of plugin goals that run in each phase of the default lifecycle. The POM reference
lists these core packaging values: pom, jar, maven-plugin, ejb, war, ear, rar. Plugins and extensions can contribute more; the
Maven Central requirements list par, aar and apklib as further examples seen in the wild. The default bindings per packaging are on
Build Lifecycle, and the archives themselves on
Packaging JARs, WARs & Manifests.
Two runs show what the value changes. A pom project has no code to build and the lifecycle mapping contains no plugin for the early phases, so
mvn -B -ntp package on the tidepool-parent POM runs nothing:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
</project>
[INFO] Building tidepool-parent 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
and install ran a single plugin goal, which copies the POM itself into the repository (this run used a throw-away
-Dmaven.repo.local, so nothing was added to the real local repository):
[INFO] --- install:3.2.0:install (default-install) @ tidepool-parent ---
[INFO] Installing <dir>/pom.xml to <dir>/repo/io/tidepool/tidepool-parent/1.0.0/tidepool-parent-1.0.0.pom
[INFO] BUILD SUCCESS
A project that declares war gets the war plugin bound to package, and with no web descriptor that goal fails, which is the classic
first error of a new web project (the war plugin 3.5.1 on Maven 3.10.0):
[INFO] --- war:3.5.1:war (default-war) @ tidepool-web ---
[INFO] Packaging webapp
[INFO] Assembling webapp [tidepool-web] in [<dir>/target/tidepool-web-1.0.0]
[INFO] Processing war project
[INFO] Building war: <dir>/target/tidepool-web-1.0.0.war
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-war-plugin:3.5.1:war (default-war) on project tidepool-web: Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode) -> [Help 1]
The two fixes are to add a src/main/webapp/WEB-INF/web.xml or to set failOnMissingWebXml to false in the plugin
configuration (this page does not run the second one; see Plugins & Their Configuration).
And a value that is not a known packaging stops before anything runs, as in the table above (Unknown packaging: jarr).
Source: Maven: POM Reference, Packaging
finalName
<build><finalName> is the name of the file Maven creates in target, without its extension. Its default is
${project.artifactId}-${project.version} (the effective POM above shows tidepool-minimal-1.0.0). It is the one coordinate-related setting
that people change on a project that has to produce a fixed file name, for example a web application called ROOT.war or a CLI called
tidepool.jar, and it is also the source of a classic misunderstanding: it renames the file in target, not the artifact in a repository.
A tidepool-model project with <finalName>tidepool</finalName>:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-model</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<finalName>tidepool</finalName>
</build>
</project>
built with mvn -B -ntp -q -Dmaven.repo.local=<scratch>/repo install (the -q hides the build log), produced
target/tidepool.jar
repo/io/tidepool/tidepool-model/1.0.0/_remote.repositories
repo/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar
repo/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom
repo/io/tidepool/tidepool-model/maven-metadata-local.xml
The file in target is called tidepool.jar, and the file installed in the repository is tidepool-model-1.0.0.jar. A repository has
a fixed layout (artifactId-version[-classifier].extension below the coordinates' directory) and a consumer finds a file by
coordinates, so the install and deploy plugins ignore finalName. Anyone who needs the plain name, such as a Dockerfile that copies
target/tidepool.jar, uses finalName; anyone who publishes does not need to care about it.
Source: Maven: POM Reference, Packaging
Properties
Maven properties are value placeholders, like properties in Ant. They are declared in the <properties> element, and their value is
available anywhere in a POM through the notation ${name}; plugins also use them as defaults for their parameters. The POM reference’s own
example declares maven.compiler.source and maven.compiler.target as 1.7. That example no longer works on a current JDK: with those
two properties set to 1.7 on JDK 26, compiler plugin 3.16.0 (Maven 3.10.0) printed
[WARNING] bootstrap class path is not set in conjunction with -source 7
[ERROR] COMPILATION ERROR :
[ERROR] Source option 7 is no longer supported. Use 8 or later.
[ERROR] Target option 7 is no longer supported. Use 8 or later.
and the build failed. Use maven.compiler.release (as the examples in this section do) or a toolchain;
Compiling & Toolchains explains the difference.
The five styles of property
The POM reference names five kinds of property. The table has the reference’s description and, in the last column, what the expression gave in
a run: a throw-away POM (the coordinates of tidepool-model) declared the <properties> shown below, and help:effective-pom printed the
interpolated values. The Java system property was java.specification.version and os.name, and the environment variable was HOME.
<properties>
<station>Tarifa</station>
<by.project>${project.groupId}:${project.artifactId}:${project.version}</by.project>
<by.property>station is ${station}</by.property>
<by.system>${java.specification.version} on ${os.name}</by.system>
<by.env>${env.HOME}</by.env>
<by.settings>${settings.localRepository}</by.settings>
</properties>
| Style | Meaning | Result in the run |
|---|---|---|
|
the shell environment variable |
|
|
the dot-notated path of an element in the POM |
|
|
the dot-notated path of an element in |
|
Java system properties |
anything in |
|
|
a name declared in |
|
The reference adds a detail about Windows that this page cannot run (not run here, macOS): environment variable names are case-insensitive
there, but the lookup of the property is case-sensitive, so ${env.Path} and ${env.PATH} are different expressions, and names are normalised to
upper case.
The settings.* row is the odd one. The model builder reference explains why: after interpolation, ${...} content can remain in the model
and be evaluated later when plugin parameters are set, and it names the settings.* values for the settings model as the main case. So
settings.* is not a POM-interpolation prefix in practice; it is available to plugin parameters. help:evaluate uses the same expression
evaluator as plugin parameters, and it does resolve it:
mvn -B -ntp -q help:evaluate -Dexpression=settings.localRepository -DforceStdout
<home>/.m2/repository
(The <home> stands for the home directory.) Reading settings.xml values is therefore possible in a plugin’s <configuration>, but not in a
property of the POM or in a dependency version.
Source: Maven: POM Reference, Properties
Properties Maven and its plugins know
Some property names are read by Maven or by the default plugins, so declaring them changes behaviour:
-
project.build.sourceEncodingandproject.reporting.outputEncodingset the encoding of source files and reports. The model builder reference explains that they are POM properties "looking like POM content" that were not added to the model, to remain compatible with older versions; their default was the platform encoding (andUTF-8for reports) until Maven 3.10.0 setUTF-8in the super POM. -
project.build.outputTimestampmakes archives reproducible; Maven 3.10.0 sets it to1980-02-01T00:00:00Zin the super POM (see above). It is the subject of Security & Reproducible Builds. -
maven.compiler.release,maven.compiler.sourceandmaven.compiler.targetare the user properties of the compiler plugin. -
maven.build.timestamp.formatcustomises the build timestamp (below). -
project.directory, available since Maven 3.5.0, changes the path that is appended when a child inherits URLs (see URLs and SCM paths in a child).
The POM reference warns in a comment of its own example not to start your own properties with project.: those names are reserved for
Maven and "will become elements in a future POM definition". Section "Precedence" below shows what happens when you do.
Precedence
A property can be declared in several places at once, and the values are not equal. A POM with the following properties (the coordinates are
those of tidepool-model) was run with and without -D options on the command line:
<properties>
<station>Tarifa</station>
<java.specification.version>from-the-pom</java.specification.version>
<env.USER>from-the-pom</env.USER>
<project.artifactId>from-the-pom</project.artifactId>
<result.station>${station}</result.station>
<result.java>${java.specification.version}</result.java>
<result.env>${env.USER}</result.env>
<result.artifact>${project.artifactId}</result.artifact>
</properties>
The table lists the value each result.* property had in the effective POM:
| Case | Maven 3.9.16 | Maven 3.10.0 | Maven 4.0.0-rc-7 (release candidate) | Winner |
|---|---|---|---|---|
|
|
|
|
the POM |
|
|
|
|
the command line |
|
|
|
|
the POM beats the JVM system property |
|
|
|
|
the command line |
|
|
|
|
the POM beats the environment |
|
|
|
|
the model in 3.x, the property in 4 |
|
|
|
|
the model in 3.x, the command line in 4 |
So, on all three versions, user properties from -D win over properties in the POM, and properties in the POM win over system properties
and environment variables, which is what lets a POM supply a default that the command line overrides. The row that differs is the one the
POM reference tells you to avoid: a user-declared project.artifactId is ignored by Maven 3.x (the model value is used first, matching the model
builder reference, which lists project.* first) and honoured by Maven 4.0.0-rc-7. Declaring properties under the project. prefix therefore
behaves differently between Maven 3 and Maven 4 and should not be done.
Source: Maven: POM Reference, Properties
Overriding from the command line
Because -D wins, a POM property is a build parameter. Declare <greeting>from-the-pom</greeting>, use ${greeting} in a place that
accepts properties, and call mvn -Dgreeting=from-the-command-line .... Beware that the same mechanism makes -D a global switch: a system
property given with -D is visible to every module and every plugin of the build. The CLI options themselves are on
Running Maven (CLI), and
profiles are the structured way to switch whole groups of properties.
Project model variables
Any single-valued field of the POM model can be referenced with the prefix project.: ${project.groupId}, ${project.version},
${project.build.sourceDirectory} and so on. The POM reference lists the fields. help:evaluate prints the value Maven has for any of them,
including values the POM never wrote. For a child of the parent used in the "Project information" section below (it declares a <parent>
and an artifactId, and nothing else):
mvn -B -ntp -q help:evaluate -Dexpression=project.groupId -DforceStdout
gave these values on Maven 3.10.0 (one expression per run; the output has no newline, the table puts the results together):
| Expression | Value |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
An expression that names a list or a complex object (project.developers, project.modules, project.properties) prints it as XML. On
JDK 26 that also printed WARNING: A terminally deprecated method in sun.misc.Unsafe has been called on standard error, a message from the
XML serialisation library the help plugin uses and not from the build; it goes away by asking for a scalar field.
The prefixes pom. and the empty prefix (${artifactId}, ${version}) work as aliases but are deprecated, and the introduction to the POM
says they "should not be used". The runs agree on the deprecation and differ on what happens next. The throw-away POM above also had
<by.pom-prefix>${pom.artifactId}</by.pom-prefix> and <by.bare>${artifactId}</by.bare>; mvn -B -ntp validate printed on Maven 3.9.16 and 3.10.0:
[WARNING] Some problems were encountered while building the effective model for io.tidepool:tidepool-model:jar:1.0.0
[WARNING] The expression ${pom.artifactId} is deprecated. Please use ${project.artifactId} instead.
[WARNING] The expression ${artifactId} is deprecated. Please use ${project.artifactId} instead.
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
and both expressions were replaced by tidepool-model. Maven 4.0.0-rc-7 (release candidate) printed no warning, still replaced
${artifactId} and left ${pom.artifactId} untouched in the effective POM. Use project. everywhere: it is the only prefix that behaves
the same in all three versions.
Special variables
A few variables are not fields of the model. The introduction to the POM lists three, and the 3.10.0 release notes add the root directory:
| Variable | Meaning | Since |
|---|---|---|
|
the directory that the current project resides in |
not stated |
|
the same directory, as a URI |
Maven 2.1.0 |
|
the timestamp of the start of the build, in UTC |
Maven 2.1.0-M1 |
|
the project’s root directory, the one that contains the |
a regular property in Maven 3.10.0 (and in Maven 4) |
|
the root directory of a multi-module build, set by the |
set by the launcher |
A two-project build (a parent in <scratch> with a module tidepool-model, and an empty .mvn directory next to the parent POM to mark the
root) declared in the parent’s <properties>
<maven.build.timestamp.format>yyyy-MM-dd'T'HH:mm'Z'</maven.build.timestamp.format>
<v.basedir>${project.basedir}</v.basedir>
<v.baseUri>${project.baseUri}</v.baseUri>
<v.timestamp>${maven.build.timestamp}</v.timestamp>
<v.multiModule>${maven.multiModuleProjectDirectory}</v.multiModule>
<v.rootDirectory>${project.rootDirectory}</v.rootDirectory>
and mvn -B -ntp -q -pl tidepool-model help:effective-pom printed these values for the module (run on all three versions; the timestamp was
produced at 14:38 local time, UTC+2):
<v.baseUri>file://<dir>/tidepool-model/</v.baseUri>
<v.basedir><dir>/tidepool-model</v.basedir>
<v.multiModule><dir></v.multiModule>
<v.rootDirectory><dir></v.rootDirectory>
<v.timestamp>2026-10-16T12:38Z</v.timestamp>
On Maven 3.10.0 and 4.0.0-rc-7 (release candidate) all five were resolved exactly like this. Maven 3.9.16 printed the same lines
except <v.rootDirectory>${project.rootDirectory}</v.rootDirectory>, left unresolved: that variable arrived in 3.10.0, where, according to the
release notes, session.topDirectory, session.rootDirectory and project.rootDirectory were "promoted to regular Maven properties,
usable in interpolation and in profile activation". ${project.basedir} is the module’s own directory, and ${project.rootDirectory} the root of the
whole build, so a path to a file shared by all modules is ${project.rootDirectory}/config/checkstyle.xml and not a ../.. chain.
Three details about the timestamp. The format is that of SimpleDateFormat and defaults to yyyy-MM-dd'T'HH:mm:ss'Z'; the example changed it
by declaring maven.build.timestamp.format, and the output has no seconds. The timestamp is in UTC, hence the two hours of difference above. And the
timestamp changes with every build, so putting it in an artifact (a manifest entry, a filtered resource) makes the artifact different every time; the
reproducible alternative is a fixed project.build.outputTimestamp (see
Security & Reproducible Builds).
Finally, these variables are POM interpolation features, not plugin expressions: help:evaluate -Dexpression=project.baseUri and
-Dexpression=maven.build.timestamp both printed null object or invalid expression, because the plugin evaluator does not know them. To see their
values use help:effective-pom, as above.
Interpolation
Interpolation is the replacement of ${...} by a computed value. The model builder performs it as one step of building the effective
model, and its place in the sequence explains most surprises:
Profile activation comes before interpolation, so a file-based profile activation condition can use only ${basedir}, system properties and user
properties. Inheritance comes before interpolation too, which is the next section’s point.
Late binding: the child’s value wins
Because variables are processed after inheritance, a parent that uses a variable gets the definition of the child, not its own. The introduction to the POM says so, and a small experiment shows it. The parent declares and uses a property:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<station>Tarifa</station>
<banner>Tides for ${station} (built from ${project.artifactId})</banner>
</properties>
</project>
and the child, which lives in the child directory below it, redefines station:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>tidepool-child</artifactId>
<properties>
<station>Cadiz</station>
</properties>
</project>
mvn -B -ntp -q help:evaluate -Dexpression=banner -DforceStdout printed, on all three versions, in the child directory
Tides for Cadiz (built from tidepool-child)
and in the parent directory Tides for Tarifa (built from tidepool-parent). Neither station nor project.artifactId was read in the parent:
the string was inherited unexpanded and expanded in the context of the project being built. This is the mechanism behind template-style
parents ("define the version once, let children set the name") and behind bugs where a child’s property silently changes what a shared
parent does.
Unresolved placeholders and typos
A ${...} that matches nothing is not an error by itself. The effective POM of the properties experiment kept
<by.missing>${no.such.property}</by.missing> as a literal string on all three versions. The error appears where the value is needed. The most common case
is a misspelt property in a dependency version:
<properties>
<commons-csv.vesion>1.14.1</commons-csv.vesion>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>${commons-csv.version}</version>
</dependency>
</dependencies>
(vesion in the property name.) The build stops before resolving anything, on Maven 3.9.16 and 3.10.0 with:
[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] 'dependencies.dependency.version' for org.apache.commons:commons-csv:jar must be a valid version but is '${commons-csv.version}'. @ line 15, column 16
Maven 4.0.0-rc-7 (release candidate) reports the same problem twice with the coordinates spelt out
('dependencies.dependency.version' for groupId='org.apache.commons', artifactId='commons-csv', type='jar' must be a valid version but is '${commons-csv.version}').
Read the placeholder in the message literally: it is the name the POM asked for, so compare it with the declaration.
No escape for a literal dollar
There is no escape syntax for a literal ${name} in a POM that Maven would not also replace. The experiment declared three properties after
station=Tarifa: <esc.dollar>$${station}</esc.dollar>, <esc.backslash>\${station}</esc.backslash> and <esc.plain>${station}</esc.plain>. The effective POM
had $Tarifa, \Tarifa and Tarifa on all three versions: the placeholder was replaced anyway and a stray character was left. Where a literal is needed in a filtered resource,
the resources plugin has its own escape parameter (escapeString, see
Resources & Filtering),.
Interpolation in POMs read from a repository
Interpolation of a project’s own POM can use anything: command-line properties, environment variables, system properties. A POM that Maven
reads from a repository, because it is a dependency, a parent or an imported BOM, is a different matter, and Maven 3.10.0 changed the rule. The
release notes list, under "Security Hardening", that such a model "contributes less to the build: restricted interpolation, profile activation limited to platform-derived criteria,
recessive repository merging and rejection of system scoped dependencies", with the property maven.model.dependencyInterpolation.full as an opt-out.
The effect is easy to reproduce. A throw-away tidepool-probe POM (packaging pom) was installed in a scratch local repository with a dependency whose version is a
placeholder that only a command-line property can fill:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>${csv.v}</version>
</dependency>
A consumer project depends on it with <type>pom</type>, and dependency:tree (plugin 3.11.0) was run with -Dcsv.v=1.14.1. The resolved tree
on Maven 3.9.16:
[INFO] io.tidepool:tidepool-consumer:jar:1.0.0
[INFO] \- io.tidepool:tidepool-probe:pom:1.0.0:compile
[INFO] \- org.apache.commons:commons-csv:jar:1.14.1:compile
[INFO] +- commons-io:commons-io:jar:2.20.0:compile
[INFO] \- commons-codec:commons-codec:jar:1.19.0:compile
and on Maven 3.10.0 with the same command:
[INFO] io.tidepool:tidepool-consumer:jar:1.0.0
[INFO] \- io.tidepool:tidepool-probe:pom:1.0.0:compile
[INFO] \- org.apache.commons:commons-csv:jar:${csv.v}:compile
On Maven 4.0.0-rc-7 (release candidate) the commons-csv node was missing altogether. Adding -Dmaven.model.dependencyInterpolation.full=true to the 3.10.0 command
restored the 3.9.16 tree. Without any -D, both 3.9.16 and 3.10.0 printed the literal ${csv.v} node. The lesson is for anyone who publishes:
a POM that goes to a repository must be self-contained. A published POM that needs a -D property, an environment variable or a profile activated by the
consumer’s machine is unreliable, and from Maven 3.10.0 on it is also ignored. Properties defined in the POM itself or in its parents still work. The
dependency side is on Dependencies, Scopes & Mediation, and the
consumer POM that Maven 4 generates so that the published file is flat is on
Maven 4: POM Model & Consumer POM.
Project information
Several elements do not affect the build; they document the project, and plugins (the site, the Javadoc and the JAR plugins, and the publishing
checks of repositories) read them. This section uses one parent POM with all of them set, written for the example project (the host
names are the reserved example.org, and nothing in it is real):
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>Tidepool</name>
<description>Tide predictions from harmonic constituents.</description>
<url>https://tidepool.example.org/</url>
<inceptionYear>2026</inceptionYear>
<organization>
<name>Tidepool Project</name>
<url>https://tidepool.example.org/</url>
</organization>
<licenses>
<license>
<name>Apache-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>
<developers>
<developer>
<id>maintainer</id>
<name>Tidepool Maintainer</name>
<email>maintainer@example.org</email>
<organization>Tidepool Project</organization>
<organizationUrl>https://tidepool.example.org/</organizationUrl>
<roles>
<role>architect</role>
<role>developer</role>
</roles>
<timezone>Europe/Madrid</timezone>
<properties>
<chat>#tidepool</chat>
</properties>
</developer>
</developers>
<scm>
<connection>scm:git:https://git.example.org/tidepool/tidepool.git</connection>
<developerConnection>scm:git:ssh://git@git.example.org/tidepool/tidepool.git</developerConnection>
<tag>HEAD</tag>
<url>https://git.example.org/tidepool/tidepool</url>
</scm>
<issueManagement>
<system>GitHub Issues</system>
<url>https://git.example.org/tidepool/tidepool/issues</url>
</issueManagement>
<ciManagement>
<system>GitHub Actions</system>
<url>https://git.example.org/tidepool/tidepool/actions</url>
</ciManagement>
</project>
The elements, in the order of the reference:
-
name,description,url,inceptionYear: the display name (it is not theartifactId), a short human-readable description, the project home page and the year the project started. Plugins use them for the site and the Javadoc header. -
licenses: the licenses that apply to this project, not to its dependencies. Each hasname,url,commentsanddistribution, which isrepo(the artifact may be downloaded from a repository) ormanual(it must be installed by hand). Use an SPDX identifier innamewhen you can; tools that audit licenses then do not have to guess. -
organization: thenameandurlof the organization that runs the project. -
developers: the people immediately responsible for the code, not everyone who ever contributed, withid,name,email,url,organization,organizationUrl,roles,timezone(a time zone id such asEurope/Madridis preferred over an offset because it does not change with daylight saving time) and free-formproperties.contributorshas the same fields withoutidand is the place for occasional contributors. -
mailingLists:name,subscribe,unsubscribe,post,archiveandotherArchivesfor each list. -
scm: how to reach the source.connectionis read-only access,developerConnectionis write access,tagnames the tag the project lives under (HEADby default) andurlis a browsable web location. Theconnectionvalues follow Maven SCM’s formatscm:<provider>:<provider_specific>;scm:git:is the one in use today, and the book examples withscm:svn:orscm:cvs:show how old the format is. -
issueManagement:systemandurlof the defect tracker. -
ciManagement:system,urlandnotifiersof the continuous integration service. The reference’s example uses Continuum, which has been retired (see the book notes at the end).
Source: Maven: POM Reference, Licenses
Source: Maven: POM Reference, Developers
Source: Maven: POM Reference, SCM
What a child inherits
A child of that parent (artifactId tidepool-model, nothing else but the <parent>) saw, in its effective POM, on all three Maven versions:
-
description,organization,licenses,developers,issueManagementandciManagementinherited unchanged. The projectnamewas not inherited (the child’sproject.nameis itsartifactId). -
the five URLs got the child’s
artifactIdappended:
<url>https://tidepool.example.org/tidepool-model/</url>
<connection>scm:git:https://git.example.org/tidepool/tidepool.git/tidepool-model</connection>
<developerConnection>scm:git:ssh://git@git.example.org/tidepool/tidepool.git/tidepool-model</developerConnection>
<url>https://git.example.org/tidepool/tidepool/tidepool-model</url>
The last is scm/url; the fifth, the distributionManagement/site/url, became file:///tmp/tidepool-site/tidepool-model/ in a variant of the parent
with a <site> element. The model builder reference documents this special handling of project.url, project.scm.connection,
project.scm.developerConnection, project.scm.url and project.distributionManagement.site.url: an unset value is the parent’s with the artifact id
appended. That is right when each module has its own page below the parent’s, and wrong for a repository URL, where the SCM URL of a module in a
git repository is the repository’s. Two settings change it:
-
an attribute on the parent’s element, available since Maven 3.6.1, turns the appending off. In the parent,
<project child.project.url.inherit.append.path="false">on theprojectelement,<scm child.scm.connection.inherit.append.path="false" child.scm.developerConnection.inherit.append.path="false" child.scm.url.inherit.append.path="false">onscmand<site child.site.url.inherit.append.path="false">onsite. With them, the child’s effective POM had the parent’s values unchanged (<url>https://tidepool.example.org/</url>,<connection>scm:git:https://git.example.org/tidepool/tidepool.git</connection>,<url>https://git.example.org/tidepool/tidepool</url>for the SCM), on 3.9.16, 3.10.0 and 4.0.0-rc-7. Thechild.project.url.inherit.append.pathattribute belongs on theprojectelement, not onurl; put onurl, it had no effect in a run. -
a
project.directoryproperty in the child, available since Maven 3.5.0, replaces theartifactIdin the appended path, for a module whose directory name is not itsartifactId. A child with<project.directory>model</project.directory>hadhttps://tidepool.example.org/model/andscm:git:https://git.example.org/tidepool/tidepool.git/model. The property is not itself inherited.
Source: Maven 3.10.0 POM model reference
What the artifacts carry
The descriptive elements are in the POM, and the POM travels with the artifact. The JAR built by the finalName example above contains a copy of the
POM and a small properties file with the coordinates, which tools use to identify a JAR found outside a repository:
META-INF/maven/io.tidepool/tidepool-model/pom.xml
META-INF/maven/io.tidepool/tidepool-model/pom.properties
artifactId=tidepool-model
groupId=io.tidepool
version=1.0.0
The installed tidepool-model-1.0.0.pom was byte-for-byte identical to the project’s pom.xml (checked with cmp): in the Maven 3 line the file that
is installed and deployed is the source POM, with its <parent> reference, its unexpanded ${...} placeholders and its profiles, which is why
a consumer needs the parent in a repository too and why the previous section’s rule (a self-contained POM) matters. The consumer POM of Maven 4 is the
change that makes the published file a flattened one.
Repositories that host public releases check the descriptive elements. Maven Central requires name, description and url, the license
information, the developer information and the SCM information in the POM, as well as correct coordinates (a release version cannot end in
-SNAPSHOT). The checklist and how to produce the Javadoc, sources and signature files are on
Publishing to Maven Central.
Environment settings
prerequisites and the Enforcer
<prerequisites><maven> states a minimum Maven version. In POM 4.0.0 maven is the only element it has. Its role has narrowed over the years:
the POM reference says that for a build-time prerequisite one should use the Maven Enforcer Plugin’s requireMavenVersion rule, and that the element
is still used at run time for packaging maven-plugin, to make sure the minimum Maven version of a plugin is met (only in the plugin’s own
pom.xml). A jar project that sets it is told off by all three Maven versions. This POM:
<prerequisites>
<maven>3.99.0</maven>
</prerequisites>
printed the same warning on 3.9.16, 3.10.0 and 4.0.0-rc-7, and built successfully (the prerequisite was not enforced):
[WARNING] The project io.tidepool:tidepool-model:jar:1.0.0 uses prerequisites which is only intended for maven-plugin projects but not for non maven-plugin projects. For such purposes you should use the maven-enforcer-plugin. See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html
The way to state a build requirement is the Enforcer plugin (3.6.3). Bound to the validate phase through an execution, with the rules in the execution’s
configuration:
<build>
<plugins>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.3</version>
<executions>
<execution>
<id>enforce-versions</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.9.0,)</version>
<message>Tidepool needs Maven 3.9.0 or newer.</message>
</requireMavenVersion>
<requireJavaVersion>
<version>[17,)</version>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
mvn -B -ntp validate passed with 3.9.16, 3.10.0 and 4.0.0-rc-7 (release candidate):
[INFO] --- enforcer:3.6.3:enforce (enforce-versions) @ tidepool-model ---
[INFO] Rule 0: org.apache.maven.enforcer.rules.version.RequireMavenVersion passed
[INFO] Rule 1: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed
[INFO] BUILD SUCCESS
and with the range changed to [3.99.0,) it failed on 3.10.0 with the message that the POM supplies:
[INFO] --- enforcer:3.6.3:enforce (enforce-versions) @ tidepool-model ---
[INFO] Rule 1: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed
[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-enforcer-plugin:3.6.3:enforce (enforce-versions) on project tidepool-model:
[ERROR] Rule 0: org.apache.maven.enforcer.rules.version.RequireMavenVersion failed with message:
[ERROR] Tidepool needs Maven 3.99.0 or newer.
The prerequisites run for a maven-plugin project was not run here; the other rules of the Enforcer are on
Enforcer.
repositories and pluginRepositories
<repositories> and <pluginRepositories> say where dependencies and plugins are downloaded from, beyond Central, which the super POM supplies
(the effective POM above shows it). Each repository has an id, a name, a url, a layout (default, or legacy for the Maven 1 layout) and
independent releases and snapshots policies with enabled, updatePolicy (always, daily as the default, interval:X in minutes or never) and
checksumPolicy (ignore, fail or warn, the default). A repository must follow the Maven repository layout; everything else, including mirrors and
credentials in settings.xml, is on
Repositories, Mirrors & Repository Managers and
settings.xml & Password Encryption. Plain http repository URLs
in examples older than Maven 3.8.1 are blocked by default today; see that page.
distributionManagement
<distributionManagement> is about the artifacts this project produces, not the ones it consumes: where deploy uploads them. The reference
lists repository and snapshotRepository (an id, a name, a url and a layout; the id is matched to a <server> in settings.xml for
credentials, and the snapshot repository is optional: with none, repository is used for snapshots), site (where site:deploy publishes the project site),
downloadUrl, status and relocation.
Two notes on the elements. The upload URL is often not the download URL (an upload by HTTP PUT or scp, a download by HTTP GET), so the two are
declared separately. And <status> must never be written by hand: Maven sets it when the artifact is transported to a repository, with the values
none, converted, partner, deployed and verified, and "should never be configured in your pom.xml". The uploading itself, with real runs
against a file:// repository, is on
Install, Deploy & Distribution Management.
Source: Maven: POM Reference, Status
Relocation
When a project changes its coordinates, for example after a change of groupId, the old coordinates should keep resolving. The mechanism is a
<relocation> element in the old coordinates' POM, published in the repository:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example.tides</groupId>
<artifactId>tides-model</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<distributionManagement>
<relocation>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-model</artifactId>
<message>The project moved to the io.tidepool group.</message>
</relocation>
</distributionManagement>
</project>
Only the elements that changed need to be given (version was left out, so it stays 1.0.0); the message explains why. In the run, this POM was
installed into a scratch repository next to the real io.tidepool:tidepool-model:1.0.0, and a consumer declared a dependency on
org.example.tides:tides-model:1.0.0. dependency:tree printed on 3.9.16, 3.10.0 and 4.0.0-rc-7 (release candidate), twice (once while the project was
read, once for the plugin):
[WARNING] The artifact org.example.tides:tides-model:jar:1.0.0 has been relocated to io.tidepool:tidepool-model:jar:1.0.0: The project moved to the io.tidepool group.
[INFO] io.tidepool:tidepool-consumer:jar:1.0.0
[INFO] \- io.tidepool:tidepool-model:jar:1.0.0:compile
The consumer gets the new artifact and is told to update its POM. The two things people get wrong are where the element lives and what it does not do. It goes in the old coordinates' POM, the one that is published in the repository, and not in the new one. The related case of a third-party artifact that is not in any repository, and more on relocation, are on Installing Third-Party JARs & Relocation.
Source: Maven: POM Reference, Relocation
modelVersion 4.0.0 versus 4.1.0
modelVersion is the version of the POM format, not of the project. It has been 4.0.0 since Maven 2, and it stayed the only value through the whole of
Maven 3. Maven 4 (release candidate) introduces model version 4.1.0, which has its own namespace, http://maven.apache.org/POM/4.1.0, and its own
XSD, and adds elements such as <subprojects> (the new name of <modules>) and the bom packaging. The Maven 4 documentation also says that Maven 4
keeps building Maven 3’s 4.0.0 projects, so no one needs to move a POM to 4.1.0 unless a new feature is wanted. A minimal 4.1.0 POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 https://maven.apache.org/xsd/maven-4.1.0.xsd">
<modelVersion>4.1.0</modelVersion>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-model</artifactId>
<version>1.0.0</version>
</project>
mvn -B -ntp validate on this POM built on Maven 4.0.0-rc-7 (release candidate) and failed on both Maven 3 versions with the same message:
[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[FATAL] 'modelVersion' of '4.1.0' is newer than the versions supported by this version of Maven: [4.0.0]. Building this project requires a newer version of Maven. @ line 5, column 17
Writing <modelVersion>4.1.0</modelVersion> inside a document that still uses the 4.0.0 namespace fails the same way on Maven 3.10.0, and
was accepted by 4.0.0-rc-7. The consequence for a team is practical: a repository whose POMs say 4.1.0 can only be built by Maven 4, so the move is a
decision for the whole team and the whole CI, and published artifacts are not affected because Maven 4 generates a consumer POM in the 4.0.0 format for
them. What else changes, the inference of modelVersion and parent coordinates, and the migration steps are on
Maven 4: POM Model & Consumer POM and
Migrating to Maven 4. The XSD address of 4.1.0 currently redirects to a
release candidate file of the model, and will change when 4.0.0 is final.
Source: Maven 4: Model version 4.1.0
The same ideas in Ant and Gradle
Not every concept has an equivalent in the other two tools, which is itself a lesson about what a POM is. The table maps the parts of this page to Ant and to Gradle:
| Concern | Maven | Ant | Gradle |
|---|---|---|---|
project identity |
|
only |
|
properties |
|
|
|
environment variables |
|
|
|
descriptive metadata (licenses, developers, scm) |
elements of the POM |
none |
the |
model version |
|
not applicable |
not applicable (the Gradle module metadata has its own version) |
The idea shared by Gradle and Maven is that a published Maven POM is an interface to the rest of the world. Whatever builds the artifact, the consumers read
the POM, which is why the Gradle maven-publish plugin generates one and why the rules for a self-contained POM in this page apply to it as well.
Classic mistakes
-
finalNamedoes not rename the file in a repository, only the file intarget. -
A property named
project.somethingis reserved. Maven 3.x ignores your value and Maven 4.0.0-rc-7 honours it. -
A misspelt property name leaves
${name}literally in the value and fails later, in a place that looks unrelated, such as the dependency version message shown above. Compare the name in the message with the declaration. -
${pom.x}and${x}are deprecated; use${project.x}. -
A POM published to a repository must not rely on
-D, the environment or the consumer’s profiles. Maven 3.9.16 let a-Dproperty leak into a dependency’s POM; Maven 3.10.0 does not. -
The
urlandscmof a child get theartifactIdappended. Set thechild.*.inherit.append.pathattributes in the parent when that is wrong. -
${maven.build.timestamp}is UTC wall-clock time and changes in every build. Use a fixedproject.build.outputTimestampfor reproducible archives. -
<prerequisites>in a jar project is only a warning; the build requirement belongs in the Enforcer plugin. -
The relocation belongs in the old coordinates' POM.
-
A POM with
modelVersion4.1.0 cannot be built by Maven 3. -
Never write
<status>in a POM. -
maven.compiler.source1.7 is no longer accepted by the JDK, even when copied from official examples.
Where the books are outdated
The Maven 2 book that informed these pages predates most of what is on this page. Statements that no longer hold:
-
"POM model 4.0.0 only": Maven 4 adds model version 4.1.0, the consumer POM and the
bompackaging (above). -
A CI example based on Continuum: Continuum was retired in 2016 (it is in the Apache Attic). Use the CI system you have in
ciManagement; the element is informational. -
scm:svn:andscm:cvs:examples: thescm:<provider>format is unchanged, Git is the norm, and the release plugin works through Maven SCM. -
Plain
httprepository URLs: blocked by default since Maven 3.8.1 (CVE-2021-26291); Central is HTTPS only. -
Codehaus groupIds and URLs in examples: Codehaus closed, MojoHaus keeps
org.codehaus.mojoas a groupId. -
Plugins used without a version: the 3.10.0 super POM no longer manages the antrun, assembly, dependency and release plugins, and the versions of the packaging’s plugins come from the running Maven’s lifecycle bindings and change with it, so pin every one (see Plugins & Their Configuration).
-
Publishing through OSSRH: it was shut down on 2025-06-30; Central has a Publisher Portal.
Where to go next
-
Inheritance, Aggregation & the Effective POM for
<parent>, the super POM andhelp:effective-pom. -
Build Lifecycle for what each packaging runs.
-
Dependencies, Scopes & Mediation for the
dependencieselement. -
Profiles for properties that change with the environment.
-
Maven 4: POM Model & Consumer POM for model version 4.1.0.