Maven: settings.xml & Password Encryption

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Apache Maven 3.10.0 (released on 27 September 2026), with Maven 3.9.16 and Maven 4.0.0-rc-7 (a release candidate) run wherever they differ. Every example marked as run was executed on JDK 26 on 2026-10-16, on macOS (Apple silicon), in a scratch directory that this page calls <lab>. Output that comes from a run is shown as printed, except that the scratch path is shortened to <lab>, that lines are removed with grep where the text says so, and that the byte counts of uploads are cut. Anything that could not be run here (SSH keys, a real corporate proxy, a hardware vault, GnuPG and 1Password as master key sources) is labelled "not run here". All passwords on this page are throwaway placeholders such as changeit-example; the encrypted values shown were produced from those placeholders and from placeholder master passwords.

settings.xml is where Maven keeps what must not travel with the project: who you are to a repository, which repository to ask instead of Central, how to get out through a proxy, where the local repository is. The first half of this page walks through every element and shows what Maven does with it. The second half is about the one element people most want to hide, the password, and about the two encryption systems: the Maven 3 master password, which Maven 3.10 still uses, and the redone system of Maven 4, the mvnenc tool. The POM is on POM Fundamentals; the settings of Maven itself (MAVEN_OPTS, .mvn) on Configuring Maven; the repositories that mirrors and servers refer to on Repositories, Mirrors & Repository Managers. The running scenario is described on the section landing page.

Left: the installation, user and project settings files merged into the effective settings. Right: a server password in settings.xml decrypted with a master secret kept elsewhere by Maven 3 or Maven 4

What belongs in settings.xml and what in the POM

The official page defines the file by what it leaves out: it holds values that configure Maven execution, "like the pom.xml, but should not be bundled to any specific project, or distributed to an audience". The practical test is whether two developers building the same commit should get the same answer.

Question Goes in the POM Goes in settings.xml

Which repositories does the project need?

<repositories> and <distributionManagement>, with an <id>

The credentials for that id (<server>), a mirror of it, a proxy to reach it

Which dependencies and plugins?

<dependencies>, <build>

Nothing; only <pluginGroups> for short plugin prefixes

Where is the local repository?

Never

<localRepository> (or -Dmaven.repo.local)

Who am I?

Never

<username>, <password>, <privateKey>

What changes between machines?

Profiles in the POM must be portable

Profiles in settings.xml, which can only set properties and repositories

The link between the two files is always an identifier: the <id> of a repository in the POM is the <id> of a <server> in the settings, and the <id> of a <mirror> is also looked up among the servers. Getting these ids to agree is most of the work, and most of the failures, below.

Where the files live and how they merge

Level Default location Option to override Release

Installation (global)

${maven.home}/conf/settings.xml

-gs (Maven 3.9 and 3.10); -is in Maven 4, where -gs is deprecated

All

User

${user.home}/.m2/settings.xml

-s

All

Project

.mvn/settings.xml, that is ${maven.project.conf}/settings.xml

-ps

Maven 4 only

If several exist, they are merged and the more specific one wins on conflicts: user over installation, and in Maven 4 project over user. The installation file is shipped with the distribution as a template full of comments; the official advice is to copy it to ~/.m2 and edit that copy rather than the installation, so that upgrading Maven does not lose the edits.

The best way to see the result is the Help plugin’s effective-settings goal. The next run merges two files with -gs and -s (Maven 3.10.0; -B is the batch mode, which is why interactiveMode shows as false):

The installation file
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <localRepository>/opt/ci/shared-repo</localRepository>
  <offline>true</offline>
  <pluginGroups>
    <pluginGroup>org.eclipse.jetty</pluginGroup>
  </pluginGroups>
</settings>
The user file
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <localRepository><lab>/m2</localRepository>
  <pluginGroups>
    <pluginGroup>org.jacoco</pluginGroup>
  </pluginGroups>
</settings>
mvn -B -N -gs global-settings.xml -s user-settings.xml help:effective-settings
<settings ...>
  <localRepository><lab>/m2</localRepository>
  <interactiveMode>false</interactiveMode>
  <pluginGroups>
    <pluginGroup>org.jacoco</pluginGroup>
    <pluginGroup>org.eclipse.jetty</pluginGroup>
    <pluginGroup>org.apache.maven.plugins</pluginGroup>
    <pluginGroup>org.codehaus.mojo</pluginGroup>
  </pluginGroups>
</settings>

The localRepository is the user’s, as the user file is dominant; the lists were merged, with the user’s pluginGroup first and the two built-in groups added at the end. Lists of servers, mirrors, proxies and profiles merge the same way, by <id>.

One element did not behave as the sentence "their contents get merged" suggests. A global file that says <offline>true</offline> had no effect in any of my runs, whether the user file was silent or absent, while the same line in the user file did. The run asks Maven for the value of settings.offline with the Help plugin’s evaluate goal:

mvn -q -B -N -gs global-offline.xml -s empty.xml org.apache.maven.plugins:maven-help-plugin:3.5.2:evaluate \
    -Dexpression=settings.offline -DforceStdout
mvn -q -B -N -gs empty.xml -s user-offline-true.xml org.apache.maven.plugins:maven-help-plugin:3.5.2:evaluate \
    -Dexpression=settings.offline -DforceStdout
Maven offline only in the global file offline only in the user file

3.9.16

false

true

3.10.0

false

true

4.0.0-rc-7

false

true

A build that must be offline everywhere therefore sets it in the user file (or passes -o), not in the installation. The shipped installation file contributes more than a template. With an empty user file (<settings/>) and -Dmaven.repo.local pointing at the scratch repository, Maven 3.10.0 reports this, where the mirror is the HTTP blocker described below and comes from the installation file, because the -gs run above, which replaced that file, shows no mirror at all:

<settings ...>
  <localRepository><lab>/m2</localRepository>
  <interactiveMode>false</interactiveMode>
  <mirrors>
    <mirror>
      <mirrorOf>external:http:*</mirrorOf>
      <name>Pseudo repository to mirror external repositories initially using HTTP.</name>
      <url>http://0.0.0.0/</url>
      <blocked>true</blocked>
      <id>maven-default-http-blocker</id>
    </mirror>
  </mirrors>
  <pluginGroups>
    <pluginGroup>org.apache.maven.plugins</pluginGroup>
    <pluginGroup>org.codehaus.mojo</pluginGroup>
  </pluginGroups>
</settings>

Maven 4 adds the project level. A .mvn/settings.xml next to the POM is read by 4.0.0-rc-7 and ignored by 3.10.0 (the Help plugin shows the pluginGroup that the file declares):

The project file, .mvn/settings.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <pluginGroups>
    <pluginGroup>io.tidepool.plugins</pluginGroup>
  </pluginGroups>
</settings>
Maven 4.0.0-rc-7:
    <pluginGroup>io.tidepool.plugins</pluginGroup>
    <pluginGroup>org.apache.maven.plugins</pluginGroup>
    <pluginGroup>org.codehaus.mojo</pluginGroup>

Maven 3.10.0:
    <pluginGroup>org.apache.maven.plugins</pluginGroup>
    <pluginGroup>org.codehaus.mojo</pluginGroup>

The project file is committed with the code, so it can carry a pluginGroup or a mirror that everybody should share, and must never carry a credential. Maven 4’s installation file uses the namespace http://maven.apache.org/SETTINGS/2.0.0 and also lists the Central repositories; Maven 3.10’s uses 1.2.0, and the reference page of 3.10 documents 1.3.0, the schema that has aliases and repositoryOrigins. Maven 3.9.16 reads a 1.3.0 file without complaint.

The top-level elements

Element What it does Section

localRepository, interactiveMode, offline, usePluginRegistry

Simple values

Simple values

pluginGroups

Extra group ids searched for short plugin prefixes

pluginGroups

servers

Credentials and transport settings, by id

Servers

mirrors

Replace a repository by another URL

Mirrors

proxies

Get out through a proxy

Proxies

profiles, activeProfiles

Properties and repositories that apply under conditions

Profiles and activeProfiles

Simple values

localRepository is the path of the local repository. The default is ${user.home}/.m2/repository; the official page points out that it is useful on a build server where all logged-in users should share one repository, and the guide says the path must be absolute. The choices and maven.repo.local are on Configuring Maven.

interactiveMode is true by default and says whether Maven may ask the user for input. -B (batch mode) forces it off, which is why every run on this page that uses -B reports false. It matters to the plugins that prompt, such as the Archetype plugin.

offline makes Maven work from the local repository only. With a fresh local repository and <offline>true</offline> in the user file, Maven 3.10.0 stops at the first thing it needs:

mvn -B -N -s user-offline-fresh.xml help:effective-settings
[ERROR] BUILD FAILURE

usePluginRegistry is a leftover: the plugin registry no longer exists, and the options -npr, -npu and -cpu are printed by Maven 3.10 as "Ineffective, only kept for backward compatibility". Do not set it.

pluginGroups

When a command line names a plugin by prefix (mvn jacoco:report), Maven looks the prefix up in the plugin groups. The list always contains org.apache.maven.plugins and org.codehaus.mojo; <pluginGroups> adds more. The JaCoCo plugin is in neither group, so without the setting the prefix is unknown, and with it the prefix resolves (Maven 3.10.0, lines removed with grep):

pluginGroups
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <pluginGroups>
    <pluginGroup>org.jacoco</pluginGroup>
  </pluginGroups>
</settings>
mvn -B -N -s server-none.xml jacoco:help
mvn -B -N -s plugingroups.xml jacoco:help
[ERROR] No plugin found for prefix 'jacoco' in the current project and in the plugin groups [org.apache.maven.plugins, org.codehaus.mojo] available from the repositories [local (<lab>/m2), central (https://repo.maven.apache.org/maven2)] -> [Help 1]
[INFO] --- jacoco:0.8.15:help (default-cli) @ tidepool-parent ---
[INFO] BUILD SUCCESS

The setting is for plugins whose group you do not want to type on a command line. A POM or a CI script can avoid the whole mechanism by naming the plugin in full, which is the better choice for a shared build, because the setting is on one machine only. See Plugins & Their Configuration.

Servers

A <server> carries what a repository needs to know about you. Its <id> is the key: it must equal the <id> of the repository (in <repositories>, <pluginRepositories>, <distributionManagement>) or of the mirror that Maven talks to. It is not the user’s id.

Element Meaning

id

The id of the repository or mirror these settings are for.

username, password

A pair for HTTP basic authentication. The password may be encrypted (below).

privateKey, passphrase

A pair for SSH: the path of the key (the default, from the official page, is ${user.home}/.ssh/id_dsa) and its passphrase. If a key is used, omit <password>: otherwise the key is ignored. Not run here.

filePermissions, directoryPermissions

Three-digit permissions for files and directories created on deployment, such as 664 and 775. Not run here (they apply to SCP and file transfers).

configuration

Free-form settings for the transport, such as HTTP headers and timeouts.

aliases

Other ids that use the same entry (Maven 3.10 and 4).

repositoryOrigins

Origins the credentials may be sent to (Maven 3.10).

A repository to test against

Credentials cannot be demonstrated without something that asks for them. The runs below use a few lines of Python as a repository that requires authentication and logs what it received: GET and PUT of files under a directory, answering 401 unless the Authorization header is Basic with a known user or the exact bearer token example-token.

authrepo.py, started with python3 authrepo.py 18081
#!/usr/bin/env python3
"""Tiny Basic-auth file repository for the examples: GET and PUT under ./repo."""
import base64, os, sys
from http.server import BaseHTTPRequestHandler, HTTPServer

USERS = {"deployer": "changeit-example", "braces": "pa{ss}word-example"}
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "repo")
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "requests.log")

class Handler(BaseHTTPRequestHandler):
    def log_message(self, *a):
        pass

    def authorised(self):
        header = self.headers.get("Authorization", "")
        who = "-"
        ok = False
        if header == "Bearer example-token":
            who, ok = "token", True
        elif header.startswith("Basic "):
            user, _, pw = base64.b64decode(header[6:]).decode().partition(":")
            who = user
            ok = USERS.get(user) == pw
        with open(LOG, "a") as f:
            f.write("%s %s user=%s %s\n" % (self.command, self.path, who, "ok" if ok else "DENIED"))
        if not ok:
            self.send_response(401)
            self.send_header("WWW-Authenticate", 'Basic realm="tidepool"')
            self.send_header("Content-Length", "0")
            self.end_headers()
        return ok

    def do_GET(self):
        if not self.authorised():
            return
        path = os.path.join(ROOT, self.path.lstrip("/"))
        if os.path.isfile(path):
            data = open(path, "rb").read()
            self.send_response(200)
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)
        else:
            self.send_response(404)
            self.send_header("Content-Length", "0")
            self.end_headers()

    def do_HEAD(self):
        self.do_GET()

    def do_PUT(self):
        length = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(length)
        if not self.authorised():
            return
        path = os.path.join(ROOT, self.path.lstrip("/"))
        os.makedirs(os.path.dirname(path), exist_ok=True)
        open(path, "wb").write(body)
        self.send_response(201)
        self.send_header("Content-Length", "0")
        self.end_headers()

HTTPServer(("127.0.0.1", int(sys.argv[1])), Handler).serve_forever()

The script is this page’s own lab setup, not an example from the Maven documentation. To publish to it, and to resolve from it, the examples use the Deploy plugin’s deploy-file goal on a one-file JAR, and a consumer that depends on it. (-Dmaven.repo.local points at a scratch local repository and is omitted from the commands shown.)

deploy() {
  mvn -B -N -s "$1" "${@:2}" deploy:deploy-file -Dfile=tidepool-tools-1.0.0.jar \
      -DgroupId=io.tidepool -DartifactId=tidepool-tools -Dversion=1.0.0 -Dpackaging=jar \
      -Durl=http://localhost:18081/ -DrepositoryId=tidepool-internal
}

Username and password

First without any <server>, then with one whose id is the -DrepositoryId. The server’s log shows what arrived. The Maven output is cut with grep to the lines that matter, and the long goal prefix of the error is as printed:

No server entry, server-none.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0"/>
With a server entry, server-plain.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <servers>
    <server>
      <id>tidepool-internal</id>
      <username>deployer</username>
      <password>changeit-example</password>
    </server>
  </servers>
</settings>
deploy server-none.xml
[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:3.2.0:deploy-file (default-cli) on project tidepool-parent: Failed to deploy artifacts: Could not transfer artifact io.tidepool:tidepool-tools:jar:1.0.0 from/to tidepool-internal (http://localhost:18081/): HTTP Status: 401

The repository’s log for that run:

PUT /io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar user=- DENIED
PUT /io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom user=- DENIED

With the entry (Maven 3.10.0; the warning is explained under "Where the credentials are sent" below):

deploy server-plain.xml
[WARNING] Using credentials of server 'tidepool-internal' for repository http://localhost:18081/, although no repository or mirror with this id is declared in settings or on the command line and the server declares no <repositoryOrigins>. Declare 'http://localhost:18081' in <repositoryOrigins> of that server in settings to bind these credentials explicitly, or set maven.repository.credentialScope=strict to refuse such credential use.
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS
PUT /io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar user=deployer ok
PUT /io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar.sha1 user=deployer ok
PUT /io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar.md5 user=deployer ok

An HTTP 401 means no credentials were sent or they were wrong; on this page it appeared in every failure of this kind, and it is the same message for a missing entry, a wrong id, a wrong password and, as seen below, a password that could not be decrypted. The way to tell which is -X and the log of the repository.

Aliases: one entry for several ids

Maven 3.10 added <aliases>: for each alias an additional server is generated with the same configuration and the alias as id. That is the way to use one set of credentials for tidepool-internal and for tidepool-staging without copying the entry. The next run uses a settings file that activates a profile with a repository called tidepool-staging and a single server called tidepool-internal with the alias, against the same repository, on the three versions:

aliases.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <servers>
    <server>
      <id>tidepool-internal</id>
      <username>deployer</username>
      <password>${env.TIDEPOOL_PASSWORD}</password>
      <aliases>
        <alias>tidepool-staging</alias>
      </aliases>
    </server>
  </servers>
  <profiles>
    <profile>
      <id>staging</id>
      <repositories>
        <repository>
          <id>tidepool-staging</id>
          <url>http://localhost:18081/</url>
          <snapshots><enabled>false</enabled></snapshots>
        </repository>
      </repositories>
    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>staging</activeProfile>
  </activeProfiles>
</settings>
Maven 3.10.0:
[INFO] Downloaded from tidepool-staging: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO]    io.tidepool:tidepool-tools:jar:1.0.0:compile -- module tidepool.tools (auto)
[INFO] BUILD SUCCESS
repository log, requests accepted with credentials: 5
Maven 3.9.16:
[INFO] BUILD FAILURE
[ERROR] Failed to read artifact descriptor for io.tidepool:tidepool-tools:jar:1.0.0
[ERROR] 	Caused by: The following artifacts could not be resolved: io.tidepool:tidepool-tools:pom:1.0.0 (absent): Could not transfer artifact io.tidepool:tidepool-tools:pom:1.0.0 from/to tidepool-staging (http://localhost:18081/): status code: 401, reason phrase: Unauthorized (401)
repository log, requests accepted with credentials: 0
Maven 4.0.0-rc-7:
[INFO] Downloaded from tidepool-staging: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO]    io.tidepool:tidepool-tools:jar:1.0.0:compile -- module tidepool.tools (auto)
[INFO] BUILD SUCCESS
repository log, requests accepted with credentials: 5

Maven 3.9.16 does not fail on the unknown element: it ignores it, so the repository is asked without credentials and answers 401. A file that relies on aliases works on 3.10 and 4 and fails on 3.9, which a team with mixed versions has to know.

Where the credentials are sent: repositoryOrigins and credentialScope

The warning in the earlier run is part of the security hardening of Maven 3.10: according to the release notes, the credentials of a server are scoped to the origin (scheme, host and port) of the repositories and mirrors declared with the same id in settings or on the command line, and the id is matched exactly, so that an entry no longer leaks to unrelated repositories that share an id prefix. In the deploy-file run there is no such repository (the URL is given with -Durl), so Maven warns and uses the credentials anyway. The property maven.repository.credentialScope=strict turns the warning into a refusal:

deploy server-plain.xml -Dmaven.repository.credentialScope=strict
[WARNING] Not using credentials of server 'tidepool-internal' for repository http://localhost:18081/: no repository or mirror with this id is declared in settings or on the command line and the server declares no <repositoryOrigins>, and maven.repository.credentialScope=strict is in effect. Declare 'http://localhost:18081' in <repositoryOrigins> of that server in settings to allow it.
[ERROR] BUILD FAILURE
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:3.2.0:deploy-file (default-cli) on project tidepool-parent: Failed to deploy artifacts: Could not transfer artifact io.tidepool:tidepool-tools:jar:1.0.0 from/to tidepool-internal (http://localhost:18081/): HTTP Status: 401

The fix suggested in the message is <repositoryOrigins>, an origin (not a full URL) the credentials may be used for. The origins are added to the ones Maven derives; nothing is replaced. They are needed when the repository is not known when the session is created, such as one that a settings profile declares and that is activated afterwards:

server-origins.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <servers>
    <server>
      <id>tidepool-internal</id>
      <username>deployer</username>
      <password>changeit-example</password>
      <repositoryOrigins>
        <repositoryOrigin>http://localhost:18081</repositoryOrigin>
      </repositoryOrigins>
    </server>
  </servers>
</settings>
deploy server-origins.xml -Dmaven.repository.credentialScope=strict
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS

Maven 4.0.0-rc-7 has the warning and the strict value too, with a shorter text, but in this run it did not honour <repositoryOrigins>: the same strict run with the same file failed there.

Maven 4.0.0-rc-7, default scope:
[WARNING] Using credentials of server 'tidepool-internal' for repository http://localhost:18081/, 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] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS

Maven 4.0.0-rc-7, strict, with repositoryOrigins in the server:
[WARNING] Not using credentials of server 'tidepool-internal' for repository http://localhost:18081/: 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-file (default-cli) on project tidepool-parent: Failed to deploy artifacts: Could not transfer artifact io.tidepool:tidepool-tools:jar:1.0.0 from/to tidepool-internal (http://localhost:18081/): HTTP Status: 401

Maven 3.9.16 prints no warning and sends the credentials. I did not reproduce the leak that the release notes describe (a repository that shares an id prefix with a server) and quote it from them.

Configuration: headers and timeouts

<configuration> is handed to the transport. The page on the Resolver transport shows custom HTTP headers for all HTTP transports, and that is how a token is sent to a repository manager that takes Authorization: Bearer instead of basic authentication. The test repository accepts that header; no <username> is involved:

server-header.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <servers>
    <server>
      <id>tidepool-internal</id>
      <configuration>
        <httpHeaders>
          <property>
            <name>Authorization</name>
            <value>Bearer example-token</value>
          </property>
        </httpHeaders>
      </configuration>
    </server>
  </servers>
</settings>
Maven 3.10.0:
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS

Maven 4.0.0-rc-7:
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS

The repository’s log shows user=token ok for the uploads of both runs. Timeouts are also set through <configuration>; the key names depend on the transport (the native HTTP transport is the default since Maven 3.9.0, the Wagon one is the alternative) and are in the transport guide. Not run here.

Mirrors

A mirror replaces a repository by another URL: a company’s repository manager, a regional copy of Central. The semantics are on Repositories, Mirrors & Repository Managers; the elements are:

Element Meaning Since

id, name

Identifier and label. The id is also the key of the <server> with the credentials for the mirror.

url

The base URL used instead of the repository’s.

mirrorOf

The ids to replace: central, repo1,repo2, *, external:* (everything except localhost and file URLs), external:http:* (only HTTP ones), and negations such as *,!inhouse. It must not equal the mirror id.

external:http:*: 3.8.0

layout, mirrorOfLayouts

Layout of the mirror and the layouts it mirrors (default,legacy by default).

Maven 3

blocked

Fail any download that would go through this mirror, explaining why.

3.8.0

The default HTTP blocker

The installation file ships a blocking mirror, shown in the earlier output: maven-default-http-blocker, mirrorOf external:http:*, url http://0.0.0.0/, blocked true. A repository that is reached over plain HTTP and is not on the local machine is therefore refused. A consumer whose POM declares a repository at http://repo.tidepool.example/maven fails with the explanation in the message (Maven 3.10.0, only the cause is kept):

mvn -B -s empty-settings.xml dependency:resolve
[ERROR] 	Caused by: The following artifacts could not be resolved: io.tidepool:tidepool-tools:pom:1.0.0 (absent): Could not transfer artifact io.tidepool:tidepool-tools:pom:1.0.0 from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories: [tidepool-internal (http://repo.tidepool.example/maven, default, releases)]

The blocker is part of the installation file, not of Maven’s code: the earlier -gs run, which replaced that file, had no mirror. A team that keeps its own installation file must carry the mirror over; the fix for the build above is HTTPS, or a more specific mirror of that repository id.

Credentials of a mirror, and the star

A mirror is looked up among the servers by its own id. Here a * mirror points everything at the test repository, and a server with the same id carries the credentials:

mirror-to-local.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <mirrors>
    <mirror>
      <id>tidepool-internal</id>
      <name>Internal repository for everything under tidepool.example</name>
      <url>http://localhost:18081/</url>
      <mirrorOf>*</mirrorOf>
    </mirror>
  </mirrors>
  <servers>
    <server>
      <id>tidepool-internal</id>
      <username>deployer</username>
      <password>${env.TIDEPOOL_PASSWORD}</password>
    </server>
  </servers>
</settings>
[ERROR] No plugin found for prefix 'dependency' in the current project and in the plugin groups [org.apache.maven.plugins, org.codehaus.mojo] available from the repositories [local (<lab>/m2c), tidepool-internal (http://localhost:18081/)] -> [Help 1]

The repository’s log starts with a request for a plugin that is not in it, first without and then with the credentials of the mirror’s server:

GET /org/apache/maven/plugins/maven-clean-plugin/3.5.0/maven-clean-plugin-3.5.0.pom user=- DENIED
GET /org/apache/maven/plugins/maven-clean-plugin/3.5.0/maven-clean-plugin-3.5.0.pom user=deployer ok

So * sends plugin downloads to the mirror too, and a mirror that holds only internal artifacts cannot answer for them: the repository manager must proxy Central, or mirrorOf must list only the ids the mirror really serves. The error text above is the usual symptom of this mistake.

Proxies

A <proxy> describes a proxy server. Only one proxy may be active at a time.

Element Meaning

id

Identifier.

active

true if this proxy is in use. This allows several to be declared and one chosen.

protocol, host, port

The proxy as protocol://host:port, in three separate elements.

username, password

Credentials for the proxy itself. The password can be encrypted like a server password (not run here).

nonProxyHosts

The hosts that are not proxied, delimited by | in the official example (a comma is also common), with * as wildcard.

The next two settings differ only in nonProxyHosts. The proxy is an address where nothing listens, and the repository is the local one, so the first build must fail connecting to the proxy and the second must bypass it. To keep the plugins out of the test they were resolved beforehand into the local repository (Maven 3.10.0):

<proxy>
  <id>office</id>
  <active>true</active>
  <protocol>http</protocol>
  <host>127.0.0.1</host>
  <port>18099</port>
  <nonProxyHosts>*.tidepool.example</nonProxyHosts>   <!-- proxy-direct.xml -->
  <!-- proxy-bypass.xml: <nonProxyHosts>localhost|*.tidepool.example</nonProxyHosts> -->
</proxy>
proxy-direct.xml (nonProxyHosts without localhost):
[ERROR] BUILD FAILURE
[ERROR] Failed to read artifact descriptor for io.tidepool:tidepool-tools:jar:1.0.0
[ERROR] 	Caused by: The following artifacts could not be resolved: io.tidepool:tidepool-tools:pom:1.0.0 (absent): Could not transfer artifact io.tidepool:tidepool-tools:pom:1.0.0 from/to tidepool-internal (http://localhost:18081/): Connect to 127.0.0.1:18099 [/127.0.0.1] failed: Connection refused
proxy-bypass.xml (nonProxyHosts with localhost):
[INFO]    io.tidepool:tidepool-tools:jar:1.0.0:compile -- module tidepool.tools (auto)
[INFO] BUILD SUCCESS

The error names the proxy, not the repository, which is how to recognise a missing nonProxyHosts entry. The credentials of the repository are unaffected. A real authenticating proxy, and the system properties https.proxyHost and http.proxyHost, are not run here.

Profiles and activeProfiles

A profile in settings.xml is a reduced profile: activation, repositories, pluginRepositories and properties, nothing else, because the file describes the build system and not a project. The activation conditions are the same as in the POM: activeByDefault, jdk, os, property, file (see Profiles), and a profile can also be named in <activeProfiles> or on the command line with -P. A profile that is active from the settings overrides a profile of the same id in the POM.

Two settings profiles, one activated by <activeProfiles> and one by the JDK, and a reference to a profile that does not exist (Maven 3.10.0):

profiles.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <profiles>
    <profile>
      <id>internal-repo</id>
      <properties>
        <tidepool.repo.url>http://localhost:18081/</tidepool.repo.url>
      </properties>
      <repositories>
        <repository>
          <id>tidepool-internal</id>
          <url>http://localhost:18081/</url>
          <releases><enabled>true</enabled><updatePolicy>never</updatePolicy></releases>
          <snapshots><enabled>false</enabled></snapshots>
        </repository>
      </repositories>
    </profile>
    <profile>
      <id>on-jdk-26</id>
      <activation>
        <jdk>26</jdk>
      </activation>
      <properties>
        <tidepool.jdk.note>built on JDK 26</tidepool.jdk.note>
      </properties>
    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>internal-repo</activeProfile>
    <activeProfile>no-such-profile</activeProfile>
  </activeProfiles>
</settings>
mvn -B -N -s profiles.xml help:active-profiles
mvn -B -N -s profiles.xml -P '!internal-repo' help:active-profiles
Active Profiles for Project 'io.tidepool:tidepool-parent:pom:1.0.0':
The following profiles are active:
 - internal-repo (source: external)
 - on-jdk-26 (source: external)
The following profiles are active:
 - on-jdk-26 (source: external)

The profiles are reported with source: external, which is how Maven names the settings. The unknown no-such-profile is silent, as the official page says ("If no matching profile is found nothing will happen"). The properties of the profile are visible to the POM: ${tidepool.repo.url} evaluated to http://localhost:18081/ in the same session (run with the Help plugin’s evaluate goal).

Repositories declared in a settings profile are not part of the project: Maven 4 removes, by default, the repositories that come from an active settings profile from the consumer POM that it publishes (see Maven 4: POM Model & Consumer POM), and a repository that appears only in somebody’s settings is a build that works on one machine. Use settings profiles for what really differs between machines, and put the repositories the project needs in the POM.

Interpolation

The text of settings.xml is interpolated before it is used. The official page lists ${user.home} and every other system property, and ${env.HOME} and the other environment variables, and says that properties defined in profiles of the same file cannot be used. That makes environment variables the mechanism for keeping secrets out of a committed settings file:

interpolation.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <localRepository>${env.TIDEPOOL_REPO}</localRepository>
  <servers>
    <server>
      <id>tidepool-internal</id>
      <username>${env.TIDEPOOL_USER}</username>
      <password>${env.TIDEPOOL_PASSWORD}</password>
    </server>
  </servers>
  <proxies>
    <proxy>
      <id>office</id>
      <active>true</active>
      <protocol>http</protocol>
      <host>${env.TIDEPOOL_PROXY_HOST}</host>
      <port>${env.TIDEPOOL_PROXY_PORT}</port>
      <nonProxyHosts>localhost|*.tidepool.example</nonProxyHosts>
    </proxy>
  </proxies>
</settings>

The release notes of Maven 3.10 announce "full settings.xml interpolation, including non-string values such as ports". The variables were set to placeholders (the repository, deployer, the password, proxy.tidepool.example and 3128), and the Help plugin printed the result on the three versions (only the repository and the proxy are shown):

Maven 3.10.0:
  <localRepository><lab>/m2</localRepository>
  <proxies>
    <proxy>
      <port>3128</port>
      <host>proxy.tidepool.example</host>
      <nonProxyHosts>localhost|*.tidepool.example</nonProxyHosts>
      <id>office</id>
    </proxy>
  </proxies>
Maven 3.9.16:
  <localRepository><lab>/m2</localRepository>
  <proxies>
    <proxy>
      <port>0</port>
      <host>proxy.tidepool.example</host>
      <nonProxyHosts>localhost|*.tidepool.example</nonProxyHosts>
      <id>office</id>
    </proxy>
  </proxies>
Maven 4.0.0-rc-7:
  <localRepository><lab>/m2</localRepository>
  <proxies>
    <proxy>
      <port>3128</port>
      <host>proxy.tidepool.example</host>
      <nonProxyHosts>localhost|*.tidepool.example</nonProxyHosts>
      <id>office</id>
    </proxy>
  </proxies>

The port is the difference: Maven 3.9.16 cannot turn ${env.TIDEPOOL_PROXY_PORT} into a number and reads 0, while 3.10 and 4 read 3128. The Help plugin masks the password as ***.

Two more observations from the same runs, identical on the three versions. A variable that is not set is not an error: the text stays as it is, so <username>${env.TIDEPOOL_NOT_SET}</username> is sent as that literal text, and the symptom is a 401 and not a message about the variable. And a property that comes from a profile of the same file is not available: a <username> of ${tidepool.user} with that property defined in an active settings profile stayed ${tidepool.user}.

Password encryption in Maven 3

A password in settings.xml is readable by everyone who can read the file. Maven 3 offers a first remedy since 2.1.0: passwords (of servers and, by the same mechanism, proxies) can be stored encrypted with a master password that lives in a second file. The guide states the use case: a shared build machine, some users allowed to deploy and some not, one settings.xml for all of them, and an extra settings-security.xml in the home directory of those who may deploy.

The guide is blunt about the strength: the encryption of Maven 3 is "obfuscation-grade", using a single-hash key derivation and no authenticated encryption; it keeps secrets out of logs and screenshots but does not resist an attacker who can read both files. It is not a replacement for a secrets manager (below), and it is the reason Maven 4 has a new tool.

Step 1: the master password

mvn --encrypt-master-password (-emp) encrypts a password of your choice. Since Maven 3.2.1 the argument is optional, and without it Maven prompts, which keeps the password out of the shell history. The prompt needs a real terminal. The next run drove the prompt through a pseudo terminal; the typed password (a placeholder) is not echoed:

mvn --encrypt-master-password
Master password:
{sLgl3xbdbm4HZ7t/LLEVrsb7XxPm8hWj/lYzSzgS/Hz7O/vq4/bTCJABw8C1PQQ5}

Piping the password does not work, and the failure is silent: with the standard input redirected there is no console, and Maven 3.10.0 encrypts an empty password and prints {}.

echo "master-example-1" | mvn --encrypt-master-password
{}

The password can still be given as an argument (mvn -emp master-example-1), as it was before 3.2.1; it then sits in the history of the shell. The output differs on every run (the encryption is salted): two runs on the same input printed different values.

The encrypted master password goes into settings-security.xml, by default ~/.m2/settings-security.xml. In the runs the file was passed with the system property settings.security:

settings-security.xml
<settingsSecurity>
  <master>{aq2p9bg3FKoHLhp/m+10Dr7zkz5aRJxe4BgmFYolIE9JeVW4K0G261Ogj84TgE8q}</master>
</settingsSecurity>

The guide says the master password is encrypted with a hardcoded key, so that the file should be treated as if it held the password in plain text. Its protection is the file permissions and, below, keeping it somewhere else.

Step 2: the server password

mvn --encrypt-password (-ep) works the same way and needs the file of step 1:

mvn -Dsettings.security=settings-security.xml --encrypt-password
Password:
{qWVsS5y31/8HIWe9nXWcUfRBeh1VkeVGky22FCF7vP6y+bpiEd92rzGXtKEwZy0y}

Without the file there is nothing to encrypt with, and the error is the Java exception (Maven 3.10.0, user home shortened):

mvn --encrypt-password changeit-example
[ERROR] Error executing Maven.
[ERROR] java.io.FileNotFoundException: ~/.m2/settings-security.xml (No such file or directory)
[ERROR] Caused by: ~/.m2/settings-security.xml (No such file or directory)

The value, braces included, replaces the password; the guide adds that text outside the braces is allowed, which makes room for a note about the password’s expiry:

server-encrypted.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <servers>
    <server>
      <id>tidepool-internal</id>
      <username>deployer</username>
      <password>{qWVsS5y31/8HIWe9nXWcUfRBeh1VkeVGky22FCF7vP6y+bpiEd92rzGXtKEwZy0y}</password>
    </server>
  </servers>
</settings>
deploy server-encrypted.xml -Dsettings.security=settings-security.xml
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS

Maven 3.9.16 produced the same success with the same two files.

What goes wrong, and how it looks

The three common failures all end in the same 401 as a wrong password, because a value that cannot be decrypted is used as it is. The message appears only with -X.

Situation Result on Maven 3.10.0

settings-security.xml not found

401; -X logs a FileNotFoundException for the file.

The master password in the file is not the one used to encrypt

401; -X logs "Failed to decrypt password for server" with a BadPaddingException.

settings-security.xml relocated to a drive that is not mounted

401, as for the missing file.

The first one, with the deploy helper of the lab setup above:

deploy server-encrypted.xml
[ERROR] BUILD FAILURE
[ERROR] ...: Failed to deploy artifacts: Could not transfer artifact io.tidepool:tidepool-tools:jar:1.0.0 from/to tidepool-internal (http://localhost:18081/): HTTP Status: 401

The repository’s last log line shows the literal braces were sent as the password: PUT /io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom user=deployer DENIED. The second, with a settings-security.xml whose master was made from another password, and the debug line of -X:

[ERROR] BUILD FAILURE
[ERROR] ...: Failed to deploy artifacts: Could not transfer artifact io.tidepool:tidepool-tools:jar:1.0.0 from/to tidepool-internal (http://localhost:18081/): HTTP Status: 401
[DEBUG] Failed to decrypt password for server tidepool-internal: org.sonatype.plexus.components.cipher.PlexusCipherException: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

The master password on a removable drive

The guide’s answer to "the file is only as good as its storage" is a relocation. The file in ~/.m2 says where the real one is:

A settings-security.xml that points elsewhere
<settingsSecurity>
  <relocation>/Volumes/mySecureUsb/secure/settings-security.xml</relocation>
</settingsSecurity>

In the runs the "drive" was a directory. With it present the deploy works, and with the directory renamed, as if unplugged, it fails with the same 401:

[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS
[ERROR] BUILD FAILURE
[ERROR] ...: Failed to deploy artifacts: Could not transfer artifact io.tidepool:tidepool-tools:jar:1.0.0 from/to tidepool-internal (http://localhost:18081/): HTTP Status: 401

Braces in passwords

The curly braces delimit the encrypted part, so a password with braces needs care. The guide’s advice is to escape literal braces inside the encrypted text with a backslash. For a password that is not encrypted, Maven 3.10 and 4 use it as it is when what is between the braces cannot be decrypted: pa{ss}word-example as a plain password was accepted by the test repository on both, with and without a settings-security.xml in place, while pa\{ss\}word-example was refused on 3.10 (Maven 4 accepted it), and pa{ss}word-example}, with an extra closing brace at the end, was refused on both. The robust choice is to avoid braces in passwords, or to encrypt the whole password and let the tool deal with them.

Password encryption in Maven 4: mvnenc

Maven 4 replaces it with a tool, mvnenc (Maven Encryption), in the bin directory. The options --encrypt-master-password and --encrypt-password no longer exist: Maven 4.0.0-rc-7 answers Unrecognized option: --encrypt-password. The "What’s new" page says the new system does what Maven 3’s did, "but adds improvements like a decrypt functionality and supports external vaults", and the guide has the vocabulary used below:

Term Meaning

Dispatcher

A pluggable provider that decrypts the values of settings.xml. The configuration chooses the default.

master

Values are encrypted with a master key and stored in settings.xml, as in Maven 3, with the cipher AES/GCM/NoPadding and PBKDF2WithHmacSHA512 over the master key.

masterSourceLookup

Introduced in 4.0.0-rc-3. Nothing is encrypted: the value in settings.xml is a reference to a master key source, and the secret is read from there when needed.

legacy

The Maven 3 mechanism, for a settings.xml shared by Maven 3 and Maven 4 builds.

Master key source

Where the key (or secret) comes from: a file, an environment variable, a Java system property, the GnuPG agent, a pinentry prompt, or the 1Password CLI.

The master key sources, with their formats in the guide: file:<absolute path>, env:<variable>, system-property:<property>, gpg-agent:<socket path>, pinentry-prompt:<pinentry binary> and onepassword:op://.... The choice is about the trade-off between a key that is always there (file, environment variable) and one that costs a person a manual step (the agent once per session, pinentry each time). The wizard also offers a "secure console password prompt".

Setup

mvnenc init is the wizard: it asks for the default dispatcher (none, master password, master source lookup), then the source of the master key and the cipher, and writes settings-security4.xml in ${maven.user.conf}, by default ~/.m2. The goal is documented as interactive only, and it needs a real terminal; my attempts to drive its menus through a pseudo terminal were not reliable enough to quote, so the file below was written by hand, with the elements that the wizard asks for (the choice of master, the source env:TIDEPOOL_MASTER_KEY, the cipher). The format is the one documented for the Plexus security dispatcher. Do not take this as the recommended way of creating the file: the guide recommends writing it with mvnenc only. Not run here: the wizard itself, and the sources gpg-agent, pinentry-prompt, onepassword and file.

settings-security4.xml in the user configuration directory
<?xml version="1.0" encoding="UTF-8"?>
<settingsSecurity xmlns="http://codehaus-plexus.github.io/plexus-sec-dispatcher/4.0.0">
  <modelVersion>4.0</modelVersion>
  <defaultDispatcher>master</defaultDispatcher>
  <configurations>
    <configuration>
      <name>master</name>
      <properties>
        <property>
          <name>source</name>
          <value>env:TIDEPOOL_MASTER_KEY</value>
        </property>
        <property>
          <name>cipher</name>
          <value>AES/GCM/NoPadding</value>
        </property>
      </properties>
    </configuration>
  </configurations>
</settingsSecurity>

mvnenc diag validates the configuration. The master key lives in an environment variable, which was set to a placeholder:

TIDEPOOL_MASTER_KEY=master-example-3 mvnenc diag
[INFO] Configuration validation of MavenSecDispatcher: VALID
[INFO]   Configuration file present on path ~/.m2/settings-security4.xml
[INFO]   Default dispatcher configured
[INFO]   Configured default dispatcher configuration is valid
[INFO]   Legacy dispatcher present in system
[WARNING]   Legacy dispatcher not operational; transparent fallback not possible
[INFO]   Configuration validation of MasterDispatcher: VALID
[INFO]     Configured Cipher supported
[INFO]     Configured Source configuration valid
[INFO]     Configuration validation of EnvMasterSource: VALID
[INFO]       Configured environment variable exist
[INFO]   Configuration validation of LegacyDispatcher: INVALID
[ERROR]     Legacy configuration not found or does not contains encrypted master password

The legacy dispatcher is reported as invalid because there is no settings-security.xml; that only matters for the fallback, below. The guide also notes that, in contrast to settings.xml, there are no expressions in the security settings.

Encrypting and decrypting

mvnenc encrypt asks for the value (not echoed) and prints it in the format {[name=<dispatcher>,<attributes>,version=...]<value>}. The guide says the version is 4.0; the 4.0.0-rc-7 run printed 4.2:

TIDEPOOL_MASTER_KEY=master-example-3 mvnenc encrypt
Enter the password to encrypt: ****************
{[name=master,cipher=AES/GCM/NoPadding,version=4.2]IWYLcf4VCiZHRJOqrt+HUvP9XCohdR9LSJ1kj5LmuBCUsEFKCFuIlXeeEPACLVZxa4kh0a38bnd79Unt}

That value was pasted into the server (it is used as it is, with no changes to the layout of the file) and Maven 4 deployed with it, with the environment variable set, and with -Dmaven.settings.security=<path> pointing at the file from another home directory:

server-encrypted4.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.3.0">
  <servers>
    <server>
      <id>tidepool-internal</id>
      <username>deployer</username>
      <password>{[name=master,cipher=AES/GCM/NoPadding,version=4.2]IWYLcf4VCiZHRJOqrt+HUvP9XCohdR9LSJ1kj5LmuBCUsEFKCFuIlXeeEPACLVZxa4kh0a38bnd79Unt}</password>
    </server>
  </servers>
</settings>
Maven 4.0.0-rc-7, key in the environment:
[WARNING] Using credentials of server 'tidepool-internal' for repository http://localhost:18081/, 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] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS

Maven 4.0.0-rc-7, security file chosen with -Dmaven.settings.security:
[WARNING] Using credentials of server 'tidepool-internal' for repository http://localhost:18081/, 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] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS

mvnenc decrypt does the opposite, which Maven 3 had no command for. Anyone who can run it with the master key can read every password of the file; the key is the secret:

TIDEPOOL_MASTER_KEY=master-example-3 mvnenc decrypt
Enter the password to decrypt: {[name=master,cipher=AES/GCM/NoPadding,version=4.2]IWYLcf4VCiZHRJOqrt+HUvP9XCohdR9LSJ1kj5LmuBCUsEFKCFuIlXeeEPACLVZxa4kh0a38bnd79Unt}

changeit-example

What does not work:

Situation Result

Maven 4 without the master key in the environment

The build stops before it starts, with two lines: "Error executing Maven." and "Error building settings". -e adds nothing, and the variable is not named.

Maven 3.10.0 reading the Maven 4 value

401. A settings.xml with Maven 4 values is for Maven 4 only. The warning from the credential scope also appeared.

The second row, as printed:

[WARNING] Using credentials of server 'tidepool-internal' for repository http://localhost:18081/, although no repository or mirror with this id is declared in settings or on the command line and the server declares no <repositoryOrigins>. Declare 'http://localhost:18081' in <repositoryOrigins> of that server in settings to bind
[ERROR] BUILD FAILURE
[ERROR] ...: Failed to deploy artifacts: Could not transfer artifact io.tidepool:tidepool-tools:jar:1.0.0 from/to tidepool-internal (http://localhost:18081/): HTTP Status: 401

The legacy dispatcher: one settings.xml for both

The legacy dispatcher reads the Maven 3 settings-security.xml. With no settings-security4.xml and a Maven 3 settings-security.xml in the home directory, mvnenc diag reports a transparent fallback, and Maven 4 deployed with the Maven 3 encrypted value of the earlier section:

[INFO] Configuration validation of MavenSecDispatcher: INVALID
[INFO]   Legacy dispatcher present in system
[INFO]   Legacy dispatcher is operational; transparent fallback possible
[ERROR]   No configuration file found on path ~/.m2/settings-security4.xml
[INFO]   Configuration validation of LegacyDispatcher: VALID
[INFO]     Legacy configuration found with encrypted master password
[INFO]     Legacy master password successfully decrypted
[WARNING] Using credentials of server 'tidepool-internal' for repository http://localhost:18081/, 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] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS

This is the migration path: keep the Maven 3 values and files while some builds still use 3.x, then re-encrypt with mvnenc and drop the old file. It is the reason to read Migrating to Maven 4.

masterSourceLookup: no secret in the file at all

The lookup dispatcher needs no cipher. mvnenc encrypt is given a source reference and prints a placeholder that contains only that reference (the tool checks that the variable exists at that moment). Here the "secret" is the password itself, in an environment variable of the CI job:

settings-security4.xml for the lookup dispatcher
<?xml version="1.0" encoding="UTF-8"?>
<settingsSecurity xmlns="http://codehaus-plexus.github.io/plexus-sec-dispatcher/4.0.0">
  <modelVersion>4.0</modelVersion>
  <defaultDispatcher>masterSourceLookup</defaultDispatcher>
</settingsSecurity>
TIDEPOOL_PASSWORD=changeit-example mvnenc encrypt     # input: env:TIDEPOOL_PASSWORD
{[name=masterSourceLookup,version=4.2]env:TIDEPOOL_PASSWORD}

With that placeholder as the password, the deploy worked when the variable held the right value, and failed with a 401 when it held another one:

Right value:
[WARNING] Using credentials of server 'tidepool-internal' for repository http://localhost:18081/, 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] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.jar
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/1.0.0/tidepool-tools-1.0.0.pom
[INFO] Uploaded to tidepool-internal: http://localhost:18081/io/tidepool/tidepool-tools/maven-metadata.xml
[INFO] BUILD SUCCESS

Wrong value:
[WARNING] Using credentials of server 'tidepool-internal' for repository http://localhost:18081/, 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.
[ERROR] BUILD FAILURE
[ERROR] ...: Failed to deploy artifacts: Could not transfer artifact io.tidepool:tidepool-tools:jar:1.0.0 from/to tidepool-internal (http://localhost:18081/): HTTP Status: 401

The placeholder is safe to commit, because it contains a name and not a secret; a 1Password reference (onepassword:op://...) or a gpg-agent source works the same way. Not run here.

Keeping credentials out of the repository

Encryption is the second line of defence. The first is that a secret is never in a file that is committed, built into an image or printed in a log.

Practice How Run here

Reference environment variables from settings.xml

<password>${env.TIDEPOOL_PASSWORD}</password>; the CI job sets the variable from its secret store.

Yes; three versions, above

Keep the file outside the checkout

~/.m2/settings.xml of the build account, with restrictive permissions (the guide suggests chmod 600).

No

Let the CI system write the file

The setup-java action of GitHub generates a settings.xml with server-id and the names of the environment variables for username and password (and, in current versions, mvn-server-repository-origins).

No

Keep settings.xml in a repository only if it has no secrets

A file under .mvn or a CI file with ${env.*} and mirrors is fine; one with passwords, encrypted or not, is not.

Partly

Scan commits for secrets

A pre-commit scanner such as detect-secrets, as this documentation repository does for its own sources.

No

Never put a secret on the command line

-Dpassword=... shows in process listings and shell history; so does the -ep argument form.

No

Prefer a token to a password

A repository token in a header (httpHeaders), revocable and scoped.

Yes; header above

Rotate what leaked

An encrypted password that has been committed is leaked: the master key and the file are enough to read it.

No

The Help plugin masks passwords when it prints the settings, and in my -X run of a deploy with a plain password the password did not appear in the log; a debug log still shows paths, user names and stack traces, so read it before pasting it into a ticket. The guide adds two cleanups after encrypting: clear the shell history and check the editor caches (the guide names ~/.viminfo), as a password typed in plain text may still be there.

The same job in Ant and Gradle

Need Ant with Ivy Gradle

Credentials for a repository

A credentials element in the Ivy settings, with host, realm, username and password; see Ivy Settings & Resolvers.

credentials in the repository declaration, read from gradle.properties or an environment variable; see Build Environment & Properties and Publishing.

Machine-wide settings that are not in the project

A property file loaded by the build, or ant.properties

~/.gradle/gradle.properties and init scripts

Encryption of the stored secret

None

None built in; use an environment variable or a secrets plugin

Mirrors and a repository manager

Resolver chains in the settings

Repository declarations with a mirror in an init script

Maven’s design is the only one of the three that has a named settings file with a standard layout and a standard place for a vault integration. The comparison across the tools is on Repositories & Publishing Across Tools and Supply-Chain Security.

Differences between the versions

All observed in the runs above.

Feature 3.9.16 3.10.0 4.0.0-rc-7

Project settings file .mvn/settings.xml

No

No

Yes

Installation settings option

-gs

-gs

-is (-gs deprecated)

Non-string values interpolated (a port)

No (0)

Yes

Yes

Server aliases

Ignored

Yes

Yes

Server repositoryOrigins

Parsed without error, no effect

Yes

Accepted but not honoured in the strict run

Warning for credentials without a declared repository, and maven.repository.credentialScope=strict

No

Yes

Yes

--encrypt-master-password and --encrypt-password

Yes

Yes

No; use mvnenc

settings-security.xml (Maven 3 values)

Yes

Yes

Through the legacy dispatcher

settings-security4.xml and {[name=...]...} values

No

No

Yes

Common problems

Symptom Usual cause

401 although <server> has the right password

The server id differs from the repository id (or from the mirror id); the password is a Maven 3 value but settings-security.xml is missing, or has another master; the environment variable is not set and the text stayed as it is.

Encrypting prints an empty value {}

Standard input was redirected; use the prompt on a terminal, or the argument form knowing it reaches the history.

"Blocked mirror for repositories"

A repository over plain HTTP that is not local; use HTTPS or a mirror of that id.

"No plugin found for prefix" after adding a * mirror

The mirror does not hold the plugins; it must proxy Central, or mirrorOf must name only the repositories it serves.

"Connect to …​ failed: Connection refused" naming a proxy

The proxy is active for a host that should be excluded, or the proxy is down; check nonProxyHosts.

The same settings work on 3.10 and not on 3.9

aliases or a port from ${env.X}; both need 3.10.

A global offline has no effect

It is not honoured from the installation file in any of the three versions; set it in the user file.

A value on a Maven 4 build is ignored in a Maven 3 build

{[name=...]...} values are Maven 4 only.

The only hint of a failed decryption is missing

It is logged at debug level only; run once with -X and search for "Failed to decrypt".

More in Troubleshooting & Best Practices. For where the credentials meet publishing, see Install, Deploy & Distribution Management; for repository filtering and checksums, Maven Resolver; for the Maven 4 changes as a whole, Maven 4: What’s New; and for the release-time use of the same servers, Publishing to Maven Central.

What was not run here

  • SSH authentication with privateKey and passphrase, and the permissions elements.

  • A real authenticating proxy and the Java proxy system properties.

  • The mvnenc init wizard, and the master key sources file, gpg-agent, pinentry-prompt, onepassword and system-property.

  • The leak of credentials to a repository that shares an id prefix, described in the Maven 3.10 release notes.

  • The GitHub setup-java action, and transport timeouts.

  • Windows quoting of passwords with special characters, which the guide covers.