Deepen PracticeOrdered learning track

Deploy, Publish, and Distribution Management

Learn Maven In Action - Part 031

Deploy, publish, and distribution management for production-grade Maven builds: install vs deploy, distributionManagement, credentials, staging, snapshot/release routing, multi-module deploy, repository promotion, and incident playbooks.

20 min read3980 words
PrevNext
Lesson 3140 lesson track2333 Deepen Practice
#maven#java#build-engineering#artifact-management+2 more

Part 031 — Deploy, Publish, and Distribution Management

Maven deployment is not “uploading a JAR”.

In a serious engineering organization, Maven deployment is the point where a local build becomes a shared artifact that other projects, teams, runtime images, and release pipelines may consume.

That means deployment is a boundary with consequences:

  • dependency consumers may resolve your artifact;
  • CI jobs may cache it;
  • repository managers may index it;
  • runtime images may embed it;
  • scanners may generate vulnerability reports from it;
  • auditors may ask where it came from;
  • rollback procedures may depend on its immutability.

This part teaches Maven deploy as an artifact publication protocol.

We will separate:

  • package — produce artifact locally inside target/;
  • install — put artifact into the local repository;
  • deploy — publish artifact to a remote repository;
  • repository manager staging — hold and validate candidate releases;
  • promotion — move validated artifacts to a trusted release repository.

By the end, you should be able to design a Maven publication flow that is reproducible, auditable, CI-safe, and safe for multi-module enterprise builds.


1. The Core Mental Model

Maven deployment is a controlled transition:

source code -> local build output -> local repository -> remote repository -> consumed dependency

Each arrow changes the trust level.

BoundaryMaven command/conceptWhat changes
Source to build outputmvn packageProject source becomes files under target/.
Build output to local artifactmvn installArtifact becomes resolvable by other local builds.
Local artifact to shared artifactmvn deployArtifact becomes resolvable by other machines.
Shared artifact to release artifactrepository staging/promotionArtifact becomes an approved organization dependency.

The mistake is treating all four as equivalent.

They are not.

package proves Maven can assemble the artifact.

install proves the artifact can be resolved locally using Maven repository layout.

deploy publishes the artifact to a remote repository for other developers and projects.

A release/promotion process proves the artifact is acceptable for wider consumption.


2. Maven Lifecycle Placement

Deployment belongs to Maven's deploy phase.

A simplified lifecycle path:

validate
  -> compile
    -> test
      -> package
        -> verify
          -> install
            -> deploy

deploy happens after install.

That is intentional. Maven should not publish an artifact before it has passed the local build, test, package, and verification flow.

In production, you rarely want developers to run mvn deploy casually from laptops.

You usually want:

Developer laptop:
  mvn verify

CI validation branch:
  mvn verify

CI release/publish job:
  mvn deploy

Why?

Because deploy changes shared state.

A local mvn verify is mostly private. A remote mvn deploy affects the organization.


3. install vs deploy

mvn install

install copies the built artifact and its POM into the local Maven repository.

Usually:

~/.m2/repository/<groupId path>/<artifactId>/<version>/

Example:

~/.m2/repository/com/acme/payment/payment-core/1.4.0/payment-core-1.4.0.jar
~/.m2/repository/com/acme/payment/payment-core/1.4.0/payment-core-1.4.0.pom

Use install when:

  • developing multiple projects locally;
  • testing a library consumer before publishing;
  • debugging dependency resolution locally;
  • working on a library and app split across repositories.

Do not use install as release.

install is private machine state.

It is not auditable across the organization.

mvn deploy

deploy uploads the artifact and POM to a configured remote repository.

Use deploy when:

  • publishing snapshots to an internal snapshot repository;
  • publishing release candidates to a staging repository;
  • publishing internal libraries for other teams;
  • publishing open-source artifacts to a public repository workflow;
  • promoting validated artifacts into a release repository.

The Maven Deploy Plugin is primarily used in the deploy phase to add artifacts to a remote repository for sharing with other developers and projects.


4. What Gets Deployed?

A Maven project usually deploys more than a JAR.

A deploy may include:

  • main artifact: .jar, .war, .ear, .pom, etc.;
  • project POM;
  • sources JAR;
  • javadoc JAR;
  • test JAR if attached;
  • shaded artifact if configured;
  • checksums generated by repository tooling;
  • signatures if signing is enabled;
  • metadata updates.

Example release artifact set:

payment-api-1.4.0.jar
payment-api-1.4.0.pom
payment-api-1.4.0-sources.jar
payment-api-1.4.0-javadoc.jar
payment-api-1.4.0.jar.asc
payment-api-1.4.0.pom.asc
payment-api-1.4.0-sources.jar.asc
payment-api-1.4.0-javadoc.jar.asc

For internal libraries, sources and javadocs are still useful.

They improve IDE navigation, debugging, and consumer understanding.

A top-tier Maven pipeline treats metadata as part of the artifact contract, not decoration.


5. distributionManagement

The normal Maven way to tell mvn deploy where to publish is distributionManagement.

Minimal example:

<project>
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.acme.payment</groupId>
  <artifactId>payment-api</artifactId>
  <version>1.4.0</version>

  <distributionManagement>
    <repository>
      <id>acme-releases</id>
      <url>https://repo.acme.internal/repository/maven-releases</url>
    </repository>
    <snapshotRepository>
      <id>acme-snapshots</id>
      <url>https://repo.acme.internal/repository/maven-snapshots</url>
    </snapshotRepository>
  </distributionManagement>
</project>

The important pieces:

ElementMeaning
repositoryTarget repository for non-SNAPSHOT releases.
snapshotRepositoryTarget repository for *-SNAPSHOT versions.
idLogical server identifier. Must match credentials in settings.xml.
urlRemote repository URL.

This split matters.

A release repository should be stable and immutable.

A snapshot repository is mutable by design.

If your organization publishes snapshots and releases into the same repository, you lose an important safety boundary.


6. The id Contract: POM Meets settings.xml

This is one of the most common Maven deploy failure points.

The POM contains repository identity:

<distributionManagement>
  <repository>
    <id>acme-releases</id>
    <url>https://repo.acme.internal/repository/maven-releases</url>
  </repository>
</distributionManagement>

The user's or CI's settings.xml contains credentials for that same id:

<settings>
  <servers>
    <server>
      <id>acme-releases</id>
      <username>${env.MAVEN_REPO_USERNAME}</username>
      <password>${env.MAVEN_REPO_PASSWORD}</password>
    </server>
  </servers>
</settings>

The id is the join key.

If id differs by one character, Maven will not use the credentials you expect.

This is why enterprise teams standardize repository IDs.

Good:

company-releases
company-snapshots
company-staging
company-thirdparty

Bad:

repo
nexus
nexus-release
release-repo
internal-release
maven-release
companyReleaseRepo

When every project invents its own IDs, CI credential management becomes fragile.


7. Where Should distributionManagement Live?

There are three common patterns.

Pattern A — In every project POM

<distributionManagement>
  ...
</distributionManagement>

Works, but scales poorly.

Problems:

  • duplicated repository URLs;
  • inconsistent repository IDs;
  • hard migration when repository manager URL changes;
  • risk of publishing to wrong repository.

Use only for small standalone projects.

Pattern B — In parent POM

<parent>
  <groupId>com.acme.build</groupId>
  <artifactId>acme-parent</artifactId>
  <version>2026.7.0</version>
</parent>

Parent contains:

<distributionManagement>
  <repository>
    <id>acme-releases</id>
    <url>https://repo.acme.internal/repository/maven-releases</url>
  </repository>
  <snapshotRepository>
    <id>acme-snapshots</id>
    <url>https://repo.acme.internal/repository/maven-snapshots</url>
  </snapshotRepository>
</distributionManagement>

This is the common enterprise pattern.

Pros:

  • centralized publishing policy;
  • consistent repository IDs;
  • easier migration;
  • fewer copy-paste errors.

Cons:

  • parent POM change affects many projects;
  • not suitable if different artifact families need different publishing flows;
  • open-source publication may need a separate parent.

Pattern C — Use alternate deployment repository in CI

Maven Deploy Plugin supports alternate deployment repository parameters such as altDeploymentRepository.

Example:

mvn deploy \
  -DaltDeploymentRepository=acme-staging::https://repo.acme.internal/repository/maven-staging

This is useful when:

  • CI decides target repository dynamically;
  • repository target depends on branch/tag;
  • you need emergency rerouting;
  • you want POMs not to contain deployment URLs;
  • large-scale centralized deployments are controlled through CI/settings.

But do not use this casually.

If deployment target is invisible in the POM, you must make it very visible in CI logs and release documentation.


8. Snapshot vs Release Deployment

Maven version suffix decides which repository target is used.

1.4.0-SNAPSHOT -> snapshotRepository
1.4.0          -> repository

Snapshots are mutable.

Release versions must be immutable.

A snapshot artifact in the remote repository usually does not stay as plain 1.4.0-SNAPSHOT.jar. Repository metadata may resolve it to timestamped snapshot builds.

Conceptually:

payment-api-1.4.0-SNAPSHOT.jar

may map to deployed timestamped artifacts like:

payment-api-1.4.0-20260703.081512-3.jar

That lets Maven distinguish different deployed builds under one logical snapshot version.

But the consumer still declares:

<dependency>
  <groupId>com.acme.payment</groupId>
  <artifactId>payment-api</artifactId>
  <version>1.4.0-SNAPSHOT</version>
</dependency>

This is powerful and dangerous.

A downstream build may resolve a different snapshot today than yesterday.

Use snapshots for integration velocity, not release certainty.


9. Snapshot Policy

A senior Maven setup defines snapshot policy explicitly.

Questions:

  • Which branches can deploy snapshots?
  • Are snapshots retained forever?
  • Can snapshots enter production images?
  • Can downstream release builds consume snapshots?
  • Are snapshots scanned?
  • Are snapshots replicated to disaster recovery repository?
  • Are snapshots allowed across team boundaries?

Suggested policy:

ConsumerSnapshot allowed?Reason
Developer local buildYesFast integration.
Feature branch CIYesIntegration testing.
Main branch CIMaybeDepends on organization discipline.
Release candidate buildNoRelease must be deterministic.
Production imageNoMutable dependency not acceptable.
Regulated/audited deploymentNoArtifact must be traceable and immutable.

Enforce no-snapshot release builds with Maven Enforcer.

Example:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <executions>
    <execution>
      <id>release-no-snapshots</id>
      <phase>validate</phase>
      <goals>
        <goal>enforce</goal>
      </goals>
      <configuration>
        <rules>
          <requireReleaseDeps>
            <message>Release builds must not depend on SNAPSHOT artifacts.</message>
          </requireReleaseDeps>
        </rules>
      </configuration>
    </execution>
  </executions>
</plugin>

The exact rule set may vary, but the invariant is stable:

A release build must not depend on mutable artifacts.


10. Repository Layout and Metadata

Maven remote repositories use a structured layout.

For coordinates:

groupId:    com.acme.payment
artifactId: payment-api
version:    1.4.0
packaging:  jar

Path:

com/acme/payment/payment-api/1.4.0/payment-api-1.4.0.jar
com/acme/payment/payment-api/1.4.0/payment-api-1.4.0.pom

Repository metadata also exists:

com/acme/payment/payment-api/maven-metadata.xml
com/acme/payment/payment-api/1.4.0-SNAPSHOT/maven-metadata.xml

Metadata supports version discovery, snapshot resolution, and repository indexing.

Operationally, corrupted metadata can break resolution even when the artifact file exists.

Deployment failures are not always JAR upload failures.

Sometimes the artifact upload succeeds but metadata update fails.

That is why repository manager health and transactional behavior matter.


11. The Deploy Plugin

Maven uses the Maven Deploy Plugin for deploy operations.

The main goals are:

GoalUse
deploy:deployDeploy artifacts produced by the current Maven project.
deploy:deploy-fileDeploy an external artifact not built by the current Maven project.

Normally you do not call deploy:deploy directly.

You run:

mvn deploy

Maven reaches the deploy lifecycle phase and invokes the bound deploy plugin goal.

Pin the plugin version

Production parent POM:

<build>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-deploy-plugin</artifactId>
        <version>${maven-deploy-plugin.version}</version>
      </plugin>
    </plugins>
  </pluginManagement>
</build>

Properties:

<properties>
  <maven-deploy-plugin.version>3.1.4</maven-deploy-plugin.version>
</properties>

Do not rely on implicit plugin versions in enterprise builds.

The deploy phase is too important to let Maven choose hidden defaults.


12. deployAtEnd for Multi-Module Builds

Multi-module deploy has a painful failure mode.

Suppose a reactor has 20 modules.

If Maven deploys each module immediately after its build and module 19 fails, modules 1–18 may already be deployed.

Now the remote repository contains a partial release.

That is dangerous.

deployAtEnd delays deployment until the end of a successful reactor build.

Example:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-deploy-plugin</artifactId>
  <configuration>
    <deployAtEnd>true</deployAtEnd>
  </configuration>
</plugin>

Mental model:

Use deployAtEnd for release-like multi-module deployments.

It reduces partial publication risk.

It does not replace repository staging.

If the network fails during final deploy, you can still get partial upload. A repository manager staging workflow is stronger.


13. Retry Behavior and Network Failure

Publishing is network I/O.

Network I/O fails.

The Deploy Plugin has retry-related configuration such as retryFailedDeploymentCount.

Example:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-deploy-plugin</artifactId>
  <configuration>
    <retryFailedDeploymentCount>3</retryFailedDeploymentCount>
  </configuration>
</plugin>

This can help with transient network issues.

But retries are not a release strategy.

You still need:

  • repository manager availability;
  • staging repository rollback/drop;
  • idempotent CI behavior;
  • immutable release repository;
  • clear human procedure for partial uploads.

For snapshots, retry is usually acceptable.

For releases, prefer staging and promotion rather than direct deploy into final release repository.


14. Deploying External Artifacts with deploy:deploy-file

Sometimes you need to add a third-party artifact to an internal repository.

Example cases:

  • vendor JAR not available in Maven Central;
  • internal binary from a legacy build;
  • generated artifact from a non-Maven build;
  • artifact built by another tool but consumed by Maven projects.

Example:

mvn deploy:deploy-file \
  -DgroupId=com.vendor.legacy \
  -DartifactId=legacy-driver \
  -Dversion=2.8.1 \
  -Dpackaging=jar \
  -Dfile=legacy-driver-2.8.1.jar \
  -DrepositoryId=acme-thirdparty \
  -Durl=https://repo.acme.internal/repository/thirdparty-releases

Use this carefully.

The moment you deploy an external artifact, your organization becomes responsible for its metadata quality.

Minimum metadata checklist:

  • correct groupId;
  • correct artifactId;
  • correct version;
  • license recorded somewhere;
  • source/vendor provenance known;
  • checksum recorded;
  • vulnerability scanning possible;
  • no accidental repackaging under misleading coordinates.

Bad pattern:

com.acme:some-jar:1.0

Good pattern:

com.vendor.product:vendor-client:2.8.1-acme1

If your organization modifies the binary, use a suffix or internal namespace to make that clear.


15. Repository Manager Publication Topology

A repository manager usually has multiple repository roles.

Common repository types:

RepositoryTypePurpose
maven-snapshotshostedMutable internal development artifacts.
maven-staginghosted/stagingRelease candidates waiting for validation.
maven-releaseshostedImmutable approved internal releases.
maven-central-proxyproxyCached external dependencies.
maven-thirdpartyhostedApproved vendor/manual artifacts.
maven-publicgroup/virtualSingle URL for consumers.

Do not let every build talk directly to Maven Central.

Use a repository manager as supply-chain boundary.


16. Direct Deploy vs Staged Release

Direct deploy

mvn deploy -> maven-releases

Simple.

Risky.

If the release repository is immutable, a bad release cannot be overwritten. That is good for audit, but bad if direct deploy published a broken artifact.

Staged release

mvn deploy -> staging repository -> validation -> promotion -> releases

Better.

It supports:

  • artifact inspection;
  • checksum/signature verification;
  • SBOM attachment;
  • vulnerability scanning;
  • license checks;
  • consumer smoke tests;
  • manual approval if needed;
  • atomic-ish promotion semantics.

Recommended enterprise flow:

The critical invariant:

A failed release candidate should be droppable without contaminating the final release repository.


17. Deployment Credentials

Do not put credentials in pom.xml.

Use settings.xml, CI secrets, or repository manager token mechanisms.

Bad:

<distributionManagement>
  <repository>
    <id>acme-releases</id>
    <url>https://user:password@repo.acme.internal/repository/maven-releases</url>
  </repository>
</distributionManagement>

Good:

<distributionManagement>
  <repository>
    <id>acme-releases</id>
    <url>https://repo.acme.internal/repository/maven-releases</url>
  </repository>
</distributionManagement>

CI settings.xml generated at runtime:

<settings>
  <servers>
    <server>
      <id>acme-releases</id>
      <username>${env.MAVEN_REPO_USERNAME}</username>
      <password>${env.MAVEN_REPO_TOKEN}</password>
    </server>
  </servers>
</settings>

Also consider:

  • use short-lived tokens where possible;
  • separate snapshot deploy credentials from release deploy credentials;
  • prevent developer accounts from deploying releases directly;
  • use service accounts with least privilege;
  • rotate tokens;
  • prevent secrets from appearing in logs;
  • scan repository for leaked credentials.

18. CI-Safe settings.xml

Example generated CI settings file:

<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
  <mirrors>
    <mirror>
      <id>acme-public</id>
      <mirrorOf>*</mirrorOf>
      <url>https://repo.acme.internal/repository/maven-public</url>
    </mirror>
  </mirrors>

  <servers>
    <server>
      <id>acme-snapshots</id>
      <username>${env.MAVEN_REPO_USERNAME}</username>
      <password>${env.MAVEN_REPO_TOKEN}</password>
    </server>
    <server>
      <id>acme-releases</id>
      <username>${env.MAVEN_REPO_USERNAME}</username>
      <password>${env.MAVEN_REPO_TOKEN}</password>
    </server>
    <server>
      <id>acme-staging</id>
      <username>${env.MAVEN_REPO_USERNAME}</username>
      <password>${env.MAVEN_REPO_TOKEN}</password>
    </server>
  </servers>
</settings>

CI command:

mvn --batch-mode \
  --settings .ci/maven-settings.xml \
  --no-transfer-progress \
  clean deploy

Use --batch-mode in CI.

Use --no-transfer-progress to reduce noisy logs.

Keep settings generation explicit and visible.


19. The install-file vs deploy-file Difference

For external artifacts:

CommandTargetScope
install:install-filelocal repositoryOnly this machine/runner.
deploy:deploy-fileremote repositoryShared by consumers.

Example local test:

mvn install:install-file \
  -DgroupId=com.vendor.legacy \
  -DartifactId=legacy-driver \
  -Dversion=2.8.1 \
  -Dpackaging=jar \
  -Dfile=legacy-driver-2.8.1.jar

Then deploy only after metadata review:

mvn deploy:deploy-file \
  -DgroupId=com.vendor.legacy \
  -DartifactId=legacy-driver \
  -Dversion=2.8.1 \
  -Dpackaging=jar \
  -Dfile=legacy-driver-2.8.1.jar \
  -DrepositoryId=acme-thirdparty \
  -Durl=https://repo.acme.internal/repository/thirdparty-releases

Do not skip local install testing.

If Maven cannot consume the artifact locally, publishing it remotely only spreads the problem.


20. POM Quality for Published Artifacts

A deployed artifact's POM is consumed by downstream builds.

Bad POM metadata can break consumers.

Important fields:

<groupId>com.acme.payment</groupId>
<artifactId>payment-api</artifactId>
<version>1.4.0</version>
<packaging>jar</packaging>
<name>Acme Payment API</name>
<description>Stable API contracts for the Acme payment domain.</description>
<url>https://git.acme.internal/payment/payment-api</url>

For open-source/public artifacts, also include:

  • license;
  • developers;
  • SCM;
  • issue management;
  • organization;
  • distribution management required by publishing flow.

For internal enterprise artifacts, still include at least:

  • clear name;
  • description;
  • SCM link;
  • owner/team metadata where supported;
  • accurate dependencies;
  • no accidental test dependencies in compile scope;
  • no private repositories leaking into consumer POM unless intended.

Consumer POM hygiene matters.

When you publish a library, your POM becomes part of other teams' build graph.


21. Avoid Repositories in Published Library POMs

A library POM that declares random repositories can affect consumers.

Example smell:

<repositories>
  <repository>
    <id>temporary-vendor-repo</id>
    <url>https://vendor.example.com/temp/maven</url>
  </repository>
</repositories>

Why risky?

  • consumers inherit resolution behavior;
  • builds may reach unapproved external endpoints;
  • supply-chain boundary is weakened;
  • repository outage can break consumers;
  • internal repository policy becomes bypassable.

Enterprise rule:

Library POMs should generally not declare arbitrary external repositories.

Resolve all external dependencies through the organization's repository manager.

Use mirrors in settings.xml to route traffic.


22. Publishing Parent POMs and BOMs

Parent POMs and BOMs are artifacts too.

They usually have packaging pom.

Parent POM:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.acme.build</groupId>
  <artifactId>acme-service-parent</artifactId>
  <version>2026.7.0</version>
  <packaging>pom</packaging>
</project>

BOM:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.acme.platform</groupId>
  <artifactId>acme-platform-bom</artifactId>
  <version>2026.7.0</version>
  <packaging>pom</packaging>

  <dependencyManagement>
    <dependencies>
      ...
    </dependencies>
  </dependencyManagement>
</project>

Publishing order matters.

If a service release depends on parent 2026.7.0 and BOM 2026.7.0, those must be available before the service can build cleanly in a fresh environment.

Recommended platform release order:

1. build parent POM
2. publish parent POM to staging/releases
3. build platform BOM
4. publish platform BOM
5. build service/library projects using parent + BOM

If parent and BOM live in the same multi-module reactor, ordering is handled by the reactor only if dependencies/parent relationships are expressed correctly.


23. Publishing Multi-Module Projects

Example enterprise reactor:

payment-platform/
  pom.xml                     # aggregator
  payment-parent/             # packaging pom
  payment-bom/                # packaging pom
  payment-api/                # jar
  payment-domain/             # jar
  payment-app/                # jar or war

Publication strategy:

ModulePublish?Reason
aggregator rootUsually noOften just local build orchestration.
parent POMYesNeeded by child builds if used externally.
BOMYesNeeded for version alignment.
API moduleYesConsumed by other services.
domain moduleMaybePublish only if cross-project reuse is intended.
app moduleMaybePublish if deployment pipeline consumes artifact from Maven repository.

Do not publish every module by default.

Publish artifacts that represent a stable reuse or deployment boundary.

For modules that should not deploy:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-deploy-plugin</artifactId>
  <configuration>
    <skip>true</skip>
  </configuration>
</plugin>

Use sparingly and document why.


24. Artifact Promotion vs Artifact Rebuild

A common release engineering mistake:

CI build #1: validate artifact
CI build #2: rebuild same version and deploy artifact

This breaks provenance.

The deployed artifact is not the exact artifact that was tested.

Better:

CI build #1:
  build artifact
  test artifact
  deploy to staging
  scan staging artifact
  promote same artifact

Or:

CI build #1:
  build artifact
  test artifact
  store CI artifact

CI release job:
  publish exact stored artifact

But Maven deploy normally publishes from project build outputs. If you rebuild during release, you need strong reproducible-build discipline.

Best invariant:

The artifact promoted to release is the artifact that passed the gates.

This is easier with repository staging than with ad-hoc rebuilds.


25. Deployment Command Patterns

Snapshot branch deployment

mvn --batch-mode --no-transfer-progress clean deploy

Conditions:

  • version ends in -SNAPSHOT;
  • target is snapshot repository;
  • branch is allowed to publish snapshots;
  • CI credentials have snapshot deploy permission only.

Release candidate staging

mvn --batch-mode --no-transfer-progress clean deploy \
  -DskipTests=false \
  -Dgpg.skip=false

Conditions:

  • version is release version, not snapshot;
  • target is staging repository;
  • CI has staging deploy permission;
  • release gates run after deploy.

Emergency no-deploy verification

mvn --batch-mode --no-transfer-progress clean verify

Use when investigating failures without changing remote repositories.

Deploy one module and required dependencies

mvn --batch-mode clean deploy -pl payment-api -am

Be careful.

Partial deployment from a multi-module project may publish artifacts without the full release context.

Use only for snapshot or explicitly scoped internal builds.


26. Permissions Model

A serious Maven repository has role-separated permissions.

RoleAllowed
DeveloperRead group repo, deploy snapshots maybe.
CI snapshot publisherDeploy snapshots.
CI staging publisherDeploy to staging.
Release manager/servicePromote staging to releases.
Repository adminManage repositories and cleanup policy.
Security/scannerRead artifacts and metadata.

Avoid one all-powerful Maven credential.

Bad:

MAVEN_USERNAME=admin
MAVEN_PASSWORD=...

Good:

snapshot-publisher token -> maven-snapshots deploy only
release-publisher token  -> maven-staging deploy only
promoter token           -> staging promote/drop only
reader token             -> read group repository only

This reduces blast radius.


27. Cleanup and Retention Policy

Snapshots grow quickly.

Define retention:

Artifact typeRetention suggestion
Feature branch snapshotsShort retention, e.g. days/weeks.
Main branch snapshotsLonger retention, e.g. weeks/months.
Release candidatesRetain if linked to release audit; otherwise cleanup failed candidates.
ReleasesImmutable and long-lived.
Third-party approved artifactsLong-lived with provenance metadata.

Do not rely on manual cleanup.

Repository managers should enforce retention policy.

For Maven, metadata cleanup matters too.

If snapshots are deleted but metadata points to missing files, consumers may fail oddly.


28. Deployment Failure Modes

401 Unauthorized

Likely causes:

  • server.id mismatch;
  • missing CI secret;
  • expired token;
  • wrong repository URL;
  • repository requires token instead of password;
  • mirror/server config confusion.

Diagnostic path:

mvn help:effective-settings
mvn -X deploy

Do not print secrets in logs.

Check whether Maven is using the expected settings file.

403 Forbidden

Likely causes:

  • credentials valid but insufficient permission;
  • trying to deploy release into snapshot repository;
  • trying to overwrite immutable release;
  • path or namespace not allowed;
  • repository manager policy blocks artifact.

409 Conflict

Likely causes:

  • release version already exists;
  • immutable repository rejects overwrite;
  • staging repository already closed;
  • concurrent release job used same version.

Correct response:

  • do not delete and overwrite release casually;
  • bump version or investigate why version was reused;
  • preserve audit trail.

Metadata failure

Symptoms:

  • artifact file exists but consumers cannot resolve latest;
  • snapshot resolution fails;
  • repository metadata inconsistent.

Actions:

  • check repository manager logs;
  • rebuild metadata if supported;
  • avoid manual filesystem edits;
  • rerun deploy only if idempotent and policy allows.

Partial multi-module deployment

Symptoms:

  • some modules exist in remote repository;
  • later modules missing;
  • consumers fail depending on graph path.

Mitigation:

  • use deployAtEnd;
  • use staging repository;
  • drop failed staging repository;
  • avoid direct release repository deploy.

29. Release Deployment Checklist

Before deploy:

  • version is non-SNAPSHOT;
  • no SNAPSHOT dependencies;
  • parent POM resolvable from clean environment;
  • BOM resolvable from clean environment;
  • plugin versions pinned;
  • toolchain controlled;
  • tests pass with mvn verify;
  • artifact content inspected;
  • generated sources deterministic;
  • secrets not in artifact;
  • distributionManagement target correct;
  • CI settings.xml uses correct server IDs;
  • release notes/changelog generated if required.

During deploy:

  • run with --batch-mode;
  • use CI, not laptop;
  • use release credential with least privilege;
  • publish to staging first if possible;
  • preserve logs;
  • capture artifact coordinates;
  • capture commit SHA and build number.

After deploy:

  • verify artifact is resolvable from clean local repo;
  • run consumer smoke test;
  • scan artifact/SBOM;
  • close/promote staging repository;
  • tag source if not already tagged;
  • announce coordinates;
  • monitor downstream failures.

30. Clean-Room Deployment Verification

After publishing, test as a consumer.

rm -rf /tmp/maven-clean-room
mkdir -p /tmp/maven-clean-room

mvn --batch-mode \
  -Dmaven.repo.local=/tmp/maven-clean-room \
  --settings .ci/maven-settings.xml \
  dependency:get \
  -Dartifact=com.acme.payment:payment-api:1.4.0

Then test a small consumer project:

<dependency>
  <groupId>com.acme.payment</groupId>
  <artifactId>payment-api</artifactId>
  <version>1.4.0</version>
</dependency>

Run:

mvn -Dmaven.repo.local=/tmp/maven-consumer-repo clean verify

This catches:

  • missing transitive dependency;
  • broken POM;
  • repository visibility issue;
  • missing parent POM;
  • wrong scope;
  • artifact deployed to wrong repository;
  • credential/mirror issue.

31. Enterprise Distribution Management Blueprint

A strong internal parent POM may include:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.acme.build</groupId>
  <artifactId>acme-service-parent</artifactId>
  <version>2026.7.0</version>
  <packaging>pom</packaging>

  <distributionManagement>
    <repository>
      <id>acme-releases</id>
      <name>Acme Maven Releases</name>
      <url>https://repo.acme.internal/repository/maven-releases</url>
    </repository>
    <snapshotRepository>
      <id>acme-snapshots</id>
      <name>Acme Maven Snapshots</name>
      <url>https://repo.acme.internal/repository/maven-snapshots</url>
    </snapshotRepository>
  </distributionManagement>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>${maven-deploy-plugin.version}</version>
          <configuration>
            <deployAtEnd>true</deployAtEnd>
            <retryFailedDeploymentCount>3</retryFailedDeploymentCount>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

This gives child projects a consistent publishing default.

For staging-based release, CI may override:

mvn deploy \
  -DaltDeploymentRepository=acme-staging::https://repo.acme.internal/repository/maven-staging

But that override must be controlled and visible.


32. Anti-Patterns

Anti-pattern: publishing from developer laptop

Problem:

  • non-reproducible local environment;
  • unmanaged credentials;
  • unrecorded provenance;
  • possible uncommitted changes;
  • inconsistent JDK/Maven version.

Fix:

  • publish only from CI;
  • use clean checkout;
  • record commit SHA;
  • use controlled toolchain;
  • use service credentials.

Anti-pattern: overwriting releases

Problem:

  • consumers cannot trust coordinates;
  • audit trail broken;
  • rollback ambiguous;
  • caches may contain different bytes for same version.

Fix:

  • immutable release repository;
  • version bump for every change;
  • repository manager blocks redeploy.

Anti-pattern: publishing everything

Problem:

  • internal implementation modules become accidental API;
  • downstream coupling increases;
  • refactoring becomes hard;
  • repository clutter grows.

Fix:

  • define publication boundary;
  • deploy only stable modules;
  • skip deploy for private modules.

Anti-pattern: POM contains credentials

Problem:

  • credential leak;
  • rotated secrets require source change;
  • forks inherit credentials;
  • logs may expose secrets.

Fix:

  • use settings.xml + CI secrets;
  • use repository tokens;
  • never include credentials in URLs.

Anti-pattern: release from snapshots

Problem:

  • release can change without version change;
  • build cannot be reproduced;
  • downstream incident root cause becomes unclear.

Fix:

  • enforce no snapshot dependencies for release;
  • use release BOM;
  • use immutable repository.

33. Senior-Level Diagnostic Commands

Show effective deploy config

mvn help:effective-pom

Search for:

distributionManagement
maven-deploy-plugin

Show effective settings

mvn help:effective-settings

Search for:

servers
mirrors
profiles

Be careful not to expose secrets.

Debug deploy

mvn -X --batch-mode deploy

Use only in secure logs because debug output may reveal sensitive context.

Verify remote resolution

mvn dependency:get -Dartifact=com.acme.payment:payment-api:1.4.0

Use clean local repository

mvn -Dmaven.repo.local=/tmp/m2-clean clean verify

This proves the build does not depend on polluted local cache.


34. Decision Matrix

SituationRecommended action
Need to test library locallymvn install, consume from local repo.
Need to share unstable integration artifactDeploy -SNAPSHOT to snapshot repo.
Need to release internal stable libraryDeploy to staging, scan, promote to releases.
Need to publish public OSS artifactFollow public repository requirements: POM metadata, signing, sources, javadocs.
Need to add vendor JARUse controlled deploy:deploy-file into third-party repository.
Need to avoid partial multi-module publishUse deployAtEnd plus staging.
Need branch-specific target repoUse CI-controlled altDeploymentRepository.
Need reproducible audit trailPublish from CI, capture commit SHA, artifact checksum, build logs.

35. Practical Implementation Exercise

Create a three-module project:

acme-payment/
  pom.xml
  payment-parent/
  payment-bom/
  payment-api/

Tasks:

  1. Configure parent POM with distributionManagement.
  2. Configure Deploy Plugin version in pluginManagement.
  3. Enable deployAtEnd.
  4. Generate CI settings.xml from environment variables.
  5. Run clean-room mvn verify.
  6. Run mvn deploy to a local test repository manager.
  7. Consume the deployed artifact from a separate sample project.
  8. Break server ID intentionally and observe 401.
  9. Try redeploying the same release version into immutable repo and observe conflict.
  10. Add -SNAPSHOT version and observe snapshot repository routing.

The exercise is not complete until you can explain every remote request Maven made.


36. Final Mental Model

Deployment is publication.

Publication creates shared dependency state.

Shared dependency state must be governed.

The production-grade Maven deployment model is:

The invariant:

Do not publish artifacts you cannot explain, reproduce, trace, and revoke from promotion.


References

Lesson Recap

You just completed lesson 31 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.