Install, Deploy & Distribution Management

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 with the Apache Maven Install Plugin 3.2.0, the Apache Maven Deploy Plugin 3.2.0, the Source Plugin 3.4.0, the Javadoc Plugin 3.12.0 and the Site Plugin 3.22.0. Every example marked as run was executed on JDK 26 on 2026-10-16 in scratch copies of the tidepool project (the parent io.tidepool:tidepool-parent with tidepool-model, tidepool-engine and tidepool-cli). Maven 3.9.16 and Maven 4.0.0-rc-7 (a release candidate) were run wherever the page says so. In the output, <lab> is the scratch directory, <proj> is the directory of the project being built, the local repository was <lab>/m2 (set with -Dmaven.repo.local, so the real ~/.m2/repository was not used), lines were filtered with grep (said each time), transfer sizes and speeds were cut, and the timestamp of a snapshot upload is written TS because it changes on every run. Nothing was published to a real server. The repositories were directories reached through file: URLs, and, for credentials, a small Python HTTP server on localhost that is described below. Publishing to Maven Central is on Publishing to Maven Central and is "not run here".

Building an artifact is only half of a build’s job; the other half is making it available to somebody else. Maven splits that into two steps that are easy to confuse. Install copies the result into the local repository of the machine, so that other projects built on the same machine can use it. Deploy uploads it to a remote repository, so that the rest of the world can. The address of that remote repository is not a property of the plugin but of the project, and lives in the POM element distributionManagement. This page covers the two plugins, that element, the credentials a remote repository asks for, what exactly gets written, and what changed in Maven 3.10 and Maven 4. The repositories themselves (layout, mirrors, managers) are on Repositories, Mirrors & Repository Managers; settings.xml and encrypted passwords on settings.xml & Password Encryption; the release process that ends in a deploy on Releasing. The scenario is on the section landing page.

Where install and deploy sit

install and deploy are the last two phases of the default lifecycle (the others are on Build Lifecycle). Each is a phase with one default binding, and both are bound to the goal of the same name for every packaging that produces an artifact:

Phase Default goal What it does

package

jar:jar for a JAR

Builds the artifact in target/; it is not copied anywhere yet.

verify

none for most packagings

Where checks on the package (integration tests, quality gates) belong.

install

install:install

Copies the project’s artifacts into the local repository.

deploy

deploy:deploy

Uploads the project’s artifacts to the remote repository named in distributionManagement.

A phase runs every phase before it, so mvn deploy also installs; there is no way to deploy without installing except by calling the goal directly (mvn deploy:deploy, which needs an already built package) or skipping the install (below).

flowchart LR PKG["package: target/*.jar"] --> VER["verify"] VER --> INS["install:install"] INS --> LOCAL[("local repository (default: user home .m2/repository)")] INS --> DEP["deploy:deploy"] DEP --> REL["distributionManagement/repository (release versions)"] DEP --> SNAP["distributionManagement/snapshotRepository (SNAPSHOT versions)"] SITE["site:deploy (separate goal)"] --> STE["distributionManagement/site"]

The diagram shows the two uploads that deploy:deploy chooses between by the version of the project, and the site, which is not part of the default lifecycle and has its own goal.

The lab: tidepool with a distributionManagement

The parent POM of the lab pins the plugins in pluginManagement (which is why the versions above are known, see Maven Plugins & Their Configuration) and declares two repositories. Their URLs are built from a property, so that a command line can point the same POM somewhere else, and the base is a directory inside the project:

pom.xml (parent, abridged)
  <properties>
    <repo.url>file://${maven.multiModuleProjectDirectory}/build/repo</repo.url>
  </properties>

  <distributionManagement>
    <repository>
      <id>tidepool-releases</id>
      <url>${repo.url}/releases</url>
    </repository>
    <snapshotRepository>
      <id>tidepool-snapshots</id>
      <url>${repo.url}/snapshots</url>
    </snapshotRepository>
  </distributionManagement>

${maven.multiModuleProjectDirectory} is the directory that contains the .mvn directory (or the directory Maven was started in, when there is none), and it exists on 3.9.16, 3.10.0 and 4.0.0-rc-7. Maven 3.10 and 4 also have ${project.rootDirectory}, but 3.9.16 leaves that expression unresolved in a URL, so the older name is the portable one (see Inheritance, Aggregation & the Effective POM). The id values, not the URLs, are what links the repository to the credentials in settings.xml.

install: the local repository

What it writes

The Install Plugin has three goals: install:install (bound to the install phase), install:install-file (for a file that no project built, see below) and install:help. Running the phase for the whole tidepool reactor (mvn -B install, lines filtered with grep -E "install:|Installing|BUILD") printed:

[INFO] --- install:3.2.0:install (default-install) @ tidepool-parent ---
[INFO] Installing pom.xml to <lab>/m2/io/tidepool/tidepool-parent/1.0.0-SNAPSHOT/tidepool-parent-1.0.0-SNAPSHOT.pom
[INFO] --- install:3.2.0:install (default-install) @ tidepool-model ---
[INFO] Installing tidepool-model/pom.xml to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.pom
[INFO] Installing tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT.jar to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.jar
[INFO] --- install:3.2.0:install (default-install) @ tidepool-engine ---
[INFO] Installing tidepool-engine/pom.xml to <lab>/m2/io/tidepool/tidepool-engine/1.0.0-SNAPSHOT/tidepool-engine-1.0.0-SNAPSHOT.pom
[INFO] Installing tidepool-engine/target/tidepool-engine-1.0.0-SNAPSHOT.jar to <lab>/m2/io/tidepool/tidepool-engine/1.0.0-SNAPSHOT/tidepool-engine-1.0.0-SNAPSHOT.jar
[INFO] --- install:3.2.0:install (default-install) @ tidepool-cli ---
[INFO] Installing tidepool-cli/pom.xml to <lab>/m2/io/tidepool/tidepool-cli/1.0.0-SNAPSHOT/tidepool-cli-1.0.0-SNAPSHOT.pom
[INFO] Installing tidepool-cli/target/tidepool-cli-1.0.0-SNAPSHOT.jar to <lab>/m2/io/tidepool/tidepool-cli/1.0.0-SNAPSHOT/tidepool-cli-1.0.0-SNAPSHOT.jar
[INFO] BUILD SUCCESS

The parent installs only its POM, because a pom packaging has no JAR (the packaging is in POM Fundamentals). The modules install their POM and their JAR; the POM matters as much as the JAR, because it is what a consumer reads to find the transitive dependencies. In the local repository a module looks like this (find tidepool-model -type f | sort):

tidepool-model/1.0.0-SNAPSHOT/_remote.repositories
tidepool-model/1.0.0-SNAPSHOT/maven-metadata-local.xml
tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.jar
tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.pom
tidepool-model/maven-metadata-local.xml

There are no checksum files: the Install Plugin 3.2.0 writes none (a deploy does, below). _remote.repositories is a record of where each file came from, kept by Maven Resolver:

#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
tidepool-model-1.0.0-SNAPSHOT.jar>=
tidepool-model-1.0.0-SNAPSHOT.pom>=

The >= with nothing after it means the file was put there by an install, from no repository; a file downloaded from the repository with the id central reads >central=, and Resolver then uses it only for builds that list that repository (the effect is on Repositories, Mirrors & Repository Managers). maven-metadata-local.xml is the local version of the metadata that a remote repository keeps, here for a snapshot:

<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
  <groupId>io.tidepool</groupId>
  <artifactId>tidepool-model</artifactId>
  <versioning>
    <lastUpdated>TIMESTAMP</lastUpdated>
    <snapshot>
      <localCopy>true</localCopy>
    </snapshot>
    <snapshotVersions>
      <snapshotVersion>
        <extension>pom</extension>
        <value>1.0.0-SNAPSHOT</value>
        <updated>TIMESTAMP</updated>
      </snapshotVersion>
      <snapshotVersion>
        <extension>jar</extension>
        <value>1.0.0-SNAPSHOT</value>
        <updated>TIMESTAMP</updated>
      </snapshotVersion>
    </snapshotVersions>
  </versioning>
  <version>1.0.0-SNAPSHOT</version>
</metadata>

(TIMESTAMP replaces the install time, which changes on every run.) localCopy tells the resolver that this snapshot is a copy that was installed locally, not downloaded from a timestamped build in a remote repository.

The parameters

The parameters below come from help:describe -Dplugin=org.apache.maven.plugins:maven-install-plugin:3.2.0 -Ddetail.

Goal and parameter User property Meaning

install skip

maven.install.skip

Do not install. For artifacts that must not reach the local repository.

install installAtEnd

installAtEnd

Install every project of the reactor at the end of the build instead of during each project’s own install phase; when the build fails, nothing is installed. Described as experimental by the plugin.

install allowIncompleteProjects

allowIncompleteProjects

A project with a packaging other than pom that has no main artifact is an error by default; this allows it (typical for a project that only attaches an assembly).

install-file file

file

The file to install (required).

install-file groupId, artifactId, version, packaging

same names

Coordinates; taken from pomFile or from a pom.xml inside the JAR when absent.

install-file classifier, sources, javadoc

same names

A classifier for the file, or the bundled sources and Javadoc to install beside it.

install-file pomFile, generatePom

pomFile, generatePom

An existing POM, or generate a minimal one when no POM is given (the default when the local repository has none yet).

install-file localRepositoryPath

localRepositoryPath

Install into a specific directory instead of the configured local repository.

installAtEnd and skipping

With -DinstallAtEnd=true each module only registers itself, and the installs happen after the last project (the deferred messages come first, then the installs; the filter was grep -E "install:|Deferring|Installing .*\.jar |BUILD", so the POM installs are not shown):

[INFO] --- install:3.2.0:install (default-install) @ tidepool-parent ---
[INFO] Deferring install for io.tidepool:tidepool-parent:1.0.0-SNAPSHOT at end
[INFO] --- install:3.2.0:install (default-install) @ tidepool-model ---
[INFO] Deferring install for io.tidepool:tidepool-model:1.0.0-SNAPSHOT at end
[INFO] --- install:3.2.0:install (default-install) @ tidepool-engine ---
[INFO] Deferring install for io.tidepool:tidepool-engine:1.0.0-SNAPSHOT at end
[INFO] --- install:3.2.0:install (default-install) @ tidepool-cli ---
[INFO] Deferring install for io.tidepool:tidepool-cli:1.0.0-SNAPSHOT at end
[INFO] Installing tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT.jar to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.jar
[INFO] Installing tidepool-engine/target/tidepool-engine-1.0.0-SNAPSHOT.jar to <lab>/m2/io/tidepool/tidepool-engine/1.0.0-SNAPSHOT/tidepool-engine-1.0.0-SNAPSHOT.jar
[INFO] Installing tidepool-cli/target/tidepool-cli-1.0.0-SNAPSHOT.jar to <lab>/m2/io/tidepool/tidepool-cli/1.0.0-SNAPSHOT/tidepool-cli-1.0.0-SNAPSHOT.jar
[INFO] BUILD SUCCESS

-Dmaven.install.skip=true turns the goal into a no-op for every project:

[INFO] --- install:3.2.0:install (default-install) @ tidepool-parent ---
[INFO] Skipping artifact installation
[INFO] --- install:3.2.0:install (default-install) @ tidepool-model ---
[INFO] Skipping artifact installation
[INFO] --- install:3.2.0:install (default-install) @ tidepool-engine ---
[INFO] Skipping artifact installation
[INFO] --- install:3.2.0:install (default-install) @ tidepool-cli ---
[INFO] Skipping artifact installation
[INFO] BUILD SUCCESS

install-file is for JARs that no Maven project built, such as a vendor library; its use, and the relocation that is the POM’s way of renaming an artifact, are on Installing Third-Party JARs & Relocation.

distributionManagement

distributionManagement says where the outputs of this project go. It is not to be confused with <repositories>, which says where the project’s inputs come from. The element has these children:

Element Meaning

repository

Where release versions are uploaded. Children: id, name, url, layout, uniqueVersion.

snapshotRepository

Where -SNAPSHOT versions are uploaded. If it is absent, repository is used for snapshots too.

site

Where site:deploy uploads the generated site (id, name, url), see below.

relocation

Tells consumers that the artifact moved to other coordinates.

downloadUrl

A page where users can download the artifact, for people; no tool reads it.

status

The status of the artifact in the remote repository (none, converted, partner, deployed, verified). The reference says never to write it by hand, because Maven sets it when the project is transported to the repository.

The layout (default or legacy) and uniqueVersion children date from Maven 2. Maven 3 supports only the default layout, and uniqueVersion is ignored: a run with <uniqueVersion>false</uniqueVersion> in the repository, on a project without a snapshotRepository, still wrote a timestamped file with a build number (see the next section).

The <id> of a repository, snapshotRepository or site is the name that Maven looks up in the <servers> of settings.xml for the user name and password (below). The id is arbitrary, but it identifies the repository among all the others of the build, so it must be unique. A typo in the id does not fail the build: the upload is simply attempted without credentials, and a protected repository answers 401.

snapshotRepository is optional

Without a snapshotRepository, a snapshot version is sent to repository. Removing it from the lab and deploying tidepool-model (filtered with grep -E "Uploaded to.*(jar|pom) |BUILD") printed:

[INFO] Uploaded to tidepool-releases: file://<proj>/build/repo/releases/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.pom
[INFO] Uploaded to tidepool-releases: file://<proj>/build/repo/releases/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar
[INFO] BUILD SUCCESS

The files of a snapshot landed under releases, which is a trap: a repository manager that keeps release and snapshot repositories apart may reject the upload. Keep both elements.

No distributionManagement at all

A project without the element cannot be deployed by the lifecycle. Deploying the same module from a POM without it failed in the Deploy Plugin, after the build had succeeded:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:3.2.0:deploy (default-deploy) on project tidepool-model: Deployment failed: repository element was not specified in the POM inside distributionManagement element or in -DaltDeploymentRepository=id::url parameter -> [Help 1]

The message names the two ways out, the POM element and the altDeploymentRepository parameter described below.

deploy: what is uploaded

A snapshot

mvn -B -pl tidepool-model deploy (filtered with grep -E "deploy:|Uploaded|BUILD", the preceding phases are the usual ones) printed:

[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.pom
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/maven-metadata.xml
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/maven-metadata.xml
[INFO] BUILD SUCCESS

The version 1.0.0-SNAPSHOT was not uploaded under that name. The file names carry a timestamp and a build number (TS-1: the date, the time in UTC and the sequence number), and the directory 1.0.0-SNAPSHOT still carries the literal version. Everything the resolver needs to find the newest one is in maven-metadata.xml files. After the build, with the checksum files included, the repository contained (find build/repo -type f | sort):

./snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/maven-metadata.xml
./snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/maven-metadata.xml.md5
./snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/maven-metadata.xml.sha1
./snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar
./snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar.md5
./snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar.sha1
./snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.pom
./snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.pom.md5
./snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.pom.sha1
./snapshots/io/tidepool/tidepool-model/maven-metadata.xml
./snapshots/io/tidepool/tidepool-model/maven-metadata.xml.md5
./snapshots/io/tidepool/tidepool-model/maven-metadata.xml.sha1

Deploying again, a few seconds later, did not replace anything: it added the files for build number 2 and rewrote the metadata of the version directory to point at it. The directory listing, and the metadata of the version directory after the second deploy:

maven-metadata.xml
maven-metadata.xml.md5
maven-metadata.xml.sha1
tidepool-model-1.0.0-TS-1.jar
tidepool-model-1.0.0-TS-1.jar.md5
tidepool-model-1.0.0-TS-1.jar.sha1
tidepool-model-1.0.0-TS-1.pom
tidepool-model-1.0.0-TS-1.pom.md5
tidepool-model-1.0.0-TS-1.pom.sha1
tidepool-model-1.0.0-TS-2.jar
tidepool-model-1.0.0-TS-2.jar.md5
tidepool-model-1.0.0-TS-2.jar.sha1
tidepool-model-1.0.0-TS-2.pom
tidepool-model-1.0.0-TS-2.pom.md5
tidepool-model-1.0.0-TS-2.pom.sha1
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
  <groupId>io.tidepool</groupId>
  <artifactId>tidepool-model</artifactId>
  <versioning>
    <lastUpdated>DATETIME</lastUpdated>
    <snapshot>
      <timestamp>DATE.TIME</timestamp>
      <buildNumber>2</buildNumber>
    </snapshot>
    <snapshotVersions>
      <snapshotVersion>
        <extension>pom</extension>
        <value>1.0.0-DATE.TIME-2</value>
        <updated>DATETIME</updated>
      </snapshotVersion>
      <snapshotVersion>
        <extension>jar</extension>
        <value>1.0.0-DATE.TIME-2</value>
        <updated>DATETIME</updated>
      </snapshotVersion>
    </snapshotVersions>
  </versioning>
  <version>1.0.0-SNAPSHOT</version>
</metadata>

timestamp and buildNumber name the newest snapshot, and each snapshotVersion entry maps an extension (and a classifier) to the value to download. The other file, in the artifact’s own directory, lists the versions the repository knows about:

<?xml version="1.0" encoding="UTF-8"?>
<metadata>
  <groupId>io.tidepool</groupId>
  <artifactId>tidepool-model</artifactId>
  <versioning>
    <versions>
      <version>1.0.0-SNAPSHOT</version>
    </versions>
    <lastUpdated>DATETIME</lastUpdated>
  </versioning>
</metadata>

A consumer that depends on 1.0.0-SNAPSHOT reads these files, and downloads the newest timestamped build; the update policy (daily by default) decides how often it looks (see Versions, Snapshots & Ranges). To check that the repository is usable, a new project that declares the directory as a repository and depends on tidepool-cli:1.0.0-SNAPSHOT, with an empty local repository, resolved the whole graph (dependency:list, filtered):

[INFO] The following files have been resolved:
[INFO]    io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT:compile -- module tidepool.cli (auto)
[INFO]    io.tidepool:tidepool-engine:jar:1.0.0-SNAPSHOT:compile -- module tidepool.engine (auto)
[INFO]    io.tidepool:tidepool-model:jar:1.0.0-SNAPSHOT:compile -- module tidepool.model (auto)
[INFO]    org.apache.commons:commons-csv:jar:1.14.1:compile -- module org.apache.commons.csv
[INFO]    commons-io:commons-io:jar:2.20.0:compile -- module org.apache.commons.io
[INFO]    commons-codec:commons-codec:jar:1.19.0:compile -- module org.apache.commons.codec
[INFO]    info.picocli:picocli:jar:4.7.7:compile -- module info.picocli
[INFO] BUILD SUCCESS

The same POM also gives a release: a version without -SNAPSHOT is uploaded under its own name, in the repository URL, and a repository manager normally refuses a second upload of the same release file (see "Credentials" below).

Checksums

For every file Maven also uploads a checksum, by default .sha1 and .md5. They are the hash of the bytes sent, so a consumer (or a repository manager) can detect corruption:

$ cat tidepool-model-1.0.0-TS-1.jar.sha1
4323ad73b2363318d68170288e32a1c41bd8d42b
$ shasum -a 1 tidepool-model-1.0.0-TS-1.jar
4323ad73b2363318d68170288e32a1c41bd8d42b  tidepool-model-1.0.0-TS-1.jar

The algorithms are a Maven Resolver setting, aether.checksums.algorithms, a comma-separated list that defaults to SHA-1,MD5. A deploy with -Daether.checksums.algorithms=SHA-1,SHA-256,SHA-512,MD5 on 3.10.0 listed (ls | grep jar):

tidepool-model-1.0.0-TS-1.jar
tidepool-model-1.0.0-TS-1.jar.md5
tidepool-model-1.0.0-TS-1.jar.sha1
tidepool-model-1.0.0-TS-1.jar.sha256
tidepool-model-1.0.0-TS-1.jar.sha512

The same property with SHA-1,SHA-256 also worked on Maven 3.9.16 and 4.0.0-rc-7. Central requires .md5 and .sha1 files and accepts the stronger ones in addition (see Publishing to Maven Central); the verification a consumer can do with them is on Supply Chain Security.

The whole reactor, in order

A deploy of the whole reactor uploaded each module in the order of the reactor, as its own deploy phase, parent first (filtered with grep -E "deploy:|Uploaded to.*(\.jar|-1\.pom) |BUILD", so the POM and JAR uploads show, and the metadata uploads do not):

[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-parent/1.0.0-SNAPSHOT/tidepool-parent-1.0.0-TS-1.pom
[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.pom
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar
[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-engine/1.0.0-SNAPSHOT/tidepool-engine-1.0.0-TS-1.pom
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-engine/1.0.0-SNAPSHOT/tidepool-engine-1.0.0-TS-1.jar
[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-cli/1.0.0-SNAPSHOT/tidepool-cli-1.0.0-TS-1.pom
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-cli/1.0.0-SNAPSHOT/tidepool-cli-1.0.0-TS-1.jar
[INFO] BUILD SUCCESS

That order has a consequence: if the build fails in the third module, the first two are already deployed. deployAtEnd changes it.

deployAtEnd

-DdeployAtEnd=true (the user property has the same name as the parameter) makes each project only register what it would deploy; the uploads happen after the last project has built successfully:

[INFO] Deferring deploy for io.tidepool:tidepool-parent:1.0.0-SNAPSHOT at end
[INFO] Deferring deploy for io.tidepool:tidepool-model:1.0.0-SNAPSHOT at end
[INFO] Deferring deploy for io.tidepool:tidepool-engine:1.0.0-SNAPSHOT at end
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-engine/1.0.0-SNAPSHOT/tidepool-engine-1.0.0-TS-1.jar
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-cli/1.0.0-SNAPSHOT/tidepool-cli-1.0.0-TS-1.jar
[INFO] BUILD SUCCESS

(The last project, tidepool-cli, triggers the uploads, so its own Deferring line is not printed. The POM uploads and the parent are left out by the filter. The uploads are not in reactor order: this run uploaded the engine, the model and then the command line module.) The point of deployAtEnd is to avoid publishing three of four modules when the fourth one fails its tests. It is not atomic: if the upload of the third module fails, the first two are already in the repository, and Maven cannot take them back. The plugin’s description of the parameter says so explicitly. For something that must be all-or-nothing, deploy to a staging area and promote it (what Central does, see Publishing to Maven Central). The Install Plugin has the equivalent installAtEnd, above.

Skipping, and the typo that fails

maven.deploy.skip (parameter skip) skips the upload. A module that is a test harness or an example, never meant to be published, sets it in its own POM:

<properties>
  <maven.deploy.skip>true</maven.deploy.skip>
</properties>

and -Dmaven.deploy.skip=true on the command line does it for the whole build:

[INFO] --- deploy:3.2.0:deploy (default-deploy) @ tidepool-parent ---
[INFO] Skipping artifact deployment
[INFO] --- deploy:3.2.0:deploy (default-deploy) @ tidepool-model ---
[INFO] Skipping artifact deployment
[INFO] --- deploy:3.2.0:deploy (default-deploy) @ tidepool-engine ---
[INFO] Skipping artifact deployment
[INFO] --- deploy:3.2.0:deploy (default-deploy) @ tidepool-cli ---
[INFO] Skipping artifact deployment
[INFO] BUILD SUCCESS

Since Deploy Plugin 3.0.0-M2 the value is not only a boolean: releases skips when the version is a release and snapshots skips when it is a snapshot (-Dmaven.deploy.skip=snapshots on the lab, whose version is a snapshot, printed Skipping artifact deployment again); any other value fails the build, which protects against a typo in a control that exists to prevent a publication:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:3.2.0:deploy (default-deploy) on project tidepool-model: Unrecognized value 'snapshto' for maven.deploy.skip. Accepted values: true, false, releases, snapshots. -> [Help 1]

The parameters of the Deploy Plugin

These come from help:describe -Dplugin=org.apache.maven.plugins:maven-deploy-plugin:3.2.0 -Ddetail.

Goal and parameter User property Meaning

deploy altDeploymentRepository

altDeploymentRepository

id::url: deploy there instead of to distributionManagement; the id picks the credentials. Since 3.0.0 the layout part of id::layout::url is gone.

deploy altReleaseDeploymentRepository, altSnapshotDeploymentRepository

the same

Like the above, but only for release or only for snapshot versions.

deploy deployAtEnd

deployAtEnd

Deploy at the end of the reactor.

deploy retryFailedDeploymentCount

retryFailedDeploymentCount

How many times a failed upload is retried; values outside 1 to 10 are moved into the range. Default 1. Not run here.

deploy skip

maven.deploy.skip

true, false, releases or snapshots; anything else fails.

deploy allowIncompleteProjects

allowIncompleteProjects

Allow a non-pom project that has no main artifact but has attachments.

deploy-file file, url, repositoryId

file, url, repositoryId

The file, the repository URL and the server id (default remote-repository); all required.

deploy-file groupId, artifactId, version, packaging, classifier, extension

same names

Coordinates; read from pomFile (or the JAR’s embedded POM) when absent.

deploy-file pomFile, generatePom

pomFile, generatePom

An existing POM to upload beside the file; or generate a default one (generatePom defaults to true).

deploy-file sources, javadoc

sources, javadoc

Bundled sources and Javadoc JARs to upload with the main file.

deploy-file files, classifiers, types

the same

Comma-separated lists of extra files, with a classifier and type for each; the three lists must have the same length.

deploy-file skip

maven.deploy.file.skip

Like skip, for the deploy-file goal.

altDeploymentRepository

-DaltDeploymentRepository=id::url overrides the repository of the POM for one run, which is how a CI job publishes a branch build to a scratch location without touching the POM. Deploying tidepool-model from the POM without distributionManagement, with -DaltDeploymentRepository=lab::file://<proj>/build/alt, worked (filter: grep -E "deploy:|Uploaded to|BUILD"):

[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to lab: file://<proj>/build/alt/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.pom
[INFO] Uploaded to lab: file://<proj>/build/alt/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar
[INFO] Uploaded to lab: file://<proj>/build/alt/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/maven-metadata.xml
[INFO] Uploaded to lab: file://<proj>/build/alt/io/tidepool/tidepool-model/maven-metadata.xml
[INFO] BUILD SUCCESS

The old form id::layout::url is still accepted by 3.2.0 but warns (this is -DaltDeploymentRepository=lab::default::file://<proj>/build/alt2):

[WARNING] Using legacy syntax for alternative repository. Use "lab::file://<proj>/build/alt2" instead.
[INFO] BUILD SUCCESS

A book or a blog written for the Deploy Plugin 2.x will use id::default::url; drop the middle part. The repository chosen this way is used for releases and snapshots alike, unless you use the altReleaseDeploymentRepository and altSnapshotDeploymentRepository variants. The id is also the server id, so credentials work the same way.

Credentials

A real repository does not accept anonymous uploads. Maven finds the user name and password in settings.xml, in the <server> whose <id> equals the id of the repository (a settings.xml is per user and never goes into the project; the elements are on settings.xml & Password Encryption).

The runs below use a Python file server on localhost:18090 that accepts a PUT only with HTTP basic authentication for the user deployer with the placeholder password changeit-example, logs what it receives, and, to imitate a rule of repository managers, answers 400 to a second upload of a release file. That rule is the lab’s imitation, not the behaviour of any particular product. The lab uses the project as version 1.0.0, and -Drepo.url=http://localhost:18090 sends both repositories there.

First with no <server> (settings-none.xml, an empty <settings/>), the build fails at the first PUT; the log of the server shows both files denied:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:3.2.0:deploy (default-deploy) on project tidepool-model: Failed to deploy artifacts: Could not transfer artifact io.tidepool:tidepool-model:pom:1.0.0 from/to tidepool-releases (http://localhost:18090/releases): HTTP Status: 401 -> [Help 1]
PUT /releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom user=- DENIED
PUT /releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar user=- DENIED

HTTP Status: 401 is what a missing entry, a wrong id, a wrong password and an undecryptable password all look like; the log of the server (or -X) tells them apart. With a <server> for each id:

settings-deploy.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <servers>
    <server>
      <id>tidepool-releases</id>
      <username>deployer</username>
      <password>changeit-example</password>
    </server>
    <server>
      <id>tidepool-snapshots</id>
      <username>deployer</username>
      <password>changeit-example</password>
    </server>
  </servers>
</settings>
mvn -B -s settings-deploy.xml -pl tidepool-model deploy -Drepo.url=http://localhost:18090

The output (Maven 3.10.0, grep -E "WARN|Uploaded|BUILD"; the two long warnings are cut where marked):

[WARNING] Using credentials of server 'tidepool-releases' for repository http://localhost:18090/releases, although no repository or mirror with this id is declared in settings or on the command line and the server declares no <repositoryOrigins>. [...]
[WARNING] Using credentials of server 'tidepool-snapshots' for repository http://localhost:18090/snapshots, although no repository or mirror with this id is declared in settings or on the command line and the server declares no <repositoryOrigins>. [...]
[INFO] Uploaded to tidepool-releases: http://localhost:18090/releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom
[INFO] Uploaded to tidepool-releases: http://localhost:18090/releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar
[INFO] Uploaded to tidepool-releases: http://localhost:18090/releases/io/tidepool/tidepool-model/maven-metadata.xml
[INFO] BUILD SUCCESS
PUT /releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom user=deployer ok
PUT /releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom.sha1 user=deployer ok
PUT /releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom.md5 user=deployer ok
PUT /releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar user=deployer ok
PUT /releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar.sha1 user=deployer ok
PUT /releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar.md5 user=deployer ok
PUT /releases/io/tidepool/tidepool-model/maven-metadata.xml user=deployer ok
PUT /releases/io/tidepool/tidepool-model/maven-metadata.xml.sha1 user=deployer ok
PUT /releases/io/tidepool/tidepool-model/maven-metadata.xml.md5 user=deployer ok

The upload worked, and the log shows the PUT of each file followed by those of its checksums. The warnings are new in Maven 3.10.

Maven 3.10: credentials are scoped to an origin

According to the release notes of 3.10.0, the credentials of a server are sent only to the origin (scheme, host and port) of the repositories and mirrors that have that id, and the match on the id is exact. Maven finds those repositories among the ones declared in settings.xml and on the command line; a distributionManagement repository of the POM is not among them in these runs, because the warning appeared for the tidepool-releases id that the POM itself declares. To bind the credentials explicitly, name the origin in the server, which the same release notes document as repositoryOrigins (the full description is on the settings page):

settings-origins.xml (the snapshots server is the same)
    <server>
      <id>tidepool-releases</id>
      <username>deployer</username>
      <password>changeit-example</password>
      <repositoryOrigins>
        <repositoryOrigin>http://localhost:18090</repositoryOrigin>
      </repositoryOrigins>
    </server>

The property maven.repository.credentialScope=strict makes Maven refuse credentials that are not bound this way. A deploy with settings-origins.xml and -Dmaven.repository.credentialScope=strict on a fresh server, per Maven version (grep -E "WARN|Uploaded|BUILD|ERROR\] Failed"; the Failed to execute line is cut after the goal):

--- Maven 3.10.0
[INFO] Uploaded to tidepool-releases: http://localhost:18090/releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom
[INFO] Uploaded to tidepool-releases: http://localhost:18090/releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar
[INFO] Uploaded to tidepool-releases: http://localhost:18090/releases/io/tidepool/tidepool-model/maven-metadata.xml
[INFO] BUILD SUCCESS
--- Maven 3.9.16
[WARNING] Some problems were encountered while building the effective settings
[WARNING] Unrecognised tag: 'repositoryOrigins'
[INFO] Uploaded to tidepool-releases: http://localhost:18090/releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom
[INFO] Uploaded to tidepool-releases: http://localhost:18090/releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar
[INFO] Uploaded to tidepool-releases: http://localhost:18090/releases/io/tidepool/tidepool-model/maven-metadata.xml
[INFO] BUILD SUCCESS
--- Maven 4.0.0-rc-7
[WARNING] Not using credentials of server 'tidepool-releases' for repository http://localhost:18090/releases: no repository or mirror with this id is declared in settings or on the command line, and maven.repository.credentialScope=strict is in effect.
[WARNING] Not using credentials of server 'tidepool-snapshots' for repository http://localhost:18090/snapshots: no repository or mirror with this id is declared in settings or on the command line, and maven.repository.credentialScope=strict is in effect.
[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:3.2.0:deploy [...]

So the explicit origin works on 3.10.0. Maven 3.9.16 does not know the element and says so (Unrecognised tag), but it sends the credentials anyway because it has no scoping. Maven 4.0.0-rc-7 printed the warning with the short text and refused, in this run, to use the credentials even though the server named the origin, which is the behaviour the settings page found too. Without strict, each version sent the credentials, with these warnings (the long lines cut where marked):

--- Maven 3.10.0
[WARNING] Using credentials of server 'tidepool-releases' for repository http://localhost:18090/releases, although no repository or mirror with this id is declared in settings or on the command line and the server declares no <repositoryOrigins>. [...]
[WARNING] Using credentials of server 'tidepool-snapshots' for repository http://localhost:18090/snapshots, although no repository or mirror with this id is declared in settings or on the command line and the server declares no <repositoryOrigins>. [...]
[INFO] BUILD SUCCESS
--- Maven 3.9.16
[INFO] BUILD SUCCESS
--- Maven 4.0.0-rc-7
[WARNING] Using credentials of server 'tidepool-releases' for repository http://localhost:18090/releases, although no repository or mirror with this id is declared in settings or on the command line. Set maven.repository.credentialScope=strict to refuse such credential use.
[WARNING] Using credentials of server 'tidepool-snapshots' for repository http://localhost:18090/snapshots, although no repository or mirror with this id is declared in settings or on the command line. Set maven.repository.credentialScope=strict to refuse such credential use.
[INFO] BUILD SUCCESS

The practical advice: for a repository that the build declares in the POM, give the server repositoryOrigins on Maven 3.10, expect a warning without it, and do not turn on strict on Maven 4 release candidates until your repository is declared where Maven looks.

Release versions are immutable

Deploying the same release a second time, with the same credentials, was refused by the lab server:

[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:3.2.0:deploy (default-deploy) on project tidepool-model: Failed to deploy artifacts: Could not transfer artifact io.tidepool:tidepool-model:pom:1.0.0 from/to tidepool-releases (http://localhost:18090/releases): HTTP Status: 400 -> [Help 1]
PUT /releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom user=deployer ok
PUT /releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar user=deployer ok

HTTP Status: 400 here is the lab’s imitation of a repository manager that does not allow redeploying a release. A plain file repository (file:) has no such rule and overwrites silently, so a local test does not catch a pipeline that redeploys a release. The rule is right: a consumer that cached 1.0.0 must never see different bytes under that name. The way out is a new version, not a deploy of the same one (see Releasing).

Other transports and how the password is protected

file: and http:/https: work with the native HTTP transport of Maven 3.9 and later. scp:, sftp: and ftp: need a Wagon provider added as a build extension, which the repositories page describes as "Transports: native and Wagon"; they were not run here, and their privateKey and passphrase settings are in the server table of the settings page. The password in the file should be encrypted with the master-password mechanism of Maven 3 or mvnenc of Maven 4 (the settings page runs both), or come from an environment variable in the CI job.

Attached artifacts: sources and Javadoc

A repository consumer, and Central, wants more than the JAR: a -sources.jar for the IDE and a -javadoc.jar for the documentation. The JAR Plugin attaches nothing but the main artifact; other plugins attach extra files to the project, and install and deploy publish every attached artifact. The Source Plugin’s jar-no-fork goal (the plain jar goal first runs the generate-sources phase again, which jar-no-fork does not) and the Javadoc Plugin’s jar goal are the usual ones, bound to the package phase with an execution each:

pom.xml (parent, added to the lab)
    <plugins>
      <plugin>
        <artifactId>maven-source-plugin</artifactId>
        <executions>
          <execution>
            <id>attach-sources</id>
            <goals><goal>jar-no-fork</goal></goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-javadoc-plugin</artifactId>
        <executions>
          <execution>
            <id>attach-javadocs</id>
            <goals><goal>jar</goal></goals>
          </execution>
        </executions>
      </plugin>
    </plugins>

(The versions come from pluginManagement; the executions are named so that -X and the logs show where each JAR comes from, and because ids must be unique within a plugin, see Build Lifecycle.) Deploying tidepool-model (grep -E "source:|javadoc:|Building jar|Uploaded to.*(jar|pom) |BUILD"):

[INFO] Building jar: <proj>/tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT.jar
[INFO] --- source:3.4.0:jar-no-fork
[INFO] Building jar: <proj>/tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT-sources.jar
[INFO] --- javadoc:3.12.0:jar
[INFO] Building jar: <proj>/tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT-javadoc.jar
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.pom
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1-sources.jar
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1-javadoc.jar
[INFO] BUILD SUCCESS

The JAR’s Javadoc run printed twelve no @param for ... warnings for the records of tidepool, which are not shown. The extra files are named <artifactId>-<version>-<classifier>.<extension>, here with the same timestamp and build number as the main artifact:

maven-metadata.xml
tidepool-model-1.0.0-TS-1-javadoc.jar
tidepool-model-1.0.0-TS-1-sources.jar
tidepool-model-1.0.0-TS-1.jar
tidepool-model-1.0.0-TS-1.pom

and the metadata lists a snapshotVersion per classifier:

    <snapshotVersions>
      <snapshotVersion>
        <extension>pom</extension>
        <value>1.0.0-DATE.TIME-1</value>
        <updated>DATETIME</updated>
      </snapshotVersion>
      <snapshotVersion>
        <extension>jar</extension>
        <value>1.0.0-DATE.TIME-1</value>
        <updated>DATETIME</updated>
      </snapshotVersion>
      <snapshotVersion>
        <classifier>sources</classifier>
        <extension>jar</extension>
        <value>1.0.0-DATE.TIME-1</value>
        <updated>DATETIME</updated>
      </snapshotVersion>
      <snapshotVersion>
        <classifier>javadoc</classifier>
        <extension>jar</extension>
        <value>1.0.0-DATE.TIME-1</value>
        <updated>DATETIME</updated>
      </snapshotVersion>
    </snapshotVersions>

install copies the same set, with the literal version, to the local repository (the four Installing lines):

[INFO] Installing <proj>/tidepool-model/pom.xml to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.pom
[INFO] Installing <proj>/tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT.jar to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.jar
[INFO] Installing <proj>/tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT-sources.jar to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT-sources.jar
[INFO] Installing <proj>/tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT-javadoc.jar to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT-javadoc.jar

The classifier is the way to publish several artifacts from one project: a test JAR (jar:test-jar, type test-jar, classifier tests; see Packaging JARs, WARs & Manifests) or an assembly (see Uber JARs with Shade & Assembly). A consumer asks for one with <classifier> in its dependency. The Javadoc Plugin runs the JDK’s javadoc tool, so its usage is also affected by the JDK (see Compiling & Toolchains).

deploy-file: uploading a file that is not a project

deploy:deploy-file uploads files that no project of the current build produced: a vendor JAR for the company repository, or the output of a non-Maven build. It needs no POM at all. With a JAR compiled with javac and packed with jar (so with no pom.xml inside), and the sources and Javadoc JARs of the earlier run, from a directory without a POM (grep -E "deploy-file|Uploaded|BUILD"; <df> is that directory):

mvn -B org.apache.maven.plugins:maven-deploy-plugin:3.2.0:deploy-file -Dfile=tidepool-extra.jar \
    -DgroupId=io.tidepool -DartifactId=tidepool-extra -Dversion=1.0.0 -Dpackaging=jar \
    -Durl=file://<df>/repo -DrepositoryId=tidepool-releases \
    -Dsources=tidepool-extra-sources.jar -Djavadoc=tidepool-extra-javadoc.jar
[INFO] --- deploy:3.2.0:deploy-file
[INFO] Uploaded to tidepool-releases: file://<df>/repo/io/tidepool/tidepool-extra/1.0.0/tidepool-extra-1.0.0.jar
[INFO] Uploaded to tidepool-releases: file://<df>/repo/io/tidepool/tidepool-extra/1.0.0/tidepool-extra-1.0.0.pom
[INFO] Uploaded to tidepool-releases: file://<df>/repo/io/tidepool/tidepool-extra/1.0.0/tidepool-extra-1.0.0-sources.jar
[INFO] Uploaded to tidepool-releases: file://<df>/repo/io/tidepool/tidepool-extra/1.0.0/tidepool-extra-1.0.0-javadoc.jar
[INFO] Uploaded to tidepool-releases: file://<df>/repo/io/tidepool/tidepool-extra/maven-metadata.xml
[INFO] BUILD SUCCESS

The tree was the JAR, the POM, the two classified JARs and one maven-metadata.xml:

./io/tidepool/tidepool-extra/1.0.0/tidepool-extra-1.0.0-javadoc.jar
./io/tidepool/tidepool-extra/1.0.0/tidepool-extra-1.0.0-sources.jar
./io/tidepool/tidepool-extra/1.0.0/tidepool-extra-1.0.0.jar
./io/tidepool/tidepool-extra/1.0.0/tidepool-extra-1.0.0.pom
./io/tidepool/tidepool-extra/maven-metadata.xml

and the generated POM and the release’s metadata were:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>io.tidepool</groupId>
  <artifactId>tidepool-extra</artifactId>
  <version>1.0.0</version>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
  <groupId>io.tidepool</groupId>
  <artifactId>tidepool-extra</artifactId>
  <versioning>
    <release>1.0.0</release>
    <versions>
      <version>1.0.0</version>
    </versions>
    <lastUpdated>DATETIME</lastUpdated>
  </versioning>
</metadata>

For a release, the metadata of the artifact has a <release> entry, which a snapshot’s metadata does not.

The embedded POM that wins

A JAR built by Maven contains its own POM in META-INF/maven/<groupId>/<artifactId>/pom.xml. deploy-file takes the POM from there when no pomFile is given, even if the coordinates on the command line are different. Uploading the JAR of tidepool-model as tidepool-extra (the JAR holds):

META-INF/maven/io.tidepool/tidepool-model/pom.xml
META-INF/maven/io.tidepool/tidepool-model/pom.properties

succeeded, and the POM it wrote for tidepool-extra was the one of tidepool-model, with its parent and its dependency (the grep -E "BUILD|artifactId|<version>" of the run):

[INFO] BUILD SUCCESS
    <artifactId>tidepool-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
  <artifactId>tidepool-model</artifactId>
      <artifactId>junit-jupiter</artifactId>

-DgeneratePom=true did not change it. The artifact was published with the wrong coordinates inside its own POM, and consumers would resolve the dependencies of another artifact. Pass an explicit POM; the file is the minimum one:

extra-pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>io.tidepool</groupId>
  <artifactId>tidepool-extra3</artifactId>
  <version>1.0.0</version>
</project>

With -DpomFile=extra-pom.xml and the coordinates taken from it:

[INFO] Uploaded to tidepool-releases: file://<df>/repo2/io/tidepool/tidepool-extra3/1.0.0/tidepool-extra3-1.0.0.jar
[INFO] Uploaded to tidepool-releases: file://<df>/repo2/io/tidepool/tidepool-extra3/1.0.0/tidepool-extra3-1.0.0.pom
[INFO] BUILD SUCCESS

Deploying a site

The site child of distributionManagement is the target of site:deploy, which uploads target/site using the same transports and the same server lookup. Adding

    <site>
      <id>tidepool-site</id>
      <url>${repo.url}/site/${project.artifactId}</url>
    </site>

to the parent and running mvn -B -pl tidepool-model site site:deploy (the site content and the reports are on Site & Reporting; grep -E "site:|BUILD") printed:

[INFO] --- site:3.22.0:site (default-site) @ tidepool-model ---
[INFO] --- site:3.22.0:deploy (default-cli) @ tidepool-model ---
[INFO] BUILD SUCCESS

The pages were written below the site URL. Note the path: site/tidepool-model/tidepool-model, with the artifact id twice. The url of a site is inherited by the children with the child’s artifact id appended, which is the intention for a multi-module site (one directory per module) and a surprise when the parent URL already contains ${project.artifactId}. help:evaluate for the child:

file://<proj>/build/repo/site/tidepool-model/tidepool-model
file://<proj>/build/repo/releases

and the directory listing of build/repo/site (first lines of find . -maxdepth 3 | sort) was:

.
./tidepool-model
./tidepool-model/tidepool-model
./tidepool-model/tidepool-model/css
./tidepool-model/tidepool-model/dependencies.html
./tidepool-model/tidepool-model/dependency-info.html
./tidepool-model/tidepool-model/dependency-management.html
./tidepool-model/tidepool-model/distribution-management.html

The second line of the first listing is the repository URL of the same child, for comparison: unlike the site URL it was not extended with the artifact id. The files of the first listing’s directory follow. A site published to GitHub Pages is usually pushed by a CI workflow from target/site rather than with site:deploy; see the site page.

Maven 3.9, 3.10 and 4: what differs

Topic Maven 3.9.16 and 3.10.0 Maven 4.0.0-rc-7 (release candidate)

What is installed or deployed for a module

The POM as written (<name>-<version>.pom) and the artifacts

Two POMs: the consumer POM as <name>-<version>.pom and the build POM (the POM as written) as <name>-<version>-build.pom

Metadata

One snapshotVersion per extension and classifier

An extra snapshotVersion with <classifier>build</classifier> for the build POM

Credentials

3.10 warns and scopes; 3.9.16 does neither

A short warning; repositoryOrigins was not honoured in strict mode

Install and Deploy Plugin 4.0.0-beta-3

Fail: unmet prerequisites

Work; the plugins of the 4.0 line

On Maven 4, mvn -B -pl tidepool-model deploy (grep -E "install:|deploy:|Installing|Uploaded to.*(pom|jar) |BUILD") printed:

[INFO] --- install:3.2.0:install
[INFO] Installing <proj>/tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT.jar to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.jar
[INFO] Installing <proj>/tidepool-model/pom.xml to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT-build.pom
[INFO] Installing <proj>/tidepool-model/target/consumer-11803575979158663052.pom to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.pom
[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.pom
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1-build.pom
[INFO] BUILD SUCCESS

and the repository listing (without checksums) and the metadata were:

maven-metadata.xml
tidepool-model-1.0.0-TS-1-build.pom
tidepool-model-1.0.0-TS-1.jar
tidepool-model-1.0.0-TS-1.pom
    <snapshotVersions>
      <snapshotVersion>
        <classifier></classifier>
        <extension>jar</extension>
        <value>1.0.0-DATE.TIME-1</value>
        <updated>DATETIME</updated>
      </snapshotVersion>
      <snapshotVersion>
        <classifier>build</classifier>
        <extension>pom</extension>
        <value>1.0.0-DATE.TIME-1</value>
        <updated>DATETIME</updated>
      </snapshotVersion>
      <snapshotVersion>
        <classifier></classifier>
        <extension>pom</extension>
        <value>1.0.0-DATE.TIME-1</value>
        <updated>DATETIME</updated>
      </snapshotVersion>
    </snapshotVersions>

The consumer POM is generated into target/consumer-<number>.pom and published as the POM of the artifact; the build POM keeps everything that only a build needs, such as <modules> of an aggregator. Its content, and the new model version 4.1.0, are on Maven 4: POM Model & Consumer POM. With the plugin versions of the 4.0 line pinned, the same build on Maven 4 printed:

[INFO] --- install:4.0.0-beta-3:install
[INFO] Installing <proj>/tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT.jar to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.jar
[INFO] Installing <proj>/tidepool-model/pom.xml to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT-build.pom
[INFO] Installing <proj>/tidepool-model/target/consumer-NNN.pom to <lab>/m2/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-SNAPSHOT.pom
[INFO] --- deploy:4.0.0-beta-3:deploy
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.jar
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1-build.pom
[INFO] Uploaded to tidepool-snapshots: file://<proj>/build/repo/snapshots/io/tidepool/tidepool-model/1.0.0-SNAPSHOT/tidepool-model-1.0.0-TS-1.pom
[INFO] BUILD SUCCESS

The 4.0.0-beta-3 plugins need Maven 4. On Maven 3.10.0 and 3.9.16 the build fails when it reaches them, with the message of the prerequisite check:

[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-install-plugin:4.0.0-beta-3:install (default-install) on project tidepool-model:
[ERROR] The plugin org.apache.maven.plugins:maven-install-plugin:4.0.0-beta-3 has unmet prerequisites:
[ERROR] 	Required Maven version 4.0.0-rc-6 is not met by current version 3.10.0
[ERROR] For more information about the errors and possible solutions, please read the following articles:

(Maven 3.9.16 printed the same text with "3.9.16" in the last line.) Do not put the 4.0 line of a plugin in a POM that is also built with Maven 3. The migration is on Migrating to Maven 4, and the release candidate status means anything above may change before 4.0.0.

The same job in Ant and Gradle

Tool Local install Remote publishing

Maven

install phase; install:install-file

deploy phase; distributionManagement; deploy:deploy-file

Ant

the Resolver Ant Tasks install (see Maven Resolver Ant Tasks); Ivy publish to a local resolver

the Resolver Ant Tasks deploy with a remoteRepo; Ivy publish with a resolver (see Ivy: Publishing & Multi-Project Builds)

Gradle

the publishToMavenLocal task of the maven-publish plugin

publishing { repositories { ... } } and publish (see Publishing)

The three tools write the same repository layout, which is why a project deployed by one can be consumed by another; the comparison, with the same tidepool modules published to build/repo by all of them, is on Repositories & Publishing Across Tools.

Classic problems

  • HTTP Status: 401 on deploy. No <server> with the right <id>, a wrong password, or a password encrypted with a master password the build cannot read. Check the id of distributionManagement against settings.xml, and the log of the server.

  • HTTP Status: 400 or 409 on the second deploy of a release. The repository does not allow redeploying a release. Release a new version.

  • repository element was not specified in the POM. No distributionManagement and no altDeploymentRepository.

  • The snapshots end up in the releases repository, or are rejected: no snapshotRepository.

  • deploy-file uploads a POM with other coordinates. The JAR has an embedded pom.xml; pass -DpomFile.

  • Some modules were deployed and the build failed. The default deploys per module; use deployAtEnd, and know it is not atomic.

  • Unrecognized value ... for maven.deploy.skip. The value must be true, false, releases or snapshots.

  • A deploy from a CI job overwrites a developer’s snapshot. Snapshots are shared by design; use CI-friendly versions that put the commit in the version (see CI-Friendly Versions).

  • A POM with ${revision} is published unresolved on Maven 3. See the same page.

  • A plugin 4.0 line on Maven 3. has unmet prerequisites, above.

Where the books are outdated

  • Older books describe deploy:deploy with the layout in altDeploymentRepository (id::default::url), the layout and uniqueVersion children of distributionManagement, and SCP or WebDAV as the normal transports. Maven 3 has one layout, ignores uniqueVersion, uses the native HTTP transport and needs a Wagon extension for SCP and FTP.

  • Plain http: repository URLs for downloads are blocked by default since Maven 3.8.1 (the settings and repositories pages); uploads to localhost over HTTP, as here, are for tests.

  • Publishing to Central through OSSRH (oss.sonatype.org) ended on 2025-06-30; see Publishing to Maven Central.

  • The release-profile of the super POM, which added sources and Javadoc JARs to a release, is removed in Maven 3.10 (see Releasing); attach them with the executions shown above.

What was not run here

  • Any real repository manager, Central, or an authenticated remote server other than the localhost lab server; the scp, sftp and ftp transports with Wagon extensions; privateKey authentication.

  • retryFailedDeploymentCount, allowIncompleteProjects, installAtEnd combined with failing builds, and the status and downloadUrl elements of distributionManagement.

  • The test JAR (test-jar) and assembly attachments (shown on the pages named above).