Releasing

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

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

This page is written against Apache Maven 3.10.0 with the Apache Maven Release Plugin 3.3.1, the Deploy Plugin 3.2.0 and the Versions Maven Plugin 2.23.0 (a MojoHaus plugin), and Git 2.54. Every example marked as run was executed on JDK 26 on 2026-10-16. Maven 3.9.16 and Maven 4.0.0-rc-7 (a release candidate) were run where the page says so. A release writes to a source repository, so every run was made against a scratch Git repository that has a local bare repository as its "remote" (scm:git:file://<lab>/remote.git, where <lab> is the scratch directory), a throwaway Git identity set through a private GIT_CONFIG_GLOBAL file (so the user’s own Git configuration was not touched), and a file: repository for the deploy. Nothing was pushed to a real remote and nothing was published anywhere. In the output, <lab> stands for the scratch directory, commit and tag object ids are the ones of the run, the nested build that the plugin forks is shown only where said, and lines were filtered with grep (said each time). Publishing to Maven Central is on Publishing to Maven Central and is "not run here".

A release is the moment a version stops changing. The 1.0.0-SNAPSHOT that everybody has been building becomes 1.0.0, which is tagged in the source repository, built once from that tag, and deployed to a repository where it must never change again; the development line moves on to 1.1.0-SNAPSHOT. There are two common ways to do it with Maven. The Release Plugin automates the sequence by editing the version in the POMs and committing; the tag-driven way keeps the version out of the commits and lets a pipeline build whatever a tag names. This page runs both. The deploy that ends a release is on Install, Deploy & Distribution Management; the version syntax is on Versions, Snapshots & Ranges; the scenario is on the section landing page.

The Release Plugin in two steps

The plugin splits a release into two goals so that a failure in the second does not undo the first.

  • release:prepare makes the source ready: it checks the working copy, changes the version, builds and tests, commits, tags, moves to the next snapshot and commits again.

  • release:perform makes the binaries: it checks out the tag that prepare created into a clean directory and runs a deploy there, so what is published was built from the tagged source and not from whatever was lying in the developer’s working copy.

sequenceDiagram participant Dev as Developer participant Prep as release:prepare participant Git as Git remote participant Perf as release:perform participant Repo as Deploy repository Dev->>Prep: mvn release:prepare Prep->>Prep: check clean tree and no SNAPSHOT dependencies Prep->>Prep: set 1.0.0 then run clean verify Prep->>Git: commit prepare release, tag v1.0.0, push Prep->>Prep: set 1.1.0-SNAPSHOT Prep->>Git: commit prepare for next development iteration, push Dev->>Perf: mvn release:perform Perf->>Git: check out tag v1.0.0 into target/checkout Perf->>Repo: mvn deploy in the checkout

The diagram shows what each goal does and where it talks to the outside: only prepare pushes to the source repository, only perform uploads to the artifact repository.

The lab

The lab is the tidepool parent with three modules, the distributionManagement of the deploy page (the two repositories are directories below -Drepo.url), and these additions to the parent POM. The plugin is pinned in pluginManagement, because the 3.10 super POM no longer manages maven-release-plugin (the 3.9.x super POM pinned it to 3.0.1):

pom.xml (parent, abridged)
  <version>1.0.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <scm>
    <connection>scm:git:file://<lab>/remote.git</connection>
    <developerConnection>scm:git:file://<lab>/remote.git</developerConnection>
    <tag>HEAD</tag>
  </scm>

  <build>
    <pluginManagement>
      <plugins>
        <plugin><artifactId>maven-release-plugin</artifactId><version>3.3.1</version></plugin>
        <!-- clean, resources, compiler, surefire, jar, install, deploy as before -->
      </plugins>
    </pluginManagement>
  </build>

and a .gitignore with the files the plugin creates: target/, build/, release.properties, pom.xml.releaseBackup and pom.xml.tag. The project was committed to the working repository (git init), a bare repository was created beside it (git init --bare), and the working copy pushed main to it. The URL is a file: URL because it is the form that works for a local bare repository; real projects use https: or ssh: URLs (not run here). This POM is this page’s own lab setup, not an example from the Maven documentation.

The scm section

The <scm> section tells the plugin, and humans, where the source is:

Element Meaning

connection

The URL for reading the source, in the Maven SCM form scm:<provider>:<provider-specific URL>, here scm:git: followed by a Git URL.

developerConnection

The URL for writing: by convention the one with credentials. In the lab both elements held the same file: URL, and release.properties recorded it as scm.url. The plugin accepts a project that has either one (its error for a project with neither is quoted below).

url

A web URL for browsing the source.

tag

The tag or branch the POM was built from. The convention is HEAD during development; the plugin writes the real tag into the released POM (shown below).

The plugin works through Apache Maven SCM, which has providers for Git, Subversion, Mercurial and others; the Git provider shells out to the git command, which must therefore be installed and configured (user, signing).

A trap with multi-module projects is that the children inherit the scm URLs with their artifact id appended to the path, which suits a repository with one directory per module. For a Git repository, where the whole multi-module project lives in one repository, the inherited URL is wrong. help:evaluate on the child:

scm:git:file://<lab>/remote.git/tidepool-model

The attributes child.scm.connection.inherit.append.path="false" and child.scm.developerConnection.inherit.append.path="false" on the <scm> element turn it off, and the child got the right URL:

  <scm child.scm.connection.inherit.append.path="false" child.scm.developerConnection.inherit.append.path="false">
scm:git:file://<lab>/remote.git

release:prepare

A dry run first

mvn -B release:prepare -DdryRun=true runs every phase without committing or tagging: it modifies nothing in the source repository and writes the modified POMs beside the originals. The plugin composes prepare of 17 phases, in this order. The output (grep for the numbered phase lines, the "Full run would" lines and the build result; the build the plugin forks for clean verify is not shown) was:

[INFO] starting prepare goal in dry-run mode, composed of 17 phases: [...]
[INFO] 1/17 prepare:check-poms dry-run
[INFO] 2/17 prepare:scm-check-modifications dry-run
[INFO] 3/17 prepare:check-dependency-snapshots dry-run
[INFO] 4/17 prepare:create-backup-poms dry-run
[INFO] 5/17 prepare:map-release-versions dry-run
[INFO] 6/17 prepare:input-variables dry-run
[INFO] 7/17 prepare:map-development-versions dry-run
[INFO] 8/17 prepare:rewrite-poms-for-release dry-run
[INFO] 9/17 prepare:generate-release-poms dry-run
[INFO] 10/17 prepare:run-preparation-goals dry-run
[INFO] Executing preparation goals - since this is simulation mode it is running against the original project, not the rewritten ones
[INFO] Executing goals 'clean verify'...
[INFO] 11/17 prepare:scm-commit-release dry-run
[INFO] Full run would be commit 4 files with message: '[maven-release-plugin] prepare release tidepool-parent-1.0.0'
[INFO] 12/17 prepare:scm-tag dry-run
[INFO] Full run would tag working copy '<lab>/rl'
[INFO] 13/17 prepare:rewrite-poms-for-development dry-run
[INFO] 14/17 prepare:remove-release-poms dry-run
[INFO] 15/17 prepare:run-completion-goals dry-run
[INFO] Executing completion goals - since this is simulation mode it is running against the original project, not the rewritten ones
[INFO] 16/17 prepare:scm-commit-development dry-run
[INFO] Full run would commit 4 files with message: '[maven-release-plugin] prepare for next development iteration'
[INFO] 17/17 prepare:end-release dry-run
[INFO] Release preparation simulation complete.
[INFO] BUILD SUCCESS

What each phase does, from the numbered lines above, the plugin’s usage page and the runs on this page:

# Phase What happens

1

check-poms

The POMs are checked for what a release needs, such as the scm section (a project without one fails here, see below).

2

scm-check-modifications

The working copy must have no local changes. The plugin ignores its own files: the log says ignoring changes on: **/pom.xml.releaseBackup, **/pom.xml.next, **/pom.xml.tag, **/pom.xml.branch, **/release.properties, **/pom.xml.backup.

3

check-dependency-snapshots

No dependency, plugin, report or extension may be a snapshot.

4

create-backup-poms

Each pom.xml is copied to pom.xml.releaseBackup, which release:rollback uses.

5-7

map-release-versions, input-variables, map-development-versions

The release version, the tag and the next development version are decided, from the command line, release.properties or the version policy, or by asking.

8

rewrite-poms-for-release

The POMs are rewritten with the release version (in a dry run, to pom.xml.tag).

9

generate-release-poms

Only for release:prepare-with-pom; "Not generating release POMs" otherwise.

10

run-preparation-goals

The preparationGoals (default clean verify) run against the release POMs. If this fails, nothing has been committed.

11

scm-commit-release

The release POMs are committed and, by default, pushed.

12

scm-tag

The tag is created (and pushed).

13

rewrite-poms-for-development

The POMs are rewritten with the next snapshot version.

14

remove-release-poms

Removes the release POMs that prepare-with-pom generates ("Not removing release POMs" otherwise).

15

run-completion-goals

The completionGoals, none by default.

16

scm-commit-development

The development POMs are committed and pushed.

17

end-release

release.properties is marked complete.

After the dry run the working directory had these files, and Git saw only the .next copies (the others are in the .gitignore):

data
pom.xml
pom.xml.next
pom.xml.releaseBackup
pom.xml.tag
release.properties
tidepool-cli
tidepool-engine
tidepool-model
?? pom.xml.next
?? tidepool-cli/pom.xml.next
?? tidepool-engine/pom.xml.next
?? tidepool-model/pom.xml.next

pom.xml.tag is the POM that would be tagged. Its differences from pom.xml (git diff --no-index --unified=1, header removed):

@@ -1,5 +1,3 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
@@ -8,3 +6,3 @@
   <artifactId>tidepool-parent</artifactId>
-  <version>1.0.0-SNAPSHOT</version>
+  <version>1.0.0</version>
   <packaging>pom</packaging>
@@ -20,3 +18,3 @@
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <project.build.outputTimestamp>2026-10-16T00:00:00Z</project.build.outputTimestamp>
+    <project.build.outputTimestamp>2026-10-16T14:33:21Z</project.build.outputTimestamp>
     <junit.version>6.1.3</junit.version>
@@ -59,3 +57,3 @@
     <developerConnection>scm:git:file://<lab>/remote.git</developerConnection>
-    <tag>HEAD</tag>
+    <tag>tidepool-parent-1.0.0</tag>
   </scm>

Four things are in it. The version is the release version. The <tag> of the scm section holds the name of the tag. The property project.build.outputTimestamp (the reproducible-build timestamp that the archive plugins write into entries, see Security & Reproducible Builds and the 3.10 notes on Versions & Release History) was updated to the time of the release run (here 2026-10-16T14:33:21Z), where it had been fixed at midnight in the POM. And the root <project> element, which was on four lines, was written on one: the plugin re-serializes the POM, so a release commit contains formatting noise as well as the version change (shown below).

release.properties is the memory of the run. The lines that matter (grep):

completedPhase=end-release
preparationGoals=clean verify
project.dev.io.tidepool\:tidepool-parent=1.0.1-SNAPSHOT
project.rel.io.tidepool\:tidepool-parent=1.0.0
projectVersionPolicyId=default
pushChanges=true
remoteTagging=true
scm.developmentCommitComment=@{prefix} prepare for next development iteration
scm.releaseCommitComment=@{prefix} prepare release @{releaseLabel}
scm.tag=tidepool-parent-1.0.0
scm.tagNameFormat=@{project.artifactId}-@{project.version}

It records the versions decided so far, the tag, the commit messages, and the phase that was last completed (completedPhase). The file is what lets an interrupted release resume, and what release:perform reads. mvn release:clean removes the dry run’s files:

[INFO] --- release:3.3.1:clean (default-cli) @ tidepool-parent ---
[INFO] BUILD SUCCESS

Choosing the versions

Without options the plugin asks three questions, with defaults from the version policy (it drops -SNAPSHOT and then increments the last number: 1.0.0 and 1.0.1-SNAPSHOT for the lab). Run without -B and with its standard input fed from a pipe, the plugin printed these questions and took the defaults (the answers I piped in were not used, so no interactive answer is shown on this page):

What is the release version for "tidepool-parent"? (tidepool-parent) 1.0.0:
What is the SCM release tag or label for "tidepool-parent"? (tidepool-parent) tidepool-parent-1.0.0:
What is the new development version for "tidepool-parent"? (tidepool-parent) 1.0.1-SNAPSHOT:

In a pipeline, nobody can answer, so use batch mode (-B) and give the values on the command line: -DreleaseVersion, -DdevelopmentVersion, -Dtag for the exact tag, or -DtagNameFormat for the pattern. The pattern uses @{...} and not ${...}, so that Maven does not interpolate it before the plugin has the release version. The default is @{project.artifactId}-@{project.version}, which gave tidepool-parent-1.0.0; most teams prefer v@{project.version}. For a multi-module project each module is asked its own version unless -DautoVersionSubmodules=true. The same options can live in the POM:

pom.xml (the plugin entry in pluginManagement, in a copy of the lab)
        <plugin>
          <artifactId>maven-release-plugin</artifactId>
          <version>3.3.1</version>
          <configuration>
            <tagNameFormat>v@{project.version}</tagNameFormat>
            <autoVersionSubmodules>true</autoVersionSubmodules>
            <scmCommentPrefix>[release] </scmCommentPrefix>
            <preparationGoals>clean verify</preparationGoals>
            <goals>deploy</goals>
            <arguments>-DskipTests</arguments>
          </configuration>
        </plugin>

A dry run with that configuration (grep for the lines that show the effect) printed:

[INFO] Executing goals 'clean verify'...
[INFO]     with additional arguments: -DskipTests
[INFO] Full run would be commit 4 files with message: '[release] prepare release v1.0.0'
[INFO] Full run would tag working copy '<lab>/t_cfg'
[INFO]   with label 'v1.0.0'
[INFO] Full run would commit 4 files with message: '[release] prepare for next development iteration'
[INFO] BUILD SUCCESS

The commit messages carry the scmCommentPrefix, the tag follows the pattern, and the extra arguments reach the forked build.

The real run

The release of the lab (mvn -B release:prepare -DreleaseVersion=1.0.0 -DdevelopmentVersion=1.1.0-SNAPSHOT -DtagNameFormat=v@{project.version}) printed, after grep for the phase lines and results:

[INFO] 1/17 prepare:check-poms
[INFO] 2/17 prepare:scm-check-modifications
[INFO] 3/17 prepare:check-dependency-snapshots
[INFO] 4/17 prepare:create-backup-poms
[INFO] 5/17 prepare:map-release-versions
[INFO] 6/17 prepare:input-variables
[INFO] 7/17 prepare:map-development-versions
[INFO] 8/17 prepare:rewrite-poms-for-release
[INFO] 9/17 prepare:generate-release-poms
[INFO] 10/17 prepare:run-preparation-goals
[INFO] Executing goals 'clean verify'...
[INFO] 11/17 prepare:scm-commit-release
[INFO] Checking in modified POMs...
[INFO] 12/17 prepare:scm-tag
[INFO] Tagging release with the label v1.0.0...
[INFO] 13/17 prepare:rewrite-poms-for-development
[INFO] 14/17 prepare:remove-release-poms
[INFO] 15/17 prepare:run-completion-goals
[INFO] 16/17 prepare:scm-commit-development
[INFO] Checking in modified POMs...
[INFO] 17/17 prepare:end-release
[INFO] Release preparation complete.
[INFO] BUILD SUCCESS

The working copy had two new commits and a tag (git log --oneline --decorate), and the remote had the same (git ls-remote origin, tag objects included):

3dc3f5b (HEAD -> main) [maven-release-plugin] prepare for next development iteration
a3c3e3d (tag: v1.0.0) [maven-release-plugin] prepare release v1.0.0
5631a64 (origin/main) tidepool 1.0.0-SNAPSHOT
3dc3f5b75b57e64cbd95a1cfda65002d76f71e11 HEAD
3dc3f5b75b57e64cbd95a1cfda65002d76f71e11 refs/heads/main
7dbc61a156781de9deab4aba7ab5b8ef37cdc9b5 refs/tags/v1.0.0
a3c3e3db345af7e0b9d20693ac7e4731e5b4fead refs/tags/v1.0.0^{}

main of the remote moved to the second commit, and the tag exists as an annotated tag (refs/tags/v1.0.0, with ^{} the commit it points to). The tag object:

tag v1.0.0
tagger <name> <email> <time>

[maven-release-plugin] copy for tag v1.0.0

The two commits (git show --stat) touched all four POMs; the release commit is bigger than the development one because it re-serializes the POMs:

a3c3e3d [maven-release-plugin] prepare release v1.0.0
 pom.xml                 | 10 ++++------
 tidepool-cli/pom.xml    |  6 ++----
 tidepool-engine/pom.xml |  6 ++----
 tidepool-model/pom.xml  |  6 ++----
 4 files changed, 10 insertions(+), 18 deletions(-)
3dc3f5b [maven-release-plugin] prepare for next development iteration
 pom.xml                 | 6 +++---
 tidepool-cli/pom.xml    | 2 +-
 tidepool-engine/pom.xml | 2 +-
 tidepool-model/pom.xml  | 2 +-
 4 files changed, 6 insertions(+), 6 deletions(-)

The POMs at the tagged commit (git show v1.0.0^{commit}:pom.xml, parent abridged) have the release version, a fixed reproducible timestamp, and a <tag> that names the tag:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>io.tidepool</groupId>
  <artifactId>tidepool-parent</artifactId>
  <version>1.0.0</version>

  <scm>
    <connection>scm:git:file://<lab>/remote.git</connection>
    <developerConnection>scm:git:file://<lab>/remote.git</developerConnection>
    <tag>v1.0.0</tag>
  </scm>

and a module at the tag points at the released parent:

  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>io.tidepool</groupId>
    <artifactId>tidepool-parent</artifactId>
    <version>1.0.0</version>
  </parent>
  <artifactId>tidepool-engine</artifactId>

The development commit moved on to the next snapshot and put HEAD back (git diff between the two commits, changed lines only):

-  <version>1.0.0</version>
+  <version>1.1.0-SNAPSHOT</version>
-    <project.build.outputTimestamp>2026-10-16T14:33:26Z</project.build.outputTimestamp>
+    <project.build.outputTimestamp>2026-10-16T14:33:28Z</project.build.outputTimestamp>
-    <tag>v1.0.0</tag>
+    <tag>HEAD</tag>

After prepare the tree is clean for Git, and release.properties and pom.xml.releaseBackup are left for the next goal:

data
pom.xml
pom.xml.releaseBackup
release.properties
tidepool-cli
tidepool-engine
tidepool-model

Because pushChanges defaults to true and remoteTagging to true, the commits and the tag were pushed by prepare itself; with -DpushChanges=false they stay local until somebody pushes (not run here).

The parameters that matter

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

Parameter User property Meaning

releaseVersion, developmentVersion

the same

The versions to use; otherwise the version policy proposes them.

tag, tagNameFormat

tag, tagNameFormat

The exact tag, or the pattern (default @{project.artifactId}-@{project.version}).

autoVersionSubmodules

autoVersionSubmodules

Give every module the version of the parent without asking (default false).

dryRun

dryRun

Do not commit or tag; write the modified POMs beside the originals.

resume

resume

Resume the previous attempt from release.properties (default true).

preparationGoals, completionGoals, preparationProfiles

the same

Goals (default clean verify) and profiles for the build against the release POMs; goals to run after the move to the next snapshot.

arguments

arguments

Arguments for the forked Maven builds (see -Darguments below).

pushChanges, remoteTagging

the same

Push the commits (default true); create the tag in the remote (default true).

scmCommentPrefix, scmReleaseCommitComment, scmDevelopmentCommitComment

the same

The commit messages (@{prefix}, @{releaseLabel}).

signTag

signTag

Sign the tag. Not run here.

projectVersionPolicyId, projectTagNamingPolicyId

the same

A version policy (default default) or a tag naming policy, as plugin extensions.

autoResolveSnapshots

autoResolveSnapshots

Resolve snapshot dependencies automatically (all, dependencies, plugins, reports, extensions).

generateReleasePoms

generateReleasePoms

Only the goal prepare-with-pom sets it.

checkModificationExcludes

checkModificationExcludes

More files to ignore in the modification check.

allowTimestampedSnapshots

allowTimestampedSnapshots

Allow timestamped snapshot dependencies (default false).

release:perform

perform reads release.properties, checks out the tag into target/checkout and runs deploy there in a forked Maven. In a real release the deploy repository comes from the distributionManagement in the tagged POM and the credentials from settings.xml. The lab has to tell the forked build where its repository is, and this is the classic stumbling block: a -D option on the outer command line does not reach the forked build. They go in the arguments parameter. To see it, the same release was performed twice more, once with no option, once with -Drepo.url=file://<lab>/repo_pa_b on the outer command line only. Both uploaded into the checkout, where the POM’s own default points (file://${maven.multiModuleProjectDirectory}/build/repo in the checkout), and where the next mvn clean or a new checkout deletes them:

[INFO] Uploaded to tidepool-releases: file://<lab>/t_pa_a/target/checkout/build/repo/releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar
[INFO] BUILD SUCCESS
[INFO] Uploaded to tidepool-releases: file://<lab>/t_pa_b/target/checkout/build/repo/releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar
[INFO] BUILD SUCCESS

The right form passes the option to the fork:

mvn -B release:perform -Darguments="-Drepo.url=file://<lab>/repo"

The output of that run (filtered with grep for the plugin’s lines):

[INFO] starting perform goal, composed of 3 phases: [...]
[INFO] 1/3 perform:verify-completed-prepare-phases
[INFO] 2/3 perform:checkout-project-from-scm
[INFO] Checking out the project to perform the release ...
[INFO] 3/3 perform:run-perform-goals
[INFO] Invoking perform goals in directory <lab>/rl/target/checkout
[INFO] Executing goals 'deploy'...
[INFO] BUILD SUCCESS

and the nested build’s deploy lines (grep -E "deploy:|Uploaded to .*(jar|pom) "):

[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to tidepool-releases: file://<lab>/repo/releases/io/tidepool/tidepool-parent/1.0.0/tidepool-parent-1.0.0.pom
[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to tidepool-releases: file://<lab>/repo/releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom
[INFO] Uploaded to tidepool-releases: file://<lab>/repo/releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar
[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to tidepool-releases: file://<lab>/repo/releases/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
[INFO] Uploaded to tidepool-releases: file://<lab>/repo/releases/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
[INFO] --- deploy:3.2.0:deploy
[INFO] Uploaded to tidepool-releases: file://<lab>/repo/releases/io/tidepool/tidepool-cli/1.0.0/tidepool-cli-1.0.0.pom
[INFO] Uploaded to tidepool-releases: file://<lab>/repo/releases/io/tidepool/tidepool-cli/1.0.0/tidepool-cli-1.0.0.jar

The repository contained every module at 1.0.0 and nothing at 1.0.0-SNAPSHOT (find . -type f without the checksum files):

./releases/io/tidepool/tidepool-cli/1.0.0/tidepool-cli-1.0.0.jar
./releases/io/tidepool/tidepool-cli/1.0.0/tidepool-cli-1.0.0.pom
./releases/io/tidepool/tidepool-cli/maven-metadata.xml
./releases/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
./releases/io/tidepool/tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
./releases/io/tidepool/tidepool-engine/maven-metadata.xml
./releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.jar
./releases/io/tidepool/tidepool-model/1.0.0/tidepool-model-1.0.0.pom
./releases/io/tidepool/tidepool-model/maven-metadata.xml
./releases/io/tidepool/tidepool-parent/1.0.0/tidepool-parent-1.0.0.pom
./releases/io/tidepool/tidepool-parent/maven-metadata.xml

The working directory of the plugin, target/checkout, is a full checkout of the tag. After a successful perform the files of prepare are removed, so a second perform has nothing to read (the working copy listing, ls and git status --short, which printed nothing):

data
pom.xml
target
tidepool-cli
tidepool-engine
tidepool-model

The release is complete when the artifacts are in the repository: the tag is in the source repository and the version is final. The deploy repository decides whether the same version can be uploaded again, and normally it refuses (see the deploy page).

The parameters of perform

Parameter User property Meaning

goals

goals

The goals to run in the checkout. The default is deploy, or deploy site-deploy when the project has a distributionManagement/site; the usage page of the plugin lists deploy site-deploy as the default.

arguments

arguments

Arguments for the forked build, separated by spaces.

releaseProfiles

releaseProfiles

Profiles to enable in the forked build, in addition to the active ones.

useReleaseProfile

useReleaseProfile

Default false and deprecated; sets performRelease to activate the super POM’s release-profile (below).

connectionUrl

connectionUrl

Where to check out, if there is no release.properties. Not run here.

localCheckout

localCheckout

Use a local checkout instead of cloning; works with distributed SCMs such as Git. Not run here.

workingDirectory

workingDirectory

The checkout directory (default target/checkout).

dryRun

dryRun

Do not check out or run the goals.

The release profile that Maven 3.10 removed

Older documentation says that a release adds source and Javadoc JARs "automatically". That worked through a profile called release-profile in the super POM that the plugin activated by setting the property performRelease (with useReleaseProfile). Maven 3.10 removed it. With -DperformRelease=true set, help:active-profiles on the lab (the profile list only):

--- Maven 3.9.16
The following profiles are active:
 - release-profile (source: )
--- Maven 3.10.0
The following profiles are active:
--- Maven 4.0.0-rc-7
The following profiles are active:

and mvn -B -DperformRelease=true -pl tidepool-model clean package on a POM that has the Source and Javadoc plugins pinned in pluginManagement but not bound (executions only, filtered with grep -E "^\[INFO\] --- |Building jar|BUILD" and the plugin id and execution id kept):

--- Maven 3.9.16
[INFO] --- clean (default-clean) ...
[INFO] --- resources (default-resources) ...
[INFO] --- compiler (default-compile) ...
[INFO] --- resources (default-testResources) ...
[INFO] --- compiler (default-testCompile) ...
[INFO] --- surefire (default-test) ...
[INFO] --- jar (default-jar) ...
[INFO] Building jar: tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT.jar
[INFO] --- source (attach-sources) ...
[INFO] Building jar: tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT-sources.jar
[INFO] --- javadoc (attach-javadocs) ...
[INFO] Building jar: tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT-javadoc.jar
[INFO] BUILD SUCCESS
--- Maven 3.10.0
[INFO] --- clean (default-clean) ...
[INFO] --- resources (default-resources) ...
[INFO] --- compiler (default-compile) ...
[INFO] --- resources (default-testResources) ...
[INFO] --- compiler (default-testCompile) ...
[INFO] --- surefire (default-test) ...
[INFO] --- jar (default-jar) ...
[INFO] Building jar: tidepool-model/target/tidepool-model-1.0.0-SNAPSHOT.jar
[INFO] BUILD SUCCESS

On 3.9.16 the profile ran the Source and Javadoc plugins (attach-sources, attach-javadocs, the execution ids of the profile) and built -sources.jar and -javadoc.jar; on 3.10.0 nothing did. A release that relied on the old profile silently loses its sources and Javadoc JARs after an upgrade to 3.10. The fix is to bind the plugins in your own POM, as on the deploy page, so that every build, not only the releases, makes them. (Central requires both, see Publishing to Maven Central.)

The other goals

Goal What it does

release:clean

Cleans up the working copy after a failed or abandoned release or a dry run (it happens by itself after a successful perform). It rolls nothing back: commits and tags stay.

release:rollback

Restores the POMs from the backups, commits that, and removes the tag. Needs the files that release:clean removes.

release:update-versions

Only changes the versions in the POMs (prompting or from -DdevelopmentVersion); no commit, no tag.

release:branch

Creates a branch from the current state, optionally with its own versions.

release:prepare-with-pom

Like prepare, but also writes a release-pom.xml per project with everything resolved.

release:stage

Like perform, to a staging repository; it replaces site-deploy by site:stage-deploy. Not run here.

rollback

After a clean prepare of 1.0.0 (-DtagNameFormat=v@{project.version}) the lab had the tag in the remote and two release commits:

<id> refs/tags/v1.0.0
6337c66 (HEAD -> main) [maven-release-plugin] prepare for next development iteration
e172fa3 (tag: v1.0.0) [maven-release-plugin] prepare release v1.0.0
5631a64 (origin/main, origin/HEAD) tidepool 1.0.0-SNAPSHOT

mvn -B release:rollback printed (grep):

[INFO] 1/3 rollback:restore-backup-poms
[INFO] 2/3 rollback:scm-commit-rollback
[INFO] 3/3 rollback:remove-scm-tag
[INFO] BUILD SUCCESS

and left the repository in this state:

2f09d6d (HEAD -> main) [maven-release-plugin] rollback the release of v1.0.0
6337c66 [maven-release-plugin] prepare for next development iteration
e172fa3 [maven-release-plugin] prepare release v1.0.0
5631a64 (origin/main, origin/HEAD) tidepool 1.0.0-SNAPSHOT

(The remote tag list is empty: the tag was removed from the remote as well. The version in the working copy was 1.0.0-SNAPSHOT again, and the extra files were gone.)

  <version>1.0.0-SNAPSHOT</version>
data
pom.xml
tidepool-cli
tidepool-engine
tidepool-model

Note what rollback does not do: it does not rewrite history. The release commit and the development commit stay, and a third commit ("rollback the release of v1.0.0") restores the old POMs. The commits were already pushed, so they stay in the remote too. Plan for that: a rolled-back release leaves three commits of noise. The usage page says that tag removal is available since 3.0.0-M1.

update-versions and branch

release:update-versions is for bumping the version without a release, for example after cutting a maintenance branch. Run in the tag-driven clone below:

$ mvn release:update-versions -DdevelopmentVersion=1.4.0-SNAPSHOT
[INFO] 1/4 updateVersions:check-poms-updateversions
[INFO] 2/4 updateVersions:create-backup-poms
[INFO] 3/4 updateVersions:map-development-versions
[INFO] 4/4 updateVersions:rewrite-pom-versions
[INFO] BUILD SUCCESS
$ git diff --stat
 pom.xml                 | 8 +++-----
 tidepool-cli/pom.xml    | 6 ++----
 tidepool-engine/pom.xml | 6 ++----
 tidepool-model/pom.xml  | 6 ++----
 4 files changed, 9 insertions(+), 17 deletions(-)

The 9 insertions and 17 deletions come from the same re-serialization of the root element as in the release commit. release:branch creates a branch in the remote with the versions you choose, commits there, and (here with -DupdateWorkingCopyVersions=false) leaves the working copy on the branch it started from:

$ mvn release:branch -DbranchName=release-1.3.x -DupdateBranchVersions=true -DreleaseVersion=1.3.1-SNAPSHOT -DupdateWorkingCopyVersions=false
[INFO] 1/12 branch:check-poms
[INFO] 2/12 branch:scm-check-modifications
[INFO] 3/12 branch:create-backup-poms
[INFO] 4/12 branch:map-branch-versions
[INFO] 5/12 branch:branch-input-variables
[INFO] 6/12 branch:map-development-versions
[INFO] 7/12 branch:rewrite-poms-for-branch
[INFO] 8/12 branch:scm-commit-branch
[INFO] 9/12 branch:scm-branch
[INFO] 10/12 branch:rewrite-poms-for-development
[INFO] 11/12 branch:scm-commit-development
[INFO] 12/12 branch:end-release
[INFO] BUILD SUCCESS
$ git ls-remote --heads origin
<id> refs/heads/main
<id> refs/heads/release-1.3.x
$ git show release-1.3.x:pom.xml | grep -m1 "<version>"
  <version>1.3.1-SNAPSHOT</version>

When it goes wrong

A release has many parts, and the failures are usually at the seams. The messages below are those of real runs.

Local modifications

An uncommitted edit stops prepare in scm-check-modifications:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-release-plugin:3.3.1:prepare (default-cli) on project tidepool-parent: Cannot prepare the release because you have local modifications :
[ERROR] [tidepool-model/src/main/java/io/tidepool/model/Height.java:modified]
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

The release descriptor is left behind, and remembers where it stopped; after the file was reverted, git status printed nothing (empty listing) and release.properties held:

completedPhase=check-poms

The stale release.properties and resume

Because of that file, running the goal again resumes: the next run (a dry run) printed that it resumed and skipped the phase that had completed:

[INFO] Resuming release from phase 'scm-check-modifications'
[INFO] 2/17 prepare:scm-check-modifications dry-run
[INFO] 3/17 prepare:check-dependency-snapshots dry-run

-Dresume=false (or release:clean first) starts again from phase 1:

[INFO] 1/17 prepare:check-poms dry-run
[INFO] 2/17 prepare:scm-check-modifications dry-run

The classic problem is a release.properties from an earlier, abandoned attempt: it remembers the old release version, tag name and phase, and the new attempt silently reuses them. The rule is to run release:clean (or rollback) before starting over, and to look at release.properties when a release does something unexpected.

Snapshot dependencies

A release must be reproducible, so every dependency must be a release too. A dependency on io.tidepool:snap-lib:2.0-SNAPSHOT (installed into the scratch local repository with install:install-file) in tidepool-cli failed check-dependency-snapshots:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-release-plugin:3.3.1:prepare (default-cli) on project tidepool-parent: Can't release project due to non released dependencies :
[ERROR]     io.tidepool:snap-lib:jar:2.0-SNAPSHOT:compile
[ERROR] in project 'tidepool-cli' (io.tidepool:tidepool-cli:jar:1.0.0-SNAPSHOT)

With -DautoResolveSnapshots=dependencies the plugin offers to replace them with the latest release instead of failing. The proper fix is to release the dependency first.

No scm section

A project without <scm> fails in the first phase, check-poms, before anything is written (no release.properties was created):

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-release-plugin:3.3.1:prepare (default-cli) on project tidepool-parent: Missing required setting: scm connection or developerConnection must be specified. -> [Help 1]

A tag that already exists

The most dangerous failure. A tag v1.0.0 was left in the repository by an earlier attempt (git tag -a v1.0.0 and git push origin v1.0.0, simulating an abandoned release), and the new prepare -DreleaseVersion=1.0.0 -DtagNameFormat=v@{project.version} was run:

<id> refs/tags/v1.0.0
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-release-plugin:3.3.1:prepare (default-cli) on project tidepool-parent: Unable to tag SCM
[ERROR] Provider message:
[ERROR] The git-tag command failed.
[ERROR] Command output:
[ERROR] fatal: tag 'v1.0.0' already exists

The commit step happens before the tag step, and it pushes. So the run failed after it had already committed and pushed the release commit:

8012334 (HEAD -> main) [maven-release-plugin] prepare release v1.0.0
5631a64 (tag: v1.0.0, origin/main, origin/HEAD) tidepool 1.0.0-SNAPSHOT
8012334 [maven-release-plugin] prepare release v1.0.0
5631a64 tidepool 1.0.0-SNAPSHOT

The working directory was left with the descriptor and the backups:

data
pom.xml
pom.xml.releaseBackup
release.properties
tidepool-cli
tidepool-engine
tidepool-model

The tempting way out is release:rollback. It restored the POMs in a new commit (pushed) and then did the third thing it does, it removed the tag named v1.0.0, which was the stale tag that was not made by this run (grep of the output, and the tags in the remote and the log of the remote afterwards):

[INFO] 1/3 rollback:restore-backup-poms
[INFO] 2/3 rollback:scm-commit-rollback
[INFO] 3/3 rollback:remove-scm-tag
[INFO] BUILD SUCCESS
a215985 [maven-release-plugin] rollback the release of v1.0.0
8012334 [maven-release-plugin] prepare release v1.0.0
5631a64 tidepool 1.0.0-SNAPSHOT

The remote tag list is empty: the tag that existed before is gone (and the local list too). If that tag had pointed at a real, published release, it would have been deleted by the rollback of an attempt to repeat it. So: pick a version that has no tag (git tag -l and git ls-remote --tags origin first), and read what a rollback is going to remove.

A push that the remote rejects

If somebody else pushed to main after the working copy was cloned, the push in the commit phase is rejected. In a stale clone of the lab, after another clone had pushed a commit, prepare failed (the grep of the error lines, and the two logs):

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-release-plugin:3.3.1:prepare (default-cli) on project tidepool-parent: Unable to commit files
[ERROR] Provider message:
[ERROR] The git-push command failed.
[ERROR] Command output:
[ERROR] To file://<lab>/remote.git
[ERROR]  ! [rejected]        main -> main (fetch first)
[ERROR] hint: Updates were rejected because the remote contains work that you do not
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.

The local release commit already exists and the remote has the other commit:

d130c37 (HEAD -> main) [maven-release-plugin] prepare release tidepool-parent-1.0.0
5631a64 (origin/main, origin/HEAD) tidepool 1.0.0-SNAPSHOT
d06543d someone else pushes
5631a64 tidepool 1.0.0-SNAPSHOT

Pull (or rebase) the other work and then release:rollback, or reset the local release commit by hand and release:clean, before trying again; the descriptor remembers the half-finished attempt.

Failures that were not reproduced here

  • The authenticity of host ... can't be established and a build that hangs on an SSH remote, and a site-deploy that hangs: both are in the troubleshooting section of the official guide. An SSH remote was not run here.

  • Signing of commits or tags (signTag) failing in a CI job that has no key.

The alternative: tag-driven releases

The Release Plugin edits the POM to say 1.0.0, commits, tags and edits the POM again. Many teams stopped doing that. The alternative is that the tag is the release: a developer (or a pipeline) creates a tag; a CI job builds the tag and publishes the version the tag names, and nothing else is committed. Two ways to put the version into the build:

  • CI-friendly versions (${revision}, ${sha1}, ${changelist}): the POM keeps a default and the job passes -Drevision=1.2.0. This is all on CI-Friendly Versions, including the flatten plugin that Maven 3 needs to publish such a POM, and the shell script that lets a tag decide.

  • versions:set of the Versions Maven Plugin: a commit that sets the version in every POM, a tag on it, a deploy, and a commit that moves to the next snapshot. It is the manual version of release:prepare with the build in the middle. Run on a clone of the lab (grep for the plugin’s lines):

$ mvn versions:set -DnewVersion=1.2.0 -DgenerateBackupPoms=false
[INFO] Processing change of io.tidepool:tidepool-parent:1.0.0-SNAPSHOT -> 1.2.0
[INFO] Processing io.tidepool:tidepool-parent
[INFO]     Updating project io.tidepool:tidepool-parent
[INFO] Processing io.tidepool:tidepool-cli
[INFO]     Updating parent io.tidepool:tidepool-parent
[INFO] Processing io.tidepool:tidepool-engine
[INFO]     Updating parent io.tidepool:tidepool-parent
[INFO] Processing io.tidepool:tidepool-model
[INFO]     Updating parent io.tidepool:tidepool-parent
[INFO] BUILD SUCCESS
$ git diff --stat
 pom.xml                 | 4 ++--
 tidepool-cli/pom.xml    | 2 +-
 tidepool-engine/pom.xml | 2 +-
 tidepool-model/pom.xml  | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)
$ mvn deploy
[INFO] Uploaded to tidepool-releases: file://<lab>/repo_tag/releases/io/tidepool/tidepool-model/1.2.0/tidepool-model-1.2.0.jar
[INFO] Uploaded to tidepool-releases: file://<lab>/repo_tag/releases/io/tidepool/tidepool-engine/1.2.0/tidepool-engine-1.2.0.jar
[INFO] Uploaded to tidepool-releases: file://<lab>/repo_tag/releases/io/tidepool/tidepool-cli/1.2.0/tidepool-cli-1.2.0.jar
[INFO] BUILD SUCCESS
$ mvn versions:set -DnewVersion=1.3.0-SNAPSHOT -DgenerateBackupPoms=false
[INFO] BUILD SUCCESS
$ git log --oneline --decorate
42b4184 (HEAD -> main, origin/main, origin/HEAD) Start 1.3.0-SNAPSHOT
67bd957 (tag: v1.2.0) Release 1.2.0
5631a64 tidepool 1.0.0-SNAPSHOT
$ git ls-remote --tags origin
<id> refs/tags/v1.2.0

The release commit and the tag exist, the deploy has built from the tagged commit, and the repository received 1.2.0. The commit history is cleaner than the Release Plugin’s (no [maven-release-plugin] commits, no reformatted POM headers beyond what versions:set changes), at the cost of doing the sequence yourself; a script or a CI pipeline does that, and it also lets the build run before the tag is created. The trade-offs:

Release Plugin Tag-driven

Version in the POM

Edited and committed twice

Default in the POM, or set in one commit

Source of the release

perform checks out the tag and rebuilds

The CI job builds the tag, once

Reproducibility

outputTimestamp is rewritten into the release commit

outputTimestamp is whatever is set; make it a function of the tag

Rollback

release:rollback (leaves commits, removes the tag)

Delete the tag and revert the commit by hand; there is no plugin state to repair

Learning cost

Many phases, files and failure modes (above)

A script

A published release should be built once and promoted, not rebuilt, so that the bytes tested are the bytes released (see Publishing to Maven Central and Security & Reproducible Builds).

Maven 3.9, 3.10 and 4

The Release Plugin 3.3.1 was run on all three. A complete prepare and perform on Maven 3.9.16 (grep, in a fresh clone, defaults and the tag tidepool-parent-1.0.0):

--- Maven 3.9.16: release:prepare
[INFO] Tagging release with the label tidepool-parent-1.0.0...
[INFO] Release preparation complete.
[INFO] BUILD SUCCESS
--- Maven 3.9.16: release:perform
[INFO] [inner] BUILD SUCCESS
[INFO] BUILD SUCCESS
--- git log
312eaa1 (HEAD -> main) [maven-release-plugin] prepare for next development iteration
8b09dc6 (tag: tidepool-parent-1.0.0) [maven-release-plugin] prepare release tidepool-parent-1.0.0
5631a64 (origin/main, origin/HEAD) tidepool 1.0.0-SNAPSHOT
--- repository
tidepool-cli/1.0.0/tidepool-cli-1.0.0.jar
tidepool-cli/1.0.0/tidepool-cli-1.0.0.pom
tidepool-cli/maven-metadata.xml
tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
tidepool-engine/maven-metadata.xml
tidepool-model/1.0.0/tidepool-model-1.0.0.jar
tidepool-model/1.0.0/tidepool-model-1.0.0.pom
tidepool-model/maven-metadata.xml
tidepool-parent/1.0.0/tidepool-parent-1.0.0.pom
tidepool-parent/maven-metadata.xml

and on Maven 4.0.0-rc-7 (a release candidate):

--- Maven 4.0.0-rc-7: release:prepare
[WARNING] Unable to find the root directory. Create a .mvn directory in the root directory or add the root="true" attribute on the root project's model to identify it.
[INFO] Tagging release with the label tidepool-parent-1.0.0...
[INFO] Release preparation complete.
[INFO] BUILD SUCCESS
--- Maven 4.0.0-rc-7: release:perform
[INFO] [WARNING] The top project (<lab>/t_v4/target/checkout/pom.xml) cannot be found in the reactor from root project (<lab>/t_v4/pom.xml). Make sure the root directory is correct (a missing '.mvn' directory in the root project is the most common cause) and the project is correctly included in the
[INFO] [inner] BUILD SUCCESS
[INFO] BUILD SUCCESS
--- git log
72b27b5 (HEAD -> main) [maven-release-plugin] prepare for next development iteration
7866fbb (tag: tidepool-parent-1.0.0) [maven-release-plugin] prepare release tidepool-parent-1.0.0
5631a64 (origin/main, origin/HEAD) tidepool 1.0.0-SNAPSHOT
--- repository
tidepool-cli/1.0.0/tidepool-cli-1.0.0-build.pom
tidepool-cli/1.0.0/tidepool-cli-1.0.0.jar
tidepool-cli/1.0.0/tidepool-cli-1.0.0.pom
tidepool-cli/maven-metadata.xml
tidepool-engine/1.0.0/tidepool-engine-1.0.0-build.pom
tidepool-engine/1.0.0/tidepool-engine-1.0.0.jar
tidepool-engine/1.0.0/tidepool-engine-1.0.0.pom
tidepool-engine/maven-metadata.xml
tidepool-model/1.0.0/tidepool-model-1.0.0-build.pom
tidepool-model/1.0.0/tidepool-model-1.0.0.jar
tidepool-model/1.0.0/tidepool-model-1.0.0.pom
tidepool-model/maven-metadata.xml
tidepool-parent/1.0.0/tidepool-parent-1.0.0-build.pom
tidepool-parent/1.0.0/tidepool-parent-1.0.0.pom
tidepool-parent/maven-metadata.xml

What differs:

  • On Maven 4 the plugin worked, with a warning about the missing root directory in both goals (the clone has no .mvn directory because Git does not track an empty directory, and the checkout under target/checkout is a project of its own). Add a .mvn directory with a file in it, or the attribute root="true" on the root project, to Maven 4 builds (see Migrating to Maven 4).

  • On Maven 4 the deploy also published the build POM (-build.pom) beside the consumer POM of every module (the listing above); see the deploy page.

  • The release-profile of the super POM exists on 3.9.16 and not on 3.10.0 or 4.0.0-rc-7 (above).

  • The Release Plugin rewrites the project.build.outputTimestamp property at release time when the POM defines it, as the lab’s parent POM does.

  • The release plugin version is not managed by Maven 3.10 any more; pin it.

The same job in Ant and Gradle

Ant has no release concept: a release is a target that sets a property, tags with exec or the Git tasks and publishes with the Resolver Ant Tasks or Ivy (see Ivy: Publishing & Multi-Project Builds). Gradle reads the version from the build script or gradle.properties, and releasing is either a convention (a tag, the version passed with -P) or a third-party plugin; the publishing side is on Publishing. The comparison is on Repositories & Publishing Across Tools.

Where the books and old pages are outdated

  • Release guides written for Maven 2 and Subversion (the official "Releasing" mini guide still shows scm:svn: URLs, tagBase and plugin 2.5.2 in its examples) apply to Git with different properties: tagNameFormat and a developerConnection with scm:git:. A release.properties with checkpoint.* entries is from the old plugin; the 3.x file has completedPhase.

  • The release-profile and useReleaseProfile are gone or deprecated (3.10, above).

  • release:prepare is no longer the only way to release: tag-driven pipelines with CI-friendly versions are common.

  • Plugin versions: the Release Plugin 2.x is obsolete; use 3.x, and pin it.

What was not run here

  • A real remote (https: or ssh:) with credentials, signing of commits and tags, and CI jobs for Git hosts.

  • Publishing to Maven Central, release:stage, release:prepare-with-pom, -DlocalCheckout=true, -DconnectionUrl without a release.properties, -DpushChanges=false, and Subversion.

  • The interactive questions with answers (the questions were printed, the answers taken were the defaults).