76

I have a web application where we deploy to production whenever a feature is ready, sometimes that can be a couple of times a day, sometimes it can be a couple of weeks between releases.

Currently, we don't increment our version numbers for our project, and everything has been sitting at version 0.0.1-SNAPSHOT for well over a year. I am wondering what is the Maven way for doing continuous delivery for a web apps. It seems overkill to bump up the version number on every commit, and never bumping the version number like we are doing now, also seems wrong.

What is the recommend best practice for this type of Maven usage?

The problem is actually a two-fold one:

  • Advancing project version number in individual pom.xml file (and there can be many).
  • Updating version number in all dependent components to use latest ones of each other.
uvsmtid
  • 4,187
  • 4
  • 38
  • 64
ams
  • 60,316
  • 68
  • 200
  • 288

7 Answers7

57

I recommend the following presentation that discusses the practical realities of doing continuous delivery with Maven:

The key takeaway is each build is a potential release, so don't use snapshots.

Mark O'Connor
  • 76,015
  • 10
  • 139
  • 185
  • I am marking this as the correct answer because the You Tube video was excellent at answering this question exactly. However, I did have to pause and rewind a few times to get of the some finer points. I have put a summary of process recommended in the video in my own answer. – ams Oct 06 '13 at 13:53
  • 2
    The video is pretty good and will help you loads. @23m35s in the video, I'd make the point that Maven should do most of the short-running sanity verification, such as unit tests, coverage checks, integration tests, etc... however Continuous Delivery doesn't stop there. For larger SOA-type organisations, breaking up your pipeline into discreet phases is actually very good practice, for example functional tests with mocked endpoints, performance tests, big integration tests, UAT testing, compliance verification, static code analysis, security review, etc. – KRK Owner Apr 02 '14 at 20:37
  • Great video. My only concern is what do people do about all of the 'release candidates' in the release repo, that are never officially released? We have a cleanup schedule set for our SNAPSHOTs, however release artifacts are kept around forever. I see this using alot of disk space, on the Maven repository side of things. – Patrick Sep 08 '17 at 14:55
  • 1
    @Patrick Release candidates are not releases until they're approved. I have a purge job that deletes them if they're older than 2 months. If they haven't been released by then they never will be :-) – Mark O'Connor Sep 08 '17 at 17:19
  • The presenter on the youtube video that you are linking to is actually explicitly saying that you should avoid using the "maven-release-plugin". See here: https://youtu.be/McTZtyb9M38?t=2179 – hfm Jan 16 '18 at 10:27
  • Great answer but there is still one requirement of the question that has NOT been answered yet! "Updating version number in all dependent components to use latest ones of each other." What about this? I am curious because this is also a headache for me too! In other words, what we should do in situations of multiple repositories with different multi-module maven projects with inter-dependencies among them? – Theodore Jan 13 '21 at 15:11
  • @Theodore What requirement are you talking about? Remember this question was posed and answered 7 years ago – Mark O'Connor Jan 13 '21 at 15:13
  • @MarkO'Connor Iam talking about this: "Updating version number in all dependent components to use latest ones of each other." – Theodore Jan 14 '21 at 08:42
  • @Theodore Gotcha! That is a bit of a nightmare, alright, and something I personally try to avoid. I reserve the use of "latest" revisions for in-house components, to maintain build stability. Periodically we then have a quick sprint where we upgrade to latest revision of our most important dependencies and test backward compatibility. Paranoia rules... We are considering the installation of Renovate on github to automatically create PRs when our dependencies change. This might be the best compromise. Hope this helped. – Mark O'Connor Jan 15 '21 at 09:43
  • How would you bump your versions in the case you dont have CI/CD in place yet? Thanks – pixel Feb 04 '23 at 20:26
46

This is my summary based on the video linked by Mark O'Connor's answer.

  • The solution requires a DVCS like git and a CI server like Jenkins.
  • Don't use snapshot builds in the Continuous Delivery pipeline and don't use the maven release plugin.
  • Snapshot versions such as 1.0-SNAPSHOT are turned into real versions such as 1.0.buildNumber where the buildNumber is the Jenkins job number.

Algorithm steps:

  1. Jenkins clones the git repo with the source code, and say the source code has version 1.0-SNAPSHOT
  2. Jenkins creates a git branch called 1.0.JENKINS-JOB-NUMBER so the snapshot version is turned into a real version 1.0.124
  3. Jenkins invokes the maven versions plugin to change the version number in the pom.xml files from 1.0-SNAPSHOT to 1.0.JENKINS-JOB-NUMBER
  4. Jenkins invokes mvn install
  5. If the mvn install is a success then Jenkins will commit the branch 1.0.JENKINS-JOB-NUMBER and a real non-snapshot version is created with a proper tag in git to reproduce later. If the mvn install fails then Jenkins will just delete the newly created branch and fail the build.

I highly recommend the video linked from Mark's answer.

seanf
  • 6,504
  • 3
  • 42
  • 52
ams
  • 60,316
  • 68
  • 200
  • 288
  • 3
    How do you handle a multi module maven project? Do you just release the whole project and all children with the same version? If I have project A depending on B and I make a commit in project B, the above steps are triggered for B, which then triggers the above steps for project A, but then how does A know the version of B? – dukethrash Apr 10 '14 at 20:02
  • 2
    It it's a multi-module project, then experience tells me that having a single version makes sense - it's consistent with the versions plugin. If some modules don't change as often, they may as well be separate components pulled in from an artifact repo - eg, log4j, sl4fj ... – KRK Owner Apr 11 '14 at 11:54
  • 5
    Well in my case it's not even a multi module. I have Project A that depends on Project B. Project B has its own Jenkins job and could have a commit that generates a released artifact. Project A then needs to reference that released artifact in the pom. Are you suggesting to just manually update Project A when Project B's build succeeds and the artifact is available? Project B is a core project and Project A is a web app so they are coupled but have a big overhead in update Project A's dependency on Project B especially when you develop in an IDE that views changes with SNAPSHOT dependencies. – dukethrash May 28 '14 at 17:13
27

Starting from Maven 3.2.1 continuous delivery friendly versions are supported out of the box : https://issues.apache.org/jira/browse/MNG-5576 You can use 3 predefined variables in version:

${changelist}
${revision}
${sha1}

So what you basically do is :

  1. Set your version to e.g. 1.0.0-${revision}. (You can use mvn versions:set to do it quickly and correctly in multi-module project.)
  2. Put a property <revision>SNAPSHOT</revision> for local development.
  3. In your CI environment run mvn clean install -Drevision=${BUILD_NUMBER} or something like this or even mvn clean verify -Drevision=${BUILD_NUMBER}.

You can use for example https://wiki.jenkins-ci.org/display/JENKINS/Version+Number+Plugin to generate interesting build numbers.

Once you find out that the build is stable (e.g. pass acceptance tests) you can push the version to Nexus or other repository. Any unstable builds just go to trash.

Piotr Gwiazda
  • 12,080
  • 13
  • 60
  • 91
  • This is much easier to deal with. No version changes + commit to make this work... I just set mine to `1.0${revision}` with a property `-SNAPSHOT`, then during a CI build i use `mvn clean deploy -Drevision=".$TRAVIS_BUILD_ID"`. It results in _patch_ versions that are _unusual_ but always increasing so they satisfy semver... – Lucas Jun 08 '18 at 18:04
10

There are some great discussions and proposals how to deal with the maven version number and continuous delivery (CD) (I will add them after my part of the answer).

So first my opinion on SNAPSHOT versions. In maven a SNAPSHOT shows that this is currently under development to the specific version before the SNAPSHOT suffix. Because of this, tools like Nexus or the maven-release-plugin has a special treatment for SNAPSHOTS. For Nexus they are stored in a separate repository and its allowed to update multiple artefacts with the same SNAPSHOT release version. So a SNAPSHOT can change without you knowing about it (because you never increment any number in your pom). Because of this I do not recommend to use SNAPSHOT dependencies in a project especially in a CD world since the build is not reliable any more.

SNAPSHOT as project version would be a problem when your project is used by other ones, because of the above reasons.

An other problem of SNAPSHOT for me is that is not really traceable or reproducibly any more. When I see a version 0.0.1-SNAPSHOT in production I need to do some searching to find out when it was build from which revision it was build. When I find a releases of this software on a filesystem I need to have a look at the pom.properties or MANIFEST file to see if this is old garbage or maybe the latest and greatest version.

To avoid the manual change of the version number (especially when you build multiple builds a day) let the Build Server change the number for you. So for development I would go with a

<major>.<minor>-SNAPSHOT

version but when building a new release the Build Server could replace the SNAPSHOT with something more unique and traceable.

For example one of this:

<major>.<minor>-b<buildNumber>
<major>.<minor>-r<scmNumber>

So the major and minor number can be used for marketing issues or to just show that a new great milestone is reached and can be changed manually when ever you want it. And the buildNumber (number from your Continuous Integration server) or the scmNumber (Revision of SUbversion or GIT) make each release unique and traceable. When using the buildNumber or Subversion revision the project versions are even sortable (not with GIT numbers). With the buildNumber or the scmNumber is also kinda easy to see what changes are in this release.

An other example is the versioning of stackoverflow which use

<year>.<month>.<day>.<buildNumber>

And here the missing links:

mszalbach
  • 10,612
  • 1
  • 41
  • 53
  • 1
    The first link Versioning in a Pipeline is broken – Aravind Yarram Nov 20 '14 at 01:45
  • Seems to be removed. I did not find a replacement link or the content in a web cache :(. – mszalbach Nov 26 '14 at 12:27
  • This answer is more about version name format and its meaning. There is already good enough formalized spec [called "semantic versioning"](http://semver.org/) - just add `SNAPSHOT` to mean "not yet released" version. And the format of the version is **not** really a problem in Maven and there **are [some restrictions](http://goo.gl/JW9PS7)**. The problem is to make this process _effortless_ and preferably _automatic_ (at least until release with semantic=manual version like `X.Y.Z` is done). Updating sections of multiple interdependent `pom.xml` files **is** the problem. – uvsmtid Jul 26 '15 at 08:28
7

DON'T DO THIS!

<Major>.<minor>-<build>

will bite you in the backside because Maven treats anything after a hyphen as LEXICAL. This means version 1 will be lexically higher than 10.

This is bad as if you're asking for the latest version of something in maven, then the above point wins.

The solution is to use a decimal point instead of a hyphen preceding the build number.

DO THIS!

<Major>.<minor>.<build>

It's okay to have SNAPSHOT versions locally, but as part of a build, it's better to use

mvn versions:set -DnewVersion=${major}.${minor}.${build.number}

There are ways to derive the major/minor version from the pom, eg using help:evaluate and pipe to a environment variable before invoking versions:set. This is dirty, but I really scratched my head (and others in my team) to make it simpler, and (at the time) Maven wasn't mature enough to handle this. I believe Maven 2.3.1 might have something that go some way in helping this, so this info may no longer be relevant.

It's okay for a bunch of developers to release on the same major.minor version - but it's always good to be mindful that minor changes are non-breaking and major version changes have some breaking API change, or deprecation of functionality/behaviour.

From a Continuous Delivery perspective every build is potentially releasable, therefore every check-in should create a build.

KRK Owner
  • 762
  • 7
  • 16
  • 7
    Implied by http://mojo.codehaus.org/versions-maven-plugin/version-rules.html and according to my own tests, this is not correct. The default Maven versioning scheme is `..-` (with most of it optional). As long as the part after the dash is numeric, it will not be compared lexically. I tested this with `mvn versions:use-latest-versions`and e.g. `1.4-11` is correctly detected as newer than `1.4-2`. – Philipp Paland Apr 10 '14 at 09:08
  • What version of Maven did you test this with? Also, did you try also installing a version of 1.4.21, then playing to find out how Maven version range resolution works? It would be interesting to hear about your findings, as I certainly has this issue with 2.2.1, and I verified this by looking in Maven's source code. Also, I don't think these rules actually apply to anything but the versions plugin, and I'm talking about dependency resolution. – KRK Owner Apr 11 '14 at 11:47
  • Follow https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm#MAVEN401 @philipp is correct. It is safety to use incremental numbers in ${major}.${minor}.${build.number} and ${major}.${minor}.${build.number}-${patchNumber} , but not ${major}.${minor}.${build.number}.${patchNumber} – iMysak Jun 28 '17 at 19:58
2

As a starting point you may have a look at Maven: The Complete Reference. Project Versions.

Then there is a good post on versioning strategy.

dmitri
  • 3,183
  • 23
  • 28
2

At my work for web apps we currently use this versioning pattern:

<jenkins build num>-<git-short-hash>

Example: 247-262e37b9.

This is nice because it it gives you a version that is always unique and traceable back to the jenkins build and git revision that produced it.

In Maven 3.2.1+ they finally killed the warnings for using a ${property} as a version so that makes it really easy to build these. Simply change all your poms to use <version>${revision}</version> and build with -Drevision=whatever. The only issue with that is that in your released poms the version will stay at ${revision} in the actual pom file which can cause all sorts of weird issues. To solve this I wrote a simple maven plugin (https://github.com/jeffskj/cd-versions-maven-plugin) which does the variable replacement in the file.

Jeff S
  • 114
  • 2
  • The plugin seems like a solution to update versions of `pom.xml` files themselves. However, the next problem is to update versions within `dependencies` section of all dependent components participating in release so that they actually use latest of each other. I have **[this problem when automatic update is unreliable](http://stackoverflow.com/q/31579730/441652)** in multi-module/reactor build (which update external and not only internal components). So, the short-term solution to stay in `SHAPSHOT`s. **Any comment on how you solved this problem?** – uvsmtid Jul 26 '15 at 07:55
  • You basically shouldn't use snapshot versions. Update the versions of dependencies before building. – Jeff S Apr 21 '16 at 01:51
  • Keep all thr modules in same version. In root module dependencyManagement section set modules to `${project.version}`. Then simple `mvn versions:set -DnewVersion=whatever` will do the trick and change versions in reactor order including parents etc. – Piotr Gwiazda Jan 28 '17 at 10:03