Maven Troubleshooting & Best Practices
|
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. Every example was run on JDK 26 on 2026-10-16 in scratch copies of the tidepool project (a parent POM and the
tidepool-model, tidepool-engine and tidepool-cli modules, with maven.compiler.release 21 and every plugin version pinned in pluginManagement) with a
scratch local repository. Where the behaviour of Maven 3.9.16 or of Maven 4.0.0-rc-7 (a release candidate, not a final release) differs, the same build was run on
them and the difference is named; What differs between Maven versions collects them. In the console output the project directory was shortened to
/work/tidepool, the scratch local repository to /work/m2full, and the lines Loaded N auto-discovered prefixes ... that Maven 3.10 prints while it
resolves were left out. A line made of ... marks lines that were cut. Everything that was not run is labelled "not run here". The failures were produced on
purpose, by a typo or a small change to the sample build, so that the messages shown are the real ones.
A Maven build rarely fails in a mysterious way. It fails in one of four places, each with its own kind of message, and most of the work of troubleshooting is knowing which of the four you are looking at. This page gives a method for finding out, a catalogue of the classic messages with their causes, the tools that show what Maven believes about your build, and the practices that keep failures rare. The mechanics behind the messages are on other pages and are linked where they matter: Build Lifecycle, Dependencies, Scopes & Mediation, Repositories, Mirrors & Repository Managers and Maven Resolver.
A method: where in the build did it fail?
A run of mvn goes through the same stages every time, and the first words of the error tell which one failed:
Stage 1 and 2 failures happen before any module is built, so the reactor summary does not list them or lists the first module as failed with no work done. Stage 3
failures name a dependency or a plugin and a repository. Stage 4 failures start with Failed to execute goal followed by a plugin, a goal, an execution id and the
module. The message after the goal is written by the plugin, not by Maven, and it is the first thing to read.
Reading the console output
This is the start of a normal mvn -B -ntp verify on Maven 3.10.0 (-B is batch mode, -ntp hides the transfer progress), cut after the first module’s tests:
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] tidepool-parent [pom]
[INFO] tidepool-model [jar]
[INFO] tidepool-engine [jar]
[INFO] tidepool-cli [jar]
[INFO]
[INFO] ---------------------< io.tidepool:tidepool-model >---------------------
[INFO] Building tidepool-model 1.0.0-SNAPSHOT [2/4]
[INFO] from tidepool-model/pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.5.0:resources (default-resources) @ tidepool-model ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.16.0:compile (default-compile) @ tidepool-model ---
[INFO] Compiling 4 source files with javac [debug release 21] to target/classes
...
[INFO] --- surefire:3.6.0:test (default-test) @ tidepool-model ---
[INFO] Running io.tidepool.model.HeightTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.021 s -- in io.tidepool.model.HeightTest
The lines to learn to read are:
-
Reactor Build Orderis the order of the modules (see Multi-Module Builds & the Reactor). A module that is not listed is not part of this build. -
Building tidepool-model 1.0.0-SNAPSHOT [2/4]andfrom tidepool-model/pom.xmlsay which module and which POM file are being built. With-plthe second line shows the POM that really was used. -
--- compiler:3.16.0:compile (default-compile) @ tidepool-model ---is one mojo execution: the plugin prefix, the plugin version, the goal, the execution id in parentheses, and the module. The version in this line is the version that is really used, which is the first thing to check when a plugin "behaves differently on another machine".default-compileis the id Maven gives to executions bound by the lifecycle; an id you chose yourself in the POM shows up here too (see Plugins & Their Configuration). -
Everything between two
---lines was printed by that plugin.
The options that change how much Maven says
The command line options for output are in the CLI reference. The ones this page uses:
| Option | Effect |
|---|---|
|
Print the stack trace of the error, with its |
|
Debug output: Maven’s own decisions (settings files read, repositories, build plan) and the full configuration of every mojo. |
|
Only errors. The build output is shortened to the failure; useful in scripts and when a goal prints a value (see |
|
Non-interactive mode and no colour. Use it on a CI server so the log is plain text. |
|
Do not print the download progress lines. They hide the real messages in a CI log. |
|
Print the Maven and Java versions at the start and carry on. Put it in the CI command so every log says which Maven and JDK ran. |
|
Write all output to a file (and disable colour). Not run here. |
|
Do not stop at the first failing module; skip only the modules that depend on it, and report all failures at the end. |
|
Force updates; see Cached failures and -U, -UA, -UM. |
With -q the failed compilation of the next section prints only the error lines, without the reactor banner or the BUILD FAILURE block. With -fae, a test that
fails in tidepool-model leaves its dependents in the summary as skipped (the order of the summary lines is the order in which Maven reported them):
[INFO] Reactor Summary for tidepool-parent 1.0.0-SNAPSHOT:
[INFO]
[INFO] tidepool-engine .................................... SKIPPED
[INFO] tidepool-cli ....................................... SKIPPED
[INFO] tidepool-parent .................................... SUCCESS [ 0.000 s]
[ERROR] tidepool-model ..................................... FAILURE [ 1.107 s]
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
Stage 1: problems with the POM and the project
Maven reads and validates all POMs before it builds anything. Problems are reported as a list for the project that has them. In the tidepool engine module two dependencies were
changed: the version of commons-beanutils was left out (no dependencyManagement entry provides it) and commons-csv was declared twice. Maven 3.10.0:
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.apache.commons:commons-csv:jar -> duplicate declaration of version (?) @ line 16, column 17
[ERROR] 'dependencies.dependency.version' for commons-beanutils:commons-beanutils:jar is missing. @ line 13, column 17
@
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]
[ERROR] The project io.tidepool:tidepool-engine:1.0.0-SNAPSHOT (/work/tidepool/tidepool-engine/pom.xml) has 2 errors
[ERROR] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.apache.commons:commons-csv:jar -> duplicate declaration of version (?) @ line 16, column 17
[ERROR] 'dependencies.dependency.version' for commons-beanutils:commons-beanutils:jar is missing. @ line 13, column 17
The message names the element by its path in the model (dependencies.dependency.version), the coordinates and the line and column in the file. The same POM on
Maven 3.9.16 reported the duplicate only as a [WARNING] and stopped on the missing version alone; Maven 4.0.0-rc-7 reported both as errors, and, because it also
validates the parent, listed them a second time under tidepool-parent:
[ERROR] Encountered 4 problem(s) while processing the POMs
[ERROR] The build could not read 2 projects -> [Help 1]
[ERROR]
[ERROR] The project io.tidepool:tidepool-engine:1.0.0-SNAPSHOT (/work/tidepool/tidepool-engine/pom.xml) has 2 errors
[ERROR] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.apache.commons:commons-csv:jar -> duplicate declaration of version (?) @ line 16, column 5
[ERROR] 'dependencies.dependency.version' for groupId='commons-beanutils', artifactId='commons-beanutils', type='jar' is missing. @ line 13, column 5
[ERROR]
[ERROR] The project io.tidepool:tidepool-parent:1.0.0-SNAPSHOT (/work/tidepool/pom.xml) has 2 errors
...
A warning in this stage means a problem that is tolerated today. A plugin used in build/plugins without a version, and not managed in pluginManagement, is the classic one.
With maven-antrun-plugin added to the CLI module without a version, Maven 3.10.0 says:
[WARNING] Some problems were encountered while building the effective model for io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
[WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-antrun-plugin is missing. @ line 19, column 15
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
Maven 4.0.0-rc-7 prints 1 problem was encountered while building the effective model for 'io.tidepool:tidepool-cli:1.0.0-SNAPSHOT' (use -e to see details), and
-e adds the text of the problem. Maven 3.9.16 printed nothing for the same POM: its Super POM still pinned maven-antrun-plugin to 3.1.0, so the plugin had a
version. Maven 3.10.0’s Super POM does not (the help:effective-pom of the same module differs in exactly those plugin entries), so the plugin is resolved to whatever
is newest on the day, and Maven warns. That is the case for the practice of pinning every plugin version; see Best practices.
Source: POM Reference: Plugin Management
The parent cannot be found
A module whose parent is not on disk at the relativePath (default ../pom.xml) and not in a repository fails in this stage. A small application POM with the parent
io.tidepool:tidepool-corporate-parent:7, built before the parent was installed:
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]
[ERROR] The project io.tidepool:tidepool-app:1.0.0 (/work/corp/app/pom.xml) has 1 error
[ERROR] Non-resolvable parent POM for io.tidepool:tidepool-app:1.0.0: The following artifacts could not be resolved: io.tidepool:tidepool-corporate-parent:pom:7 (absent): prefixes: Path io/tidepool/tidepool-corporate-parent/7/tidepool-corporate-parent-7.pom NOT allowed from central and 'parent.relativePath' points at wrong local POM @ line 3, column 11 -> [Help 2]
Read it from the right: 'parent.relativePath' points at wrong local POM says there is no POM at ../pom.xml, and the (absent) part says that no repository has it. The
prefixes: ... NOT allowed from central part is the Maven 3.10 repository filter saying that Central does not publish the group io.tidepool (see
Maven Resolver); it is a consequence, not the cause. The remedies are to set relativePath to where the parent
really is, to build the parent first (or in the same reactor), or to install or deploy the parent to a repository that the build reads. The same inheritance rules are on
Inheritance, Aggregation & the Effective POM.
Stage 2: the command line and the build plan
If the words you typed after mvn are not a phase, not a goal and not a known plugin prefix, Maven says so before it does any work. Three typos on Maven 3.10.0:
$ mvn -B -ntp pakage
[ERROR] Unknown lifecycle phase "pakage". You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: pre-clean, clean, post-clean, validate, initialize, ... deploy, pre-site, site, post-site, site-deploy. -> [Help 1]
$ mvn -B -ntp compiler:compille
[ERROR] Could not find goal 'compille' in plugin org.apache.maven.plugins:maven-compiler-plugin:3.16.0 among available goals compile, help, testCompile -> [Help 1]
$ mvn -B -ntp fooplugin:bar
[ERROR] No plugin found for prefix 'fooplugin' in the current project and in the plugin groups [org.apache.maven.plugins, org.codehaus.mojo] available from the repositories [local (/work/m2full), central (https://repo.maven.apache.org/maven2)] -> [Help 1]
The first message lists every phase, which is the quickest way to see a typo. The second one resolved the prefix compiler to the plugin and the version that the
project pins, and lists the plugin’s goals. The third one says where Maven looked for the prefix: the plugins of the project, the plugin groups of settings.xml
(org.apache.maven.plugins and org.codehaus.mojo by default) and the repositories. The causes are a misspelt prefix, a plugin in another group that is not listed in
pluginGroups, or a plugin repository that the build cannot reach; the cure for the last two is to use the full coordinates, groupId:artifactId:version:goal.
How a prefix maps to a plugin is explained in Running Maven (CLI).
A frequent variant is a shell that passes compile -pl nope as one word (the command was put in quotes by mistake): Maven then reports Unknown lifecycle phase "compile -pl nope".
If the "phase" in the message contains a space, look at how the command was quoted.
Stage 3: resolving plugins and dependencies
This stage is where most real-world failures happen, because it depends on the network, on the caches and on the other people’s repositories. The first line is always
Could not resolve dependencies for project (a dependency of the module) or Plugin ... or one of its dependencies could not be resolved (a plugin), followed by
the artifact and the repository. The text after that names the cause:
| Message | Meaning and cure |
|---|---|
|
No repository that the build reads has it. A typo in the coordinates (the example below), a version that was never published, or a repository that is not configured. |
|
The earlier miss is remembered; see Cached failures and -U, -UA, -UM. |
|
|
|
A repository with a plain |
|
The server or the DNS name cannot be reached: a proxy, a VPN, a typo in a URL, a server that is down. |
|
The file is in the local repository, but was downloaded through a repository id that this build does not use; see The local repository is remembering where a file came from. |
|
The download does not match its |
|
The POM (not the jar) of a dependency or parent could not be read. The last one is only a warning: the jar may still be used, but without its own dependencies. |
A dependency that does not exist
A typo in the artifact id (commons-csvv for commons-csv) in the CLI module, on a clean build, Maven 3.10.0:
[WARNING] The POM for org.apache.commons:commons-csvv:jar:1.14.1 is missing, no dependency information available
[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal on project tidepool-cli: Could not resolve dependencies for project io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
[ERROR] dependency: org.apache.commons:commons-csvv:jar:1.14.1 (compile)
[ERROR] Could not find artifact org.apache.commons:commons-csvv:jar:1.14.1 in central (https://repo.maven.apache.org/maven2)
[ERROR] -> [Help 1]
Notice that there is no Failed to execute goal <plugin>: the failure is in Maven’s own dependency resolution, which runs before the first mojo of the module that needs a
classpath. The line dependency: ... (compile) gives the scope the dependency was declared in. When the dependency is not a direct one, the message instead says
Failed to read artifact descriptor for ... and names the dependency that brought it in; dependency:tree (below) shows the path.
A module of the same project that is not built
The most common cause of Could not find artifact io.tidepool:tidepool-engine:jar:1.0.0-SNAPSHOT for a sibling module is building a single module from the
root without its siblings, when they have not been installed to the local repository either:
$ mvn -B -ntp test -pl tidepool-cli
[WARNING] The POM for io.tidepool:tidepool-engine:jar:1.0.0-SNAPSHOT is missing, no dependency information available
[ERROR] Failed to execute goal on project tidepool-cli: Could not resolve dependencies for project io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
[ERROR] dependency: io.tidepool:tidepool-engine:jar:1.0.0-SNAPSHOT (compile)
[ERROR] prefixes: Path io/tidepool/tidepool-engine/1.0.0-SNAPSHOT/tidepool-engine-1.0.0-SNAPSHOT.jar NOT allowed from central
Maven 3.9.16 said Could not find artifact io.tidepool:tidepool-engine:jar:1.0.0-SNAPSHOT without the prefixes: line; Maven 4.0.0-rc-7 printed the prefixes:
line and then explained that a remote repository filter rejected the artifact, which "commonly happens with repository managers using virtual/group repositories that
do not properly aggregate prefix files". Here the filter is right and harmless: the artifact simply does not exist anywhere. The cure is -am (also make), which adds
the modules that tidepool-cli needs to the build, or mvn install once. With mvn -B -ntp test -pl tidepool-cli -am all four modules built and the build
succeeded. The reactor options are on Multi-Module Builds & the Reactor.
Cached failures and -U, -UA, -UM
Maven remembers that a file was not found, so that a build does not ask the same server for the same missing file on every run. The memory is a *.lastUpdated file in the local repository,
next to where the artifact would be:
$ ls m2full/org/apache/commons/commons-csvv/1.14.1/
commons-csvv-1.14.1.jar.lastUpdated
commons-csvv-1.14.1.pom.lastUpdated
$ cat m2full/org/apache/commons/commons-csvv/1.14.1/commons-csvv-1.14.1.pom.lastUpdated
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Fri Oct 16 16:45:33 CEST 2026
https\://repo.maven.apache.org/maven2/.error=
https\://repo.maven.apache.org/maven2/.lastUpdated=1792161933113
The trap is the order of events: a dependency is requested before it is published (or before the repository is reachable), the build fails, the dependency is published, and the
next build still fails, now with a different message. Reproduced with a repository served from localhost (a plain python3 -m http.server over a directory), a dependency
com.example.legacy:legacy-lib:3.0 and the CLI module, on Maven 3.10.0. First run, the file is not on the server yet:
[WARNING] The POM for com.example.legacy:legacy-lib:jar:3.0 is missing, no dependency information available
[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal on project tidepool-cli: Could not resolve dependencies for project io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
[ERROR] dependency: com.example.legacy:legacy-lib:jar:3.0 (compile)
[ERROR] prefixes: Path com/example/legacy/legacy-lib/3.0/legacy-lib-3.0.jar NOT allowed from central
[ERROR] Could not find artifact com.example.legacy:legacy-lib:jar:3.0 in local-test (http://localhost:8099/repo)
The file is then copied to the server and the build repeated, with no other change:
[WARNING] The POM for com.example.legacy:legacy-lib:jar:3.0 is missing, no dependency information available
[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal on project tidepool-cli: Could not resolve dependencies for project io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
[ERROR] dependency: com.example.legacy:legacy-lib:jar:3.0 (compile)
[ERROR] prefixes: Path com/example/legacy/legacy-lib/3.0/legacy-lib-3.0.jar NOT allowed from central
[ERROR] com.example.legacy:legacy-lib:jar:3.0 was not found in http://localhost:8099/repo during a previous attempt. This failure was cached in the local repository and resolution is not reattempted until the update interval of local-test has elapsed or updates are forced
mvn -U (--update-snapshots) forced the check and the build succeeded:
[INFO] Reactor Summary for tidepool-parent 1.0.0-SNAPSHOT:
[INFO]
[INFO] tidepool-parent .................................... SUCCESS [ 0.001 s]
[INFO] tidepool-model ..................................... SUCCESS [ 1.308 s]
[INFO] tidepool-engine .................................... SUCCESS [ 0.065 s]
[INFO] tidepool-cli ....................................... SUCCESS [ 0.430 s]
[INFO] BUILD SUCCESS
The cached-failure message was the same on Maven 3.9.16 and 4.0.0-rc-7 (3.9.16 repeated it once per repository, as it also asks Central). Maven 3.10.0 splits the force
flag into -UA (--update-artifacts: retry cached retrieval errors) and -UM (--update-metadata: refresh plugin prefixes, version ranges and snapshots); -U forces the check for missing releases and updated snapshots. In a second experiment on 3.10.0 with the same cached failure, -UM alone did not help (BUILD FAILURE) and -UA alone succeeded.
Maven 3.9.16 and 4.0.0-rc-7 have only -U (their --help lists no -UA or -UM). Do not put -U in every CI command: it makes every build re-check every
snapshot and every cached miss, and slows all of them down; use it to recover from a cached failure.
A refused connection (nothing listening on the port) is not cached: running the build twice gave the same message both times. With the repository URL pointing at a port with no server, Maven 3.10.0 said:
[WARNING] Could not transfer metadata /.meta/prefixes.txt from/to local-test (http://localhost:8098/repo): Connect to localhost:8098 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused
[ERROR] Failed to execute goal on project tidepool-cli: Could not collect dependencies for project io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
[ERROR] Failed to read artifact descriptor for com.example.legacy:legacy-lib:jar:4.0
[ERROR] Caused by: The following artifacts could not be resolved: com.example.legacy:legacy-lib:pom:4.0 (absent): Could not transfer artifact com.example.legacy:legacy-lib:pom:4.0 from/to local-test (http://localhost:8098/repo): Connect to localhost:8098 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused
Source: Maven 3.10.0: CLI options reference and Maven Resolver: Local Repository, Error Handling and Caching
The offline and caching behaviour has its own section in Repositories, Mirrors & Repository Managers.
Offline mode
With -o and an empty local repository, Maven 3.10.0 has nothing to build with and says so:
$ mvn -B -ntp -o verify -pl tidepool-model
[WARNING] The POM for org.apache.maven.plugins:maven-jar-plugin:jar:3.5.1 is missing, no dependency information available
[ERROR] BUILD FAILURE
[ERROR] Plugin org.apache.maven.plugins:maven-jar-plugin:3.5.1 or one of its dependencies could not be resolved:
[ERROR] Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.apache.maven.plugins:maven-jar-plugin:jar:3.5.1 has not been downloaded from it before.
The message says which plugin is missing first, not that it is the only one; a build that must run offline needs every plugin and dependency downloaded in advance, for example by
running the build once online with the same local repository (the Dependency plugin’s go-offline goal is the tool made for it, but it was not run here).
Blocked HTTP repositories
Since Maven 3.8.1 a repository whose URL starts with http:// is blocked, because the download could be tampered with in transit. A dependency whose POM declares
http://repo.example.com/maven as a repository (the library com.example.legacy:legacy-lib:2.1 and the repository id legacy are made up; nothing is served there):
[ERROR] Failed to execute goal on project tidepool-cli: Could not collect dependencies for project io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
[ERROR] Failed to read artifact descriptor for com.example.legacy:legacy-lib:jar:2.1
[ERROR] Caused by: The following artifacts could not be resolved: com.example.legacy:legacy-lib:pom:2.1 (absent): Could not transfer artifact com.example.legacy:legacy-lib:pom:2.1 from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories: [legacy (http://repo.example.com/maven, default, releases+snapshots)]
The real repository never was contacted; the "mirror" maven-default-http-blocker (shown in help:effective-settings below) stood in its place. The cure is an https URL, or a
mirror that you choose deliberately. Details and the mirror settings are on
Repositories, Mirrors & Repository Managers. localhost is not blocked, which is why the
experiments of this page can serve a repository with http.server.
The local repository is remembering where a file came from
The local repository records, in _remote.repositories, the id of the repository each file came from. A build that uses a different set of repository ids (a mirror added to
settings.xml, a settings file from another machine) does not trust the file until it has been checked against the new repositories. With a mirror
corp-mirror for central whose host does not resolve, the local repository filled earlier through central, on Maven 3.10.0:
[ERROR] BUILD FAILURE
[ERROR] Plugin org.apache.maven.plugins:maven-resources-plugin:3.5.0 or one of its dependencies could not be resolved:
[ERROR] The following artifacts could not be resolved: org.apache.maven.plugins:maven-resources-plugin:pom:3.5.0 (present, but unavailable): Could not transfer artifact org.apache.maven.plugins:maven-resources-plugin:pom:3.5.0 from/to corp-mirror (https://repo.example.com/maven-central/): repo.example.com
(The messages before it added Artifact ... is present in the local repository, but cached from a remote repository ID that is unavailable in current build context, verifying that is downloadable from [corp-mirror ...].)
(present, but unavailable) means that the file is on disk but Maven could not verify it against the new repository. The cure is to use the settings that the local repository was
filled with, to make the mirror reachable, or to use a separate local repository per configuration.
Checksum failures
A repository file comes with a .sha1; Maven compares the download with it. The default policy of a repository is warn: the mismatch is only logged and the build goes on.
A repository local-test served from localhost held legacy-lib-2.1.jar whose .sha1 had been replaced with forty zeros. With the default policy, Maven 3.10.0 warned and succeeded:
[WARNING] Could not validate integrity of download from http://localhost:8099/repo/com/example/legacy/legacy-lib/2.1/legacy-lib-2.1.jar
org.eclipse.aether.transfer.ChecksumFailureException: Checksum validation failed, expected '0000000000000000000000000000000000000000' (REMOTE_EXTERNAL) but is actually 'd2fb5b40b6449d5b4c70373b1e7a718207b295ed'
...
[INFO] BUILD SUCCESS
The jar had been stored in the local repository with that warning, so the next build did not warn again. Setting the policy of the repository to fail
(<releases><checksumPolicy>fail</checksumPolicy></releases> in its repository element) or running with -C (--strict-checksums) turns it into an error; Maven 3.10.0 and 3.9.16:
[ERROR] Failed to execute goal on project tidepool-cli: Could not resolve dependencies for project io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
[ERROR] dependency: com.example.legacy:legacy-lib:jar:2.1 (compile)
[ERROR] Could not transfer artifact com.example.legacy:legacy-lib:jar:2.1 from/to local-test (http://localhost:8099/repo): Checksum validation failed, expected '0000000000000000000000000000000000000000' (REMOTE_EXTERNAL) but is actually 'd2fb5b40b6449d5b4c70373b1e7a718207b295ed'
A checksum failure can be a corrupt download or a proxy that rewrites files, but it can also be a real attack, so a failure that appears on a file that used to work deserves an explanation before it gets an exception; the other side of this, what Maven does and does not verify, is on Security & Reproducible Builds. Checksum policy details are on Repositories, Mirrors & Repository Managers.
A corrupt file in the local repository
An interrupted download, a full disk or a copy between machines can leave a truncated jar in the local repository. The nasty part is that the error shows up in the
compiler, far from the cause. With commons-csv-1.14.1.jar cut to its first 1000 bytes, the engine module on Maven 3.10.0 gave six errors; the first one is the cause and
the rest follow from it:
[ERROR] COMPILATION ERROR :
[ERROR] error reading /work/m2c/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar; zip END header not found
[ERROR] /work/tidepool/tidepool-engine/src/main/java/io/tidepool/engine/StationReader.java:[1,1] cannot access io.tidepool.engine
ZipException opening "commons-csv-1.14.1.jar": zip END header not found
[ERROR] /work/tidepool/tidepool-engine/src/main/java/io/tidepool/engine/TideEngine.java:[13,34] cannot find symbol
symbol: class Station
location: class io.tidepool.engine.TideEngine
...
[INFO] 6 errors
zip END header not found (or error in opening zip file) always means a damaged archive. The cure is to remove the damaged artifact from the local repository and download it again. The Dependency plugin’s
purge-local-repository goal does that for named artifacts:
$ mvn -B -ntp -pl tidepool-engine dependency:purge-local-repository -DmanualInclude=org.apache.commons:commons-csv -DreResolve=true
[INFO] --- dependency:3.11.0:purge-local-repository (default-cli) @ tidepool-engine ---
[INFO] Deleting 1 manual dependency from /work/m2c
[INFO] BUILD SUCCESS
and the next build downloaded the file again, with commons-csv-1.14.1.jar back at 60964 bytes. Deleting the directory org/apache/commons/commons-csv/1.14.1 by hand has the same effect.
Purging the whole local repository (deleting ~/.m2/repository) also works, but it throws away everything and makes the next build download all plugins again; treat it as a last
resort, not as the first reaction to an error. Nothing in ~/.m2 was deleted while preparing this page; the experiments used scratch copies.
Stage 4: a plugin fails
When the failure comes from a mojo, the message is Failed to execute goal <groupId>:<artifactId>:<version>:<goal> (<execution id>) on project <module>: followed by the plugin’s own text.
The plugin’s documentation is where to look the text up; the plugin usually also writes a report to a file (Surefire to target/surefire-reports, for instance).
The compiler
A reference to an undefined constant in Height.java, Maven 3.10.0 (the error is printed by the compiler plugin once as it happens and once in the summary):
[INFO] --- compiler:3.16.0:compile (default-compile) @ tidepool-model ---
[INFO] Compiling 4 source files with javac [debug release 21] to target/classes
[ERROR] COMPILATION ERROR :
[ERROR] /work/tidepool/tidepool-model/src/main/java/io/tidepool/model/Height.java:[6,25] cannot find symbol
symbol: variable FEET_PER_METRE
location: class io.tidepool.model.Height
[INFO] 1 error
[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.16.0:compile (default-compile) on project tidepool-model: Compilation failure
[ERROR] /work/tidepool/tidepool-model/src/main/java/io/tidepool/model/Height.java:[6,25] cannot find symbol
[ERROR] symbol: variable FEET_PER_METRE
[ERROR] location: class io.tidepool.model.Height
The position is [line,column] in the file. The message is javac’s own; Maven only adds the frame around it. How the compiler plugin picks release, source and target, and how
toolchains select a JDK, is on Compiling & Toolchains.
The wrong JDK
mvn -v shows the JDK that Maven runs on (taken from JAVA_HOME, or from the java on the PATH); that, not the JDK you think is installed, decides what can be compiled. With
JAVA_HOME pointing to a JDK 17 and maven.compiler.release still 21, Maven 3.10.0:
$ JAVA_HOME=~/jdks/jbr-17 mvn -v
Apache Maven 3.10.0 (c43a36b8d67be7e0805a411bc0898af1a51f5472)
Maven home: /work/tools/apache-maven-3.10.0
Java version: 17.0.14, vendor: JetBrains s.r.o., runtime: ~/jdks/jbr-17/Contents/Home
$ JAVA_HOME=~/jdks/jbr-17 mvn -B -ntp compile -pl tidepool-model
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.16.0:compile (default-compile) on project tidepool-model: Fatal error compiling: error: release version 21 not supported -> [Help 1]
(~/jdks/jbr-17 stands for the path of the JDK 17 used; the real path was shortened.) The same message appears with -Dmaven.compiler.release=99 on the right JDK: error: release version 99 not supported.
The remedy is to run Maven on a JDK that is at least the release, or to select the JDK with a toolchain.
A failing test
A test whose expected value was changed to 2.5, Maven 3.10.0:
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running io.tidepool.model.HeightTest
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.023 s <<< FAILURE! -- in io.tidepool.model.HeightTest
[ERROR] io.tidepool.model.HeightTest.keepsMetres -- Time elapsed: 0.017 s <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <2.5> but was: <1.5>
at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:935)
at io.tidepool.model.HeightTest.keepsMetres(HeightTest.java:10)
[ERROR] Failures:
[ERROR] HeightTest.keepsMetres:10 expected: <2.5> but was: <1.5>
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.6.0:test (default-test) on project tidepool-model: There are test failures.
[ERROR]
[ERROR] See /work/tidepool/tidepool-model/target/surefire-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
Failures are assertions that failed, Errors are exceptions that were not expected. The report directory has the full output of every test; running one test class again, skipping
tests, and the forked JVM that crashes are on Testing with Surefire, and the integration tests that
fail in the verify phase on Integration Testing with Failsafe.
Parameters that the plugin does not know
A plugin configuration that names a parameter the goal does not have is reported by Maven 3.9.16, 3.10.0 and 4.0.0-rc-7. A typo is the usual reason: the compiler plugin’s showWarnings written as
showWarning, and compilerArgs as comilerArgs. All three printed, at the start of the compile execution:
[WARNING] Parameter 'comilerArgs' is unknown for plugin 'maven-compiler-plugin:3.16.0:compile (default-compile)'
[WARNING] Parameter 'showWarning' is unknown for plugin 'maven-compiler-plugin:3.16.0:compile (default-compile)'
The build is not failed: the misspelt options are simply ignored, which means that the build does not do what you configured. Treat these warnings as errors in your own review. The
property maven.plugin.validation (values brief, default and verbose, since the plugin validation of Maven 3.9.2) controls how much of the plugin validation report is shown at the end of a build; it did not
change the unknown-parameter lines above (run with VERBOSE, BRIEF and NONE), and no end-of-build report appeared in these builds.
Out of memory
Maven itself is a Java program and has a heap. A build with a heap far too small, forced here with MAVEN_OPTS=-Xmx8m (a deliberately absurd value), failed before the first module:
$ MAVEN_OPTS=-Xmx8m mvn -B -ntp compile
[INFO] Scanning for projects...
[ERROR] Java heap space -> [Help 1]
...
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/OutOfMemoryError
A real project that needs a bigger heap sets it in MAVEN_OPTS or, better, in the project’s .mvn/jvm.config so that everybody and the CI use the same value; see
Configuring Maven. An out of memory error in a forked JVM (the tests, the compiler with fork) is set with
that plugin’s own argLine or meminitial parameter instead.
Asking Maven what it believes
When the message does not say enough, ask Maven. The tools below show the state that the build used, not the state that you think it used.
-e: the stack trace
-e prints the stack trace of the failure with its Caused by chain. For the cached failure above, the line that explained the cause was already in the message; the stack trace adds the
frames and the exception class (here DependencyResolutionException), which is what you attach to a bug report:
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal on project tidepool-cli: Could not resolve dependencies for project io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
dependency: org.apache.commons:commons-csvv:jar:1.14.1 (compile)
org.apache.commons:commons-csvv:jar:1.14.1 was not found in https://repo.maven.apache.org/maven2 during a previous attempt. This failure was cached in the local repository and resolution is not reattempted until the update interval of central has elapsed or updates are forced
at org.apache.maven.lifecycle.internal.LifecycleDependencyResolver.getDependencies (LifecycleDependencyResolver.java:250)
at org.apache.maven.lifecycle.internal.LifecycleDependencyResolver.resolveProjectDependencies (LifecycleDependencyResolver.java:140)
...
Caused by: org.apache.maven.project.DependencyResolutionException: Could not resolve dependencies for project io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
-X: debug output
-X prints several hundred lines even for a small build (743 for one failing module here, most of them the class realms that Maven creates at start-up). Search it instead of reading it. The lines that answer
the common questions, from the same Maven 3.10.0 run:
Apache Maven 3.10.0 (c43a36b8d67be7e0805a411bc0898af1a51f5472)
Maven home: /work/tools/apache-maven-3.10.0
Java version: 26, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk-26.jdk/Contents/Home
...
[DEBUG] Reading global settings from /work/tools/apache-maven-3.10.0/conf/settings.xml
[DEBUG] Reading user settings from ~/.m2/settings.xml
[DEBUG] Reading global toolchains from /work/tools/apache-maven-3.10.0/conf/toolchains.xml
[DEBUG] Reading user toolchains from ~/.m2/toolchains.xml
[DEBUG] Using local repository at /work/m2full
...
[DEBUG] === PROJECT BUILD PLAN ================================================
[DEBUG] Project: io.tidepool:tidepool-cli:1.0.0-SNAPSHOT
[DEBUG] Dependencies (collect): []
[DEBUG] Dependencies (resolve): [compile, runtime, test]
[DEBUG] Repositories (dependencies): [central (https://repo.maven.apache.org/maven2, default, releases)]
[DEBUG] Repositories (plugins) : [central (https://repo.maven.apache.org/maven2, default, releases)]
These lines say which settings.xml was read (a missing file is simply not listed as an error), which local repository is used, which repositories each kind of lookup consults, and which
scopes will be resolved before the build plan runs. Further down, every mojo’s configuration is printed before it runs. For the compiler plugin on the model module:
[DEBUG] Configuring mojo execution 'org.apache.maven.plugins:maven-compiler-plugin:3.16.0:compile:default-compile' with basic configurator -->
[DEBUG] (f) debug = true
[DEBUG] (f) encoding = UTF-8
[DEBUG] (f) outputDirectory = /work/tidepool/tidepool-model/target/classes
[DEBUG] (f) showWarnings = true
[DEBUG] (s) release = 21
[DEBUG] (f) source = 1.8
[DEBUG] (s) target = 1.8
...
[DEBUG] -d /work/tidepool/tidepool-engine/target/classes -classpath /work/tidepool/tidepool-engine/target/classes:/work/tidepool/tidepool-model/target/classes:/work/m2full/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:/work/m2full/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:...
(Only seven of the parameters are shown.) This is the answer to "which value did the plugin really get": release = 21 came from the maven.compiler.release property of the parent POM, while
source and target show the plugin defaults (1.8), which javac does not use when release is given (the log line says javac [debug release 21]). The last line is the exact class path handed to javac for the engine module, in order; for a
NoSuchMethodError or a duplicate class at run time, this order (or the one printed by dependency:build-classpath) is the evidence.
Source: Guide to Configuring Plug-ins
help:effective-pom
help:effective-pom prints the POM that Maven works with after inheritance, interpolation, profile activation and the Super POM, in the form of one XML document. Together with
-Dverbose, each element carries a comment with the POM it came from and the line (Maven 3.10.0, engine module, trimmed):
$ mvn -B -ntp -q help:effective-pom -Dverbose -pl tidepool-engine -Doutput=effective.xml
<modelVersion>4.0.0</modelVersion> <!-- io.tidepool:tidepool-engine:1.0.0-SNAPSHOT, line 5 -->
<groupId>io.tidepool</groupId> <!-- io.tidepool:tidepool-parent:1.0.0-SNAPSHOT, line 7 -->
<properties>
<maven.compiler.release>21</maven.compiler.release> <!-- io.tidepool:tidepool-parent:1.0.0-SNAPSHOT, line 19 -->
<project.build.outputTimestamp>1980-02-01T00:00:00Z</project.build.outputTimestamp> <!-- org.apache.maven:maven-model-builder:3.10.0:super-pom, line 30 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- io.tidepool:tidepool-parent:1.0.0-SNAPSHOT, line 20 -->
</properties>
...
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId> <!-- io.tidepool:tidepool-engine:1.0.0-SNAPSHOT, line 14 -->
<artifactId>commons-csv</artifactId> <!-- io.tidepool:tidepool-engine:1.0.0-SNAPSHOT, line 14 -->
<version>1.14.1</version> <!-- io.tidepool:tidepool-parent:1.0.0-SNAPSHOT, line 40 -->
<scope>compile</scope>
</dependency>
The version of commons-csv came from line 40 of the parent (its dependencyManagement), the property maven.compiler.release from line 19 of the parent, and
project.build.outputTimestamp from the Super POM of Maven 3.10.0, a property that the same command on Maven 3.9.16 does not show. Without -Dverbose the same document has no comments. This is the first tool to use for "where
does this value come from", "why is this plugin configured like this" and "which profile applied"; the elements of the effective POM are explained on
Inheritance, Aggregation & the Effective POM.
help:effective-settings
help:effective-settings prints the merged result of the global and the user settings.xml, with the passwords hidden. With a throwaway settings file (-s) holding a server with the placeholder password
changeit-example, a profile and activeProfiles, Maven 3.10.0 printed (the lines that name the user and the host in the header were left out):
Effective user-specific configuration settings:
<?xml version="1.0" encoding="UTF-8"?>
...
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0" ...>
<localRepository>/work/m2full</localRepository>
<interactiveMode>false</interactiveMode>
<servers>
<server>
<username>build</username>
<password>***</password>
<id>corp-releases</id>
</server>
</servers>
<mirrors>
<mirror>
<mirrorOf>external:http:*</mirrorOf>
<name>Pseudo repository to mirror external repositories initially using HTTP.</name>
<url>http://0.0.0.0/</url>
<blocked>true</blocked>
<id>maven-default-http-blocker</id>
</mirror>
</mirrors>
<profiles>
<profile>
<properties>
<tidepool.channel>beta</tidepool.channel>
</properties>
<id>corp</id>
</profile>
</profiles>
<activeProfiles>
<activeProfile>corp</activeProfile>
</activeProfiles>
<pluginGroups>
<pluginGroup>org.apache.maven.plugins</pluginGroup>
<pluginGroup>org.codehaus.mojo</pluginGroup>
</pluginGroups>
</settings>
Three things to read: the password is masked, the blocking mirror for http repositories is there although nobody wrote it, and the plugin groups are the defaults. Use it
when a mirror, a proxy or a server credential "is not used". The settings file is explained on
settings.xml & Password Encryption, and help:active-profiles (see
Profiles) lists the profiles that are really active.
Source: Help plugin: effective-settings
help:describe
help:describe documents a plugin, a goal or a lifecycle phase without opening a browser. -Dplugin=compiler -Dgoal=compile prints the goal’s description (add -Ddetail for every
parameter with its user property and default). -Dcmd=package answers "what does mvn package run for this module":
$ mvn -B -ntp help:describe -Dcmd=package -pl tidepool-model
[INFO] 'package' is a phase corresponding to this plugin:
org.apache.maven.plugins:maven-jar-plugin:3.5.1:jar
It is a part of the lifecycle for the POM packaging 'jar'. This lifecycle includes the following phases:
* validate: Not defined
* initialize: Not defined
* generate-sources: Not defined
* process-sources: Not defined
* generate-resources: Not defined
* process-resources: org.apache.maven.plugins:maven-resources-plugin:3.5.0:resources
* compile: org.apache.maven.plugins:maven-compiler-plugin:3.16.0:compile
...
* test: org.apache.maven.plugins:maven-surefire-plugin:3.6.0:test
...
* package: org.apache.maven.plugins:maven-jar-plugin:3.5.1:jar
...
* install: org.apache.maven.plugins:maven-install-plugin:3.2.0:install
* deploy: org.apache.maven.plugins:maven-deploy-plugin:3.2.0:deploy
This is the lifecycle of Build Lifecycle with the plugin versions this very project uses.
Source: Help plugin: describe
help:evaluate
help:evaluate evaluates an expression against the project, which is the only reliable way to read a value that is computed from properties and inheritance. With -q -DforceStdout, the value is the only thing
printed, which makes it usable in a shell script (Maven 3.10.0):
$ mvn -B -ntp -q help:evaluate -Dexpression=maven.compiler.release -DforceStdout -pl tidepool-engine
21
$ mvn -B -ntp -q help:evaluate -Dexpression=project.build.directory -DforceStdout -pl tidepool-engine
/work/tidepool/tidepool-engine/target
Source: Help plugin: evaluate
dependency:tree
When the classpath is wrong, ask for the tree; -Dverbose includes the dependencies that were omitted and why, and -Dincludes filters it. Declaring commons-beanutils 1.9.4 (which wants
commons-collections 3.2.2) next to a direct commons-collections 3.2.1 in the engine module gave, on Maven 3.10.0:
[INFO] --- dependency:3.11.0:tree (default-cli) @ tidepool-engine ---
[INFO] io.tidepool:tidepool-engine:jar:1.0.0-SNAPSHOT
[INFO] +- commons-beanutils:commons-beanutils:jar:1.9.4:compile
[INFO] | +- commons-logging:commons-logging:jar:1.2:compile
[INFO] | \- (commons-collections:commons-collections:jar:3.2.2:compile - omitted for conflict with 3.2.1)
[INFO] +- commons-collections:commons-collections:jar:3.2.1:compile
...
The nearest declaration wins, here the older 3.2.1 that was declared directly; the line with omitted for conflict is the loser. -Dincludes=commons-logging shows the path by which one
artifact arrives (commons-logging:commons-logging:jar:1.2 comes through commons-beanutils). The mediation rules, dependency:analyze, and the convergence checks are on
Dependency Analysis & Convergence and
Dependencies, Scopes & Mediation.
Source: Dependency plugin: tree
Summary of the tools
| Question | Tool | Page with details |
|---|---|---|
What did Maven read, and from where? |
|
|
Which value did the plugin get? |
|
|
Where does a POM value come from? |
|
|
Which settings, mirrors, servers apply? |
|
|
Which profiles are active? |
|
|
What does a phase run? |
|
|
Why is this version on the classpath? |
|
|
Is the download the right file? |
checksum policy, |
Source: Apache Maven Help Plugin
Best practices
The practices below are the ones that remove the failures of the sections above. Each says what to do, why, and what it looked like when the practice was missing. They match the chapter on practices and the troubleshooting appendix of the Maven 2 book in the bibliography, updated to Maven 3.10; Where the book is outdated says what changed.
Pin every plugin version
A plugin without a version is resolved to whatever the Super POM or the repository metadata says on that day. On Maven 3.9.16 the Super POM pinned a few plugins (maven-antrun-plugin,
maven-assembly-plugin, maven-dependency-plugin and maven-release-plugin, as the help:effective-pom diff between 3.9.16 and 3.10.0 showed) and Maven 3.10.0 pins none of those, so an unpinned plugin
now means a warning and a build that changes when a new plugin version is released. The pins belong in pluginManagement of the parent POM, including the plugins that Maven
brings in by itself through the lifecycle (clean, install, deploy, site), which people forget because they never wrote them. The requirePluginVersions rule of maven-enforcer-plugin finds the forgotten
ones. With only compiler, resources, surefire, jar and the enforcer pinned in a corporate parent, the rule failed on Maven 3.10.0:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-enforcer-plugin:3.6.3:enforce (enforce-hygiene) on project tidepool-corporate-parent:
[ERROR] Rule 0: org.apache.maven.enforcer.rules.RequirePluginVersions failed with message:
[ERROR] Some plugins are missing valid versions or depend on Maven 3.10.0 defaults (LATEST, RELEASE, SNAPSHOT, TIMESTAMP SNAPSHOT as plugin version are not allowed)
[ERROR] org.apache.maven.plugins:maven-clean-plugin. The version currently in use is 3.5.0 via default lifecycle bindings
[ERROR] org.apache.maven.plugins:maven-install-plugin. The version currently in use is 3.2.0 via default lifecycle bindings
[ERROR] org.apache.maven.plugins:maven-site-plugin. The version currently in use is 3.22.0 via default lifecycle bindings
[ERROR] org.apache.maven.plugins:maven-deploy-plugin. The version currently in use is 3.2.0 via default lifecycle bindings
The message even gives the version that is in use today, which is the version to write down. Update the pins on purpose (a dependency-update bot or the Versions plugin’s display-plugin-updates, see
Dependency Analysis & Convergence), not by accident. The rule and its siblings are on
Maven Enforcer; the plugin elements are on
Plugins & Their Configuration.
Give the organisation one parent POM
The settings that every project of an organisation repeats — the plugin pins above, the Java release, the encoding, the enforcer rules, the reproducible-build timestamp, the
distribution management — belong in one parent POM that is versioned and published like any other artifact, with the projects inheriting from it by coordinates. The corporate parent that
produced the output above, in full (it is a pom-packaging project, so it has no sources):
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>io.tidepool</groupId>
<artifactId>tidepool-corporate-parent</artifactId>
<version>7</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputTimestamp>2026-01-01T00:00:00Z</project.build.outputTimestamp>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin><artifactId>maven-compiler-plugin</artifactId><version>3.16.0</version></plugin>
<plugin><artifactId>maven-resources-plugin</artifactId><version>3.5.0</version></plugin>
<plugin><artifactId>maven-surefire-plugin</artifactId><version>3.6.0</version></plugin>
<plugin><artifactId>maven-jar-plugin</artifactId><version>3.5.1</version></plugin>
<plugin><artifactId>maven-install-plugin</artifactId><version>3.2.0</version></plugin>
<plugin><artifactId>maven-deploy-plugin</artifactId><version>3.2.0</version></plugin>
<plugin><artifactId>maven-clean-plugin</artifactId><version>3.5.0</version></plugin>
<plugin><artifactId>maven-site-plugin</artifactId><version>3.22.0</version></plugin>
<plugin><artifactId>maven-enforcer-plugin</artifactId><version>3.6.3</version></plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-hygiene</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requirePluginVersions/>
<requireMavenVersion><version>[3.9.0,)</version></requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
After mvn install of the parent, an application whose POM contains only the parent element, an artifactId and a version built with the compiler, resources, surefire and jar
plugins at the pinned versions and with the two enforcer rules passing (Rule 0: ... RequirePluginVersions passed, Rule 1: ... RequireMavenVersion passed). Before the install, the same application
failed with the Non-resolvable parent POM error of stage 1 above. Distributing the same parent also distributes mistakes, so version it (here 7, not a SNAPSHOT) and let projects choose when to move to the next version.
The inheritance mechanics, the relativePath and the multi-module alternative are on
Inheritance, Aggregation & the Effective POM, and the repository-level side (a repository manager,
the same settings.xml for every developer and the CI, repository URLs in the settings rather than in the POMs) in
Repositories, Mirrors & Repository Managers.
Manage versions in one place: a BOM
Declare each dependency version once, in dependencyManagement of the parent or in an imported BOM, and leave the versions out of the module POMs; the tidepool modules show it (tidepool-cli lists tidepool-engine and
picocli without versions). The -Dverbose effective POM above shows the result: the version of commons-csv in the engine module is the parent’s line 40. The mechanics and the import scope are on
Dependency Management & BOMs. Avoid version ranges and LATEST or RELEASE for dependencies; Versions, SNAPSHOTs & Ranges says why.
Keep the API and the implementation apart
Maven has no api versus implementation configuration as Gradle has (see below): a compile dependency of a module is on the compile class path of every consumer. A module cannot hide a dependency
from the consumers except with optional, and a dependency that is needed only at run time is declared runtime. The reliable design is therefore at module level: an API module that holds the interfaces and the
model and has few dependencies, and one or more implementation modules that depend on it, so that consumers compile against the API and get the implementation (a logging back end, a database driver) at runtime in
their final application. In tidepool, tidepool-model plays the role of the API: it depends only on JUnit (test scope). Check the result with dependency:analyze (used but undeclared, declared but unused; see
Dependency Analysis & Convergence). optional and exclusions are on
Dependencies, Scopes & Mediation.
No system scope, no files in the project
The system scope points at a jar by path and ties the build to a machine. A system dependency whose systemPath is ${project.basedir}/libs/legacy-lib-2.1.jar produced, on Maven 3.10.0:
[WARNING] Some problems were encountered while building the effective model for io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT
[WARNING] 'dependencies.dependency.scope' for com.example.legacy:legacy-lib:jar declares usage of deprecated 'system' scope @ line 15, column 14
[WARNING] 'dependencies.dependency.systemPath' for com.example.legacy:legacy-lib:jar should not point at files within the project directory, ${project.basedir}/libs/legacy-lib-2.1.jar will be unresolvable by dependent projects @ line 16, column 19
Maven 3.9.16 printed only the systemPath warning; Maven 4.0.0-rc-7 printed 2 problems were encountered while building the effective model and lists them with -e. The warning says what goes wrong: a project that depends on tidepool-cli cannot find the file. Install
the jar in a repository instead, with install:install-file for a local one or by deploying it to the repository manager; the steps, with the relocation alternative, are on
Installing Third-Party Jars & Relocation.
No SNAPSHOTs in a release
A release that depends on a SNAPSHOT cannot be rebuilt: the SNAPSHOT changes. Let the build refuse it: the enforcer’s requireReleaseDeps rule (with onlyWhenRelease it checks only when the project itself is a release) and
the release plugin’s own check, both described in Maven Enforcer and Releasing. For the day-to-day build, snapshot
repositories are enabled per repository, not globally, as in Versions, SNAPSHOTs & Ranges.
Make the build reproducible and independent of the machine
-
Use the Maven Wrapper, so that every developer and the CI use the same Maven: Maven Wrapper. A project-level
.mvn/maven.configand.mvn/jvm.configcarry options and the JVM flags that everyone must share: Configuring Maven. -
Put
-V(and-B -ntp) in the CI command so that a log tells which Maven and JDK produced it. -
Set
project.build.outputTimestamp(as the parent above does) so that the same sources give the same archives: Security & Reproducible Builds. -
Use the enforcer for the rules that you would otherwise write in a wiki: Maven and Java versions, dependency convergence, banned dependencies, plugin versions: Maven Enforcer.
-
Keep the repository URLs, the mirrors and the credentials in
settings.xmlrather than in the POM, and use one mirror for external repositories: settings.xml & Password Encryption.
Do not use profiles to describe environments
A profile that selects a different database or a different set of dependencies for "dev", "test" and "prod" produces artifacts that were never tested as built: the jar that goes to production is not the jar that the
tests ran against, and the effective POM depends on a -P flag that is easy to forget. Build one artifact and supply the environment at run time (a configuration file, an environment variable, a system property). Profiles
are for things that really differ in the build — a JDK-specific option, an operating system — and are best kept few; see Profiles, whose pitfalls section shows
how an inactive profile hides a mistake. help:active-profiles and help:effective-pom are the tools that find these.
Keep it simple, and split by change
Prefer the conventions to configuration: the standard layout (Philosophy, Coordinates & Directory Layout), one artifact per module, the
default lifecycle bindings. A POM full of executions and phase overrides is a sign that the project works against Maven; a script in antrun for a thing a plugin does is a sign of the same. Split a project into
modules by what changes together and by dependency direction (API, implementation, application), not by package names, and keep the reactor acyclic and shallow: Multi-Module Builds & the Reactor. Speed
problems are a different topic: Build Performance.
A checklist
| Practice | How to check it |
|---|---|
Every plugin is pinned, including the lifecycle ones |
|
One parent POM for the organisation, versioned |
|
Versions declared once (BOM or |
no |
API and implementation in separate modules |
|
No |
no deprecated |
No SNAPSHOTs in a release |
|
The same Maven and JDK everywhere |
wrapper in the repository; |
Environment-independent artifacts |
no |
What differs between Maven versions
Everything in this table was seen in the runs of this page; versions not listed in a row behaved like the one before them.
| Situation | 3.9.16 | 3.10.0 | 4.0.0-rc-7 (release candidate) |
|---|---|---|---|
Duplicate dependency declaration |
|
|
|
Plugin without version, not managed |
silent (the Super POM pins some plugins, such as antrun) |
|
|
|
only the |
deprecated-scope warning and |
|
Artifact not on any repository |
|
|
the |
Cached failure |
message repeated per repository |
|
only |
Unknown plugin parameter |
warning |
warning |
warning |
Super POM |
pins antrun, assembly, dependency, release plugins |
pins none of those; adds |
not compared here |
Without |
nothing |
nothing |
|
The migration side of the Maven 4 differences is on Migrating to Maven 4.
The same ideas in Ant and Gradle
-
Ant has no model to validate and no dependency resolution of its own, so stage 1 to 3 failures belong to the build file and to Ivy or the Resolver Ant Tasks. Its
-verboseand-debugoptions correspond to-X, and the Maven Resolver Ant Tasks fail with the messages of this page: see Running Ant and Maven Resolver Ant Tasks. -
Gradle separates
apifromimplementation(the API/implementation split above is a language feature there), pins plugin versions in thepluginsblock or a version catalog, reports a failure with--stacktraceand--info/--debug, and refreshes cached dependencies with--refresh-dependencies, the counterpart of-U: see Best Practices & Troubleshooting for Gradle. -
The three tools side by side: Comparing Ant, Maven & Gradle, and how a Maven repository setup looks from each: Repositories & Publishing Across Tools.
Where the book is outdated
Apache Maven 2 Effective Implementation (Ching and Porter, Packt, 2009) was written for Maven 2.2. Its advice on organising a build — one parent for the organisation, versions managed centrally, small modules, a repository manager, a release process that is scripted — stands; the details have moved:
-
Maven 2.2 has been end of life since 2014; the maintained lines are 3.10.0 and 3.9.16, with 4.0.0-rc-7 as a release candidate. Messages, option names and defaults on this page are those of the 3.x and 4 lines.
-
The book relies on plugins without versions and on the
RELEASEandLATESTmetaversions; the practice today is to pin every plugin (see above), and Maven 3.10 no longer pins any for you. -
Repositories on plain
http://, normal in the Maven 2 era, are blocked since Maven 3.8.1. -
Its troubleshooting depends on
-Xand the Help plugin; both are still there and work as described, with more output (theReading ... settingslines, the repository filter) than in 2.2. The-Uoption exists, and from 3.10-UAand-UMsplit it. -
Its coverage tool (EMMA), its Archiva and Continuum servers, and its Subversion-based release examples are historical; see Site & Reporting, Repositories, Mirrors & Repository Managers and Releasing.
Summary of pitfalls
-
The first error is the cause; the rest cascade. A truncated jar produced six compiler errors, and the real cause was the first line.
-
Read the stage: before any
---header it is the POM, the command line or the repositories; after it, a plugin wrote the text. -
A failure to download is cached. After fixing the cause, run once with
-U(or-UAon 3.10); do not keep-Uin every command. -
A
[WARNING]about a missing plugin version, a deprecated scope or an unknown parameter is a build that does not do what its POM says. Fix it or enforce that it cannot appear. -
mvn -pl modulewithout-amfails when the siblings have not been installed; theprefixes: ... NOT allowed from centralline next to it is noise. -
The JDK that runs Maven is the one from
JAVA_HOMEor thePATH; checkmvn -v, not the IDE. -
The default checksum policy only warns, and the damaged file is kept.
-
Deleting the whole local repository is the last resort. Delete the damaged artifact, or use
dependency:purge-local-repository. -
Test passwords, mirrors and URLs with
help:effective-settingsand-Xbefore suspecting the server. -
Profiles that switch environments produce artifacts nobody tested as built.