Preparing the release with Maven

Table of Contents

The moment has finally come, all features implemented, bugs fixed - your application is ready to be published. You want to mark current code as production ready and prepare code base for next development iteration. But where to start? And what actually should be done? Manual release might be time-consuming and error-prone. Thankfully we have Maven and the Release plugin! In this post I will show how to perform all tasks required to configure and perform release using Maven Release plugin.

Preparing the release doesn't have to be painful :)

Before you start

It’s good idea to test functionalities of release plugin on new project. The reason is simple, plugin makes commits to the repository on your behalf which means that if you apply plugin on project which is already under development by more than one person it may be hard to undo the changes. You can also use dryRun option to check if everything works before actual release.

mvn release:prepare -DdryRun=true
....
[INFO] Release preparation simulation complete.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Example project

For demonstration purposes I will keep it as simple as possible. Generally project contains Maven configuration pom.xml and simple Java class which writes current version to standard output App.java.

Our project structure looks like this:

> tree
.
├── pom.xml (Maven configuration)
└── src
    └── main
        └── java
            └── codes
                └── hubertwo
                    └── maven
                        └── release
                            └── App.java (App which displays current version)

Release steps

Let’s start from short, but concrete description.

This plugin is used to release a project with Maven, saving a lot of repetitive, manual work. Releasing a project is made in two steps: prepare and perform.

From: https://maven.apache.org/maven-release/maven-release-plugin/index.html

So what does the repetitive, manual work actually mean? Let’s break it into few main steps:

  1. Removes the SNAPSHOT from current version ex. version 1.0-SNAPSHOT will be converted to 1.0 in pom.xml
  2. Commits and tags the version without SNAPSHOT - this is your release
  3. Updates the version for next development iteration ex. 1.0 will be converted to 2.0-SNAPSHOT in pom.xml
  4. Commits 2.0-SNAPSHOT changes and pushes changes to repository

The plugin also does some additional validation steps like checking if there are no uncommitted changes in repository, checking if there are not SNAPSHOT dependencies etc. For readability, I will skip them here, the main point is that if any of these validation steps fail, the whole release fails.

Configuring Release plugin

Let’s start from adding the plugin to Maven configuration pom.xml.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-release-plugin</artifactId>
    <version>3.0.0-M5</version>
</plugin>

Since the plugin works with VCS (Version Control System) we also need to configure this.

<scm>
    <developerConnection>scm:git:https://github.com/HubertWo/hubert-codes-maven-release</developerConnection>
</scm>

Release

I decided to divide the process to three parts. Before release, performing release and after the release. This should help to illustrate better what is actually going.

Before

Let’s build and run our simple application first.

> mvn package
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------< codes.hubertwo.maven.release:release-example >------------
[INFO] Building release-example 1.0-SNAPSHOT
...
[INFO] --- maven-shade-plugin:3.2.4:shade (default) @ release-example ---
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing target/release-example-1.0-SNAPSHOT.jar with target/release-example-1.0-SNAPSHOT-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
> java -jar target/release-example-1.0-SNAPSHOT.jar
Hello! Application release details:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven 3.8.2
Built-By: hubert
Build-Jdk: 17
Main-Class: codes.hubertwo.maven.release.App
Specification-Version: 1.0-SNAPSHOT

We can see that current version of the application is 1.0-SNAPSHOT.

Now is the time to check how the git log and git tags look like before release.

> git log --oneline
15ee7ce (HEAD -> main, origin/main) Pom.xml - added scm
fa799ea Release and shade plugin
3f13a67 Empty Maven project
> git tag

As we can see there’s no tags yet. The logs show some latest work in actual version. Now we are ready for performing the release.

Preparing the release

We will perform the release in manual mode for the readability purposes. However, you may read about batch mode to automate the whole process. In manual mode Maven will ask for few values during the execution. If you want to use values proposed by Maven just press ENTER.

# Example release question and default value '1.0'
What is the release version for "release-example"? (codes.hubertwo.maven.release:release-example) 1.0: :
> mvn release:prepare             
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------< codes.hubertwo.maven.release:release-example >------------
[INFO] Building release-example 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-release-plugin:3.0.0-M5:prepare (default-cli) @ release-example ---
[INFO] phase verify-release-configuration
[INFO] starting prepare goal, composed of 17 phases: check-poms, scm-check-modifications, check-dependency-snapshots, create-backup-poms, map-release-versions, input-variables, map-development-versions, rewrite-poms-for-release, generate-release-poms, run-preparation-goals, scm-commit-release, scm-tag, rewrite-poms-for-development, remove-release-poms, run-completion-goals, scm-commit-development, end-release
[INFO] [prepare] 1/17 check-poms
[INFO] [prepare] 2/17 scm-check-modifications
...
What is the release version for "release-example"? (codes.hubertwo.maven.release:release-example) 1.0: : 
[INFO] [prepare] 6/17 input-variables
What is the SCM release tag or label for "release-example"? (codes.hubertwo.maven.release:release-example) release-example-1.0: : 
[INFO] [prepare] 7/17 map-development-versions
What is the new development version for "release-example"? (codes.hubertwo.maven.release:release-example) 1.1-SNAPSHOT: : 
[INFO] [prepare] 8/17 rewrite-poms-for-release
[INFO] Transforming 'release-example'...
[INFO] [prepare] 9/17 generate-release-poms
[INFO] Not generating release POMs
[INFO] [prepare] 10/17 run-preparation-goals
[INFO] Executing goals 'clean verify'...
[WARNING] Maven will be executed in interactive mode, but no input stream has been configured for this MavenInvoker instance.
...
[INFO] [prepare] 11/17 scm-commit-release
[INFO] Checking in modified POMs...
[INFO] Executing: /bin/sh -c cd '' && 'git' 'add' '--' 'pom.xml'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'rev-parse' '--show-prefix'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'status' '--porcelain' '.'
[INFO] Working directory: 
[WARNING] Ignoring unrecognized line: ?? pom.xml.releaseBackup
[WARNING] Ignoring unrecognized line: ?? release.properties
[INFO] Executing: /bin/sh -c cd '' && 'git' 'commit' '--verbose' '-F' '/var/folders/j7/b_l02t1d7nxgrtrszg446pmm0000gn/T/maven-scm-866565484.commit'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'symbolic-ref' 'HEAD'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'push' 'https://github.com/HubertWo/hubert-codes-maven-release' 'refs/heads/main:refs/heads/main'
[INFO] Working directory: 
[INFO] [prepare] 12/17 scm-tag
[INFO] Tagging release with the label release-example-1.0...
[INFO] Executing: /bin/sh -c cd '' && 'git' 'tag' '-F' '/var/folders/j7/b_l02t1d7nxgrtrszg446pmm0000gn/T/maven-scm-1748404989.commit' 'release-example-1.0'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'push' 'https://github.com/HubertWo/hubert-codes-maven-release' 'refs/tags/release-example-1.0'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'ls-files'
[INFO] Working directory: 
[INFO] [prepare] 13/17 rewrite-poms-for-development
[INFO] Transforming 'release-example'...
[INFO] [prepare] 14/17 remove-release-poms
[INFO] Not removing release POMs
[INFO] [prepare] 15/17 run-completion-goals
[INFO] [prepare] 16/17 scm-commit-development
[INFO] Checking in modified POMs...
[INFO] Executing: /bin/sh -c cd '' && 'git' 'add' '--' 'pom.xml'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'rev-parse' '--show-prefix'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'status' '--porcelain' '.'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'commit' '--verbose' '-F' '/var/folders/j7/b_l02t1d7nxgrtrszg446pmm0000gn/T/maven-scm-1014427007.commit'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'symbolic-ref' 'HEAD'
[INFO] Working directory: 
[INFO] Executing: /bin/sh -c cd '' && 'git' 'push' 'https://github.com/HubertWo/hubert-codes-maven-release' 'refs/heads/main:refs/heads/main'
[INFO] Working directory: 
[INFO] [prepare] 17/17 end-release
[INFO] Release preparation complete.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

Wow! That was quick! In few seconds Maven executed a lot of stuff. We already went through the main execution points earlier in this post, but fill free to examine log one more time.

After

Now, when we executed the goal. Let’s see and compare what changed. Firstly we will build and execute our application.

> mvn clean package
...
[INFO] Replacing target/release-example-1.1-SNAPSHOT.jar with target/release-example-1.1-SNAPSHOT-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
> java -jar target/release-example-1.1-SNAPSHOT.jar 
Hello! Application release details:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven 3.8.2
Built-By: hubert
Build-Jdk: 17
Main-Class: codes.hubertwo.maven.release.App
Specification-Version: 1.1-SNAPSHOT

Please notice that JAR name changed to release-example-1.1-SNAPSHOT.jar and Specification-Version in manifest is set to 1.1-SNAPSHOT.

Let’s compare what’s changed in VCS.

git log --oneline
79ba81c (HEAD -> main) [maven-release-plugin] prepare for next development iteration
5528c24 (tag: release-example-1.0) [maven-release-plugin] prepare release release-example-1.0
15ee7ce (origin/main) Pom.xml - added scm
fa799ea Release and shade plugin
3f13a67 Empty Maven project

Maven added two new commits.

  1. 5528c24 - this is the 1.0 version. You can also see that it’s tagged. Click here to see commit on GitHub
  2. 79ba81c - this is 1.1-SNAPSHOT version. Click here to see commit on GitHub

It’s already visible in log but let’s do the full comparison.

> git tag          
release-example-1.0

As expected our release has been tagged as release-example-1.0.

Where is my release JAR?

Great! So far we did a lot of stuff, but where’s exactly the released JAR? In this post I won’t show how to push the build (artifact) to the repo (this will probably covered in separate post). Instead, I will show you how to do it manually. To do so we will switch in git to tagged version and build it.

> git checkout tags/release-example-1.0
Note: switching to 'tags/release-example-1.0'.

And now let’s build and execute the JAR to see what version of app we have.

mvn clean package 
[INFO] Replacing /target/release-example-1.0.jar with /target/release-example-1.0-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
java -jar target/release-example-1.0.jar
Hello! Application release details:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven 3.8.2
Built-By: hubert
Build-Jdk: 17
Main-Class: codes.hubertwo.maven.release.App
Specification-Version: 1.0

As you can see version is 1.0. So long story short our release is under the VCS tag.

Troubleshooting

I cannot login to GitHub when executing maven:prepare

You have to generate Personal Access token and then login using it. The username is your GitHub username and password is the Personal Access token - More here.

Summary

In this post we learnt how to configure project to use Maven Release plugin, we described the steps executed by plugin and finally performed the release. After all this we compared the changes in code and repository made by Maven. Now it should be clear how to automate release process.

Source code

All code samples and Maven project described in this post is available on GitHub Preparing release with Maven Release plugin.
If you found this post useful do not forget to leave a ⭐️ on GitHub :) Thanks!

Read more

  1. Maven Release plugin documentation