First of all, I want to point out that semver.org is THE place to read about semantic versioning. If you have not read this, do so now. If you have read it, go ahead and read it again. Most developers I know are familiar with the semver standard, yet time and time again, I am faced with people who feel that it is OK to not follow it strictly. I feel compelled to talk about why some of these specific exceptions are wrong. I will admit, that semantic versioning for applications is not so important. However, when developing APIs or libraries, properly versioning your artifact is critical.
First of all, use it
Let’s get the obvious out of the way. There are many libraries, for example, Spring, which flat-out ignore semver with versions like “1.4.5.RELEASE”. Why? I have no idea, but if you maintain a library that does this, please stop.
Now let’s move on to the main reason I see people not following semver, even when they “in theory” believe in it. The excuse most offered up is CI/CD. I have seen many organizations decide that in order to do CI/CD, they must have automated version number increments, and to do so, they must append a build number or commit hash to the semantic version. Semver has a feature specifically for this purpose, which is the “+” segment. The problem with this, for some people, is that you will end up with multiple artifacts with the same semantic version (“+” is not part of the “semantic” part of the version number). Let’s just clear the air right here. DO NOT AUTOMATE YOUR VERSION NUMBER INCREMENTS. Version numbers are semantic, which means they have meaning. A meaning only developers will understand. Only developers will know the difference between a bugfix, a backwards-compatible change, and a breaking change. Your build server does not know these things. It should be the developer’s duty to properly indicate these semantic version changes. Also, this does not prevent CI/CD. Simply fail the build if you are trying to release a version which has already been released. The developer will then need to properly increment the version before their code will be accepted. Most artifact repositories will simply fail if you try to push a duplicate version to them, so you can even rely on that. Your semantic version is part of your public api, so like changes to an API, each change should be carefully considered.
Understand breaking changes
I have also seen, countless times, breaking changes introduced in minor or patch releases. So let’s talk about what constitutes a break change.
New version will not work with older versions of related modules
This comes up most often in frameworks, where multiple artifacts are expected to work together. Examples are logging frameworks and Spring. But this is important to consider in single-artifact libraries as well. You never know which public parts of your library your clients will be using, or overriding. If you expose it to the consumer of your library, then it is part of your API. Of course, as time goes on, you will need to refactor your code. When removing a public method, changing a public method’s signature, moving code to new packages, changing a public class, or changing any interface, you should leave the old code in place for backward compatibility and marked as deprecated. This will prevent this problem. You can delete all the old deprecated code on your next major release. Of course that means your code must be properly abstracted and decoupled to begin with, but you should be doing this anyway, especially when developing a library.
Ok so you use library X in your library. X just came out with version 2.0, and oh boy, isn’t it great? So you upgrade in such a way that you are not breaking any of your own API. That is a minor change right? Probably not. X is a really good library, and consumers of your library are more than likely also using X. you have now introduced a nasty dependency conflict in your client’s dependency tree. When upgrading to new major (and sometimes minor, if they are not good at semver) versions of other libraries and frameworks, you should check to see if this could be a breaking change (it probably is). Do a major release accordingly.
So what this boils down to is two simple rules: stick to the semver standard (no exceptions!), and be mindful of what a breaking change is. Do this, and we can come closer to the dream of semver, which is to eliminate dependency hell.