Semantic versioning is a formal convention for determining the version number of new software releases. The standard helps software users to understand the severity of changes in each new distribution.
A project that uses semantic versioning will advertise a Major, Minor and Patch number for each release. The version string
1.2.3 indicates a major version of 1, a minor version of 2 and a patch number of 3.
Version numbers using this format are widely used by both software packages and end-user executables such as apps and games. Not every project exactly follows the standard set out by semver.org.
The specification was created to address the problems caused by inconsistent versioning practices among software packages used as dependencies. By “package” and “dependency,” we’re referring to a library of code that’s intended to be used within another software project and is distributed by a package manager such as
nuget. This is the application of semantic versioning which we’re considering within this article.
Major, Minor and Patch
It’s important to understand the meaning of the three components involved. Together, they chart a project’s development journey and relate the end-user impact of each new release.
- Major number – The major number indicates the current version of the package’s public interface. This should be incremented every time you make a change that would require existing users of your package to update their own work.
- Minor number – The minor number describes the current functional release of your software. This is incremented whenever you add a new feature but do not otherwise alter your package’s interface. It communicates to users that a significant change has been made but the package remains fully backwards compatible with the previous minor number.
- Patch number – The patch number gets incremented every time you make a minor change that neither impacts the public interface or overall functionality of your package. This is most commonly used for bug fixes. Consumers should always be able to update to the latest patch release without hesitation.
The semantic versioning release structure is best modelled as a tree. At the top, you have your public interface changes, each of which result in a new major number. Every major series has its own set of minor releases, where new functionality is added in a backwards compatible manner. Finally, minor releases may receive bug-fixing patches from time-to-time.
Where to Start?
Most projects should use
1.0.0 as their initial version. You are publishing your first public interface and an initial unaltered set of functionality. You haven’t yet had to create a patch, so the patch version is
Let’s now look at what happens as you make changes to your package. After your initial release, you receive a bug report from a user. When you release the fix, the correct version number will be
1.0.1. Were you to then create another bug-fixing release, you’d bump the patch number up to
In the meantime, you’ve also been working on an exciting new feature. It’s entirely optional so users won’t need to do anything to upgrade. You release this as
1.1.0 – a new functional series has been created and you haven’t had to patch it yet. Unfortunately, bug reports soon come in and so
1.1.1 gets pushed out to your users.
Several months on, you’ve decided to refactor the entire project. Some of the functionality you used to offer has been removed or is now accessed through a consolidated interface. If you released this work, people using the current version of your package would have to make major alterations within their project. It’s time for you to publish
2.0.0 into your package repository.
Maintaining Older Branches
Bumping a number within your version string doesn’t create a point of no return. After publishing
1.1.1, you might discover a bug which was also present in
1.0.2. Using branches in your source control system, you could apply the patch to both version series. You’d end up releasing
Similarly, you might want to keep maintaining the
1.x branch of your project despite having released
2.0.0. It may feel strange to publish
2.0.1 but this is perfectly acceptable practice. Semantic versioning does not create a linear always-incrementing version number; instead, it is intended to be used as part of a branching development model that capitalises on the ease-of-patching offered by source control systems such as Git.
Published versions must be immutable. Once you’ve created a release, such as
2.4.3, you cannot “update” it by simply pushing additional code under the same version string. You have to assign a new version number to each release, so users can always access each specific revision of your package.
Handling Pre-Release Packages
Ordinarily, you always bump the major version of your project whenever a backwards incompatible change is introduced. When you’re in a pre-launch state, your codebase may evolve very quickly, resulting in a slew of major versions being published.
You can avoid this by advertising your project as
0.y.z to begin with. Adopting
0 as your major version indicates that your package is unstable. The normal rules around backwards incompatible changes no longer apply, so you can publish new releases by incrementing the minor and patch numbers only. This means you can still use
1.0.0 to label the first “finished” version of your software.
You may also append extra “identifiers” to the end of the version string, using a hyphen as the separator:
1.0.0-alpha.1. You can use this to clearly denote alpha and beta variants. Similarly, you can include build metadata by appending it with the
Making consistent use of semantic versioning helps users to have confidence in your project. They can clearly see how your codebase is evolving and whether they’ll need to do work themselves to stay up-to-date.
Advertising a semantic versioning string is essential when you publish to most popular package managers. Nonetheless, it’s ultimately your decision which numbers you bump for each new release. Sticking to the standard clearly communicates your intentions to the community and minimises the risk of unintentionally breaking someone else’s work.