2011-01-29
Maven Releases on Steroids (2): Preparing the POMs
In this part, we will make the necessary adjustments to the POMs to make it all work.
Our goal is to have a single command to:
- Build and Test the SCM revision currently checked out
- Deploy the binary artifacts to the Artifact Repository
- Tag the SCM revision with the version number of the artifacts
As we only want one commit instead of 3 per release, the version number can not be contained inside the POM anymore, as this would make it impossible to update it without updating the POM itself.
We have to balance this against the need for a default version number when performing local, non-release builds, as we don't want to pass in a version number for every build.
This means we have the following requirements:
- Version number settable from outside for release builds
- Default version number for local, non-release builds
This can be solved by introducing a VERSION_NUMBER property:
Parent POM:
<project ...>
...
<version>${VERSION_NUMBER}</version>
...
<properties>
...
<VERSION_NUMBER>1.0-SNAPSHOT</VERSION_NUMBER>
...
</properties>
...
Child POMs:
<project ...>
...
<parent>
...
<version>${VERSION_NUMBER}</version>
...
</parent>
...
<version>${VERSION_NUMBER}</version>
...
The VERSION_NUMBER property now determines the version of our project.
By default it is set to 1.0-SNAPSHOT for local, non-release builds.
It can also be set externally using -DVERSION_NUMBER=... for release builds.
And normally, this should be it!
Unfortunately, Maven has a minefield of bugs we need to work around. What it basically boils down to, is that Maven neglects to replace variables in installed (local repo) and deployed (remote repo) POMs. This means our POMs get deployed with <version>${VERSION_NUMBER}</version>, which causes problems at runtime.
We will need to overwrite the broken POMs with a new version that has its variables replaced with their values.
The first thing we need to add (only to our parent POM) is a way to distinguisch between snapshot and release builds:
<project ...>
...
<properties>
...
<releaseRepoUrl>http://my.release.repo</releaseRepoUrl>
<snapshotRepoUrl>http://my.snapshot.repo</snapshotRepoUrl>
<deployRepoUrl>${releaseRepoUrl}</deployRepoUrl>
<isRelease>true</isRelease>
...
</properties>
...
<profiles>
<profile>
<id>snapshot-deploy-url-override</id>
<activation>
<property>
<name>!VERSION_NUMBER</name>
</property>
</activation>
<properties>
<deployRepoUrl>${snapshotRepoUrl}</deployRepoUrl>
<isRelease>false</isRelease>
</properties>
</profile>
</profiles>
...
<distributionManagement>
<snapshotRepository>
<id>snapshots-repo</id>
<name>Snapshots Repo</name>
<url>${snapshotRepoUrl}</url>
</snapshotRepository>
<repository>
<id>releases-repo</id>
<name>Releases Repo</name>
<url>${releaseRepoUrl}</url>
</repository>
</distributionManagement>
...
And now comes the real juicy part (only necessary in the parent POM):
- Fixing the POM by substituting the variables
- Overwriting the existing POM in the local repo
- Overwriting the existing POM in the remote repo
<project ...>
...
<build>
...
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<id>replace-pom-placeholder</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>${basedir}</directory>
<includes>
<include>pom.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<outputDirectory>${project.build.directory}/pom-install-deploy-fix</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<id>overwrite-pom</id>
<phase>install</phase>
<goals>
<goal>install-file</goal>
</goals>
<configuration>
<packaging>pom</packaging>
<file>target/pom-install-deploy-fix/pom.xml</file>
<pomFile>target/pom-install-deploy-fix/pom.xml</pomFile>
<version>${project.version}</version>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>overwrite-pom</id>
<phase>deploy</phase>
<goals>
<goal>deploy-file</goal>
</goals>
<configuration>
<packaging>pom</packaging>
<file>target/pom-install-deploy-fix/pom.xml</file>
<pomFile>target/pom-install-deploy-fix/pom.xml</pomFile>
<url>${deployRepoUrl}</url>
<version>${project.version}</version>
<updateReleaseInfo>${isRelease}</updateReleaseInfo>
<uniqueVersion>false</uniqueVersion>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>
...
If you also want to deploy source artifacts, check out the Maven Source Plugin.
So far, so good: we now have taken care of the deployment in the Artifact Repository!
Let's add the final bit to enable SCM tagging...
First add the scm section to your parent POM:
<project ...>
...
<scm>
<connection>scm:my-provider:my-read-url</connection>
<developerConnection>scm:my-provider:my-read-write-url</developerConnection>
</scm>
...
And now add the scm plugin to the plugins section of the parent POM:
<plugin>
<artifactId>maven-scm-plugin</artifactId>
<version>1.4</version>
<configuration>
<tag>${project.artifactId}-${VERSION_NUMBER}</tag>
</configuration>
</plugin>
Done!
We can now
- publish new snapshots using
mvn clean deploy - release and tag new versions using
mvn clean deploy scm:tag -DVERSION_NUMBER=1.2.3
In Part 3, we conclude our adventure by choosing a version number strategy and looking at Jenkins integration.

About Axel Fontaine
I'm an entrepreneur, public speaker and software development expert based in Munich.
I'm the creator of Sprinters. Sprinters lets you run your GitHub Actions jobs 10x cheaper on your own AWS account with secure, ephemeral, high-performance, low-cost runners within the privacy of your own VPC.
I also created CloudCaptain, previously known as Boxfuse. CloudCaptain is a cloud deployment platform enabling small and medium size companies to focus on development, while it takes care of infrastructure and operations.
Back in 2010, I bootstrapped Flyway, and grew it into the world's most popular database migration tool. Starting late 2017, I expanded the project beyond its open-source roots into a highly profitable business, acquiring many of the world's largest companies and public institutions as customers. After two years of exponential growth, I sold the company to Redgate in 2019.
In the past I also spoke regularly at many large international conferences including JavaOne, Devoxx, Jfokus, JavaZone, JAX and more about a wide range of topics including modular monoliths, immutable infrastructure and continuous delivery. As part of this I received the JavaOne RockStar speaker award. As a recognition for my contributions to overall Java industry, Oracle awarded me the Java Champion title.
You can find me on 𝕏 as @axelfontaine and email me at axel@axelfontaine.com