Alternative Versioning

AltVer is a relaxed alternative to SemVer.

ALTVER summary

Summary

The version number is composed of three parts: BRAND, NEW, and CARE.

Increment BRAND when you make something extraordinary, usually with breaking changes. Increment NEW when you add exciting new functionalities (including breaking changes with an easy upgrade path). Increment CARE version when you make backward compatible bug fixes and improvements.

Essentially, AltVer is SemVer but explicitly acknowledges that small breaking changes can live in minor versions when the impact is minimal and the upgrade is easy.

Just like SemVer, additional labels for pre-release and build metadata are available as extensions.

Motivation

After over a decade of using and maintaining open source, I’ve come to believe SemVer made everybody terrified of new major versions.

On one hand, most library users lock their dependency manager to the latest minor version (i.e. the latest released right before the next major).

On the other hand, most maintainers are hesitant to tag a major version because it will partition the community.

In the wild, many projects are actually versioned with AltVer without naming it. Maintainers often balance the pros and cons for each change and might introduce “a breaking change” in a minor (or patch) version.

Understanding what a breaking change is

When I discovered SemVer, I thought this was very scientific and everyone always agrees on what a breaking change is. Unfortunately, it’s a lot more subtle than that.

Example 1: Empty results

A function get_users() returns null when no users are found, but the docs say it should return []. Users have written if (result === null) checks everywhere. Fixing it to return [] breaks their code, but new users see it as an obvious bug fix.

Example 2: Date formatting

A function format_date() returns “DD/MM/YYYY” but the docs say “MM/DD/YYYY”. European users built their apps around this behavior. American users file bugs constantly. Fixing it is a “bug fix” for Americans and a “breaking change” for Europeans.

Example 3: Case sensitivity

A search function is supposed to be case-insensitive but accidentally matches case-sensitively. Some users rely on this to filter results precisely. Fixing it changes what their searches return.


People consider things to be a bug fix or breaking change depending on how it affects them.


The consequences of a Breaking Change

Whenever a breaking change is released, you’d increase the major version number. Major versions are well named: MAJOR means it’s important and significant, so it should be rare.

Delayed release

As a user, you don’t expect a major version every few weeks. So breaking changes are bundled and the fix is delayed. Instead of getting an important fix now, you’ll get it “next time”.

One solution is sometimes to run a fork of the dependency until the next release.

Partitioning user base

Most users upgrade to the latest minors but won’t update often to the next major (unless you’re an important part of the stack). If you’re a small dependency, users won’t upgrade until they need to. It’s fine! But now as a maintainer you have users using multiple major versions (sending reports, feature requests and so on).

This creates real problems:

The more major versions you release, the thinner you spread the community.

Trust the maintainer

Maintainers are always responsible for what gets into the release. They will carefully weigh if something needs to be fixed with or without breaking changes, when it should be released, and what the version type should be.

Maintainers need to keep things manageable for them so you get new features and bug fixes.

Maintainers will always avoid breaking things and upsetting the people trusting them with their code.

Mistakes happen. It happens to all of us but generally, you should trust the maintainer.

AltVer is a statement that maintainers will do their best to balance all the project stakes. Trust the maintainer.

Specification

Any new BRAND version must be exciting! The upgrade might require some extra work so people must want to do it.

NEW versions usually include important new features, something visible, like a setting you want to enable.

New CARE versions include bug fixes, performance improvements, and new features that are not as visible.

As the project matures, BRAND will probably reach the end and won’t be updated again, NEW becomes more and more rare while CARE is the most common.

NEW or PATCH ?

Fixing a bug? Should be PATCH.

Adding a small insignificant feature? Should be PATCH.

Adding a really cool feature? Should be NEW.

Introducing a breaking change? Should NEVER be PATCH.

Judging the coolness of a feature is very subjective but typically, it’s something that has been anticipated. If users keep requesting this feature it’s definitely a NEW. Again, as a maintainer, you know.

BRAND or NEW ?

When there is a new feature and it’s breaking, should it be a BRAND or a NEW version? The maintainer will take into consideration many things to decide:

If it breaks everything but all the users have to do is change a key configuration, it might be fine. If the upgrade is harder but it most likely affects nobody because it’s not the way things were supposed to be used, it might be fine too.

Maintainers might have different opinions on each situation but it’s already like that with SemVer. AltVer just makes it explicit.

BRAND is also used when maintainers are bundling a lot of cool features and they want to draw more attention to a version. BRAND versions don’t have to be breaking but they often are.

Users are encouraged to set their version requirement to “latest version until next BRAND”, which is typically what we do for SemVer.

If you don’t have enough test coverage yet, you should at least update to the latest CARE version automatically.

Examples with NPM
{
  "dependencies": {
    "my-lib": "^4.7.9"
  }
}
Example with Composer
{
  "require": {
    "my-lib": "^4.7.9"
  }
}
Example with Bundler
gem 'my-lib', '~> 4.7'
Example with Cargo (Rust)
[dependencies]
my-lib = "4.7"
Example with pip (Python)
my-lib~=4.7.9
Example with Poetry (Python)
[tool.poetry.dependencies]
my-lib = "^4.7.9"
Example with Go modules
require my-lib v4.7.9

Go modules don’t have a built-in syntax for “up to next major”. Use tools like Dependabot or Renovate to automate updates.

Example with Maven (Java)
<dependency>
  <groupId>com.example</groupId>
  <artifactId>my-lib</artifactId>
  <version>[4.7,5.0)</version>
</dependency>
Example with NuGet (.NET)
<PackageReference Include="my-lib" Version="4.7.*" />

FAQ

Why use three main parts X.Y.Z?

SemVer has become The One True Way™ to version software for a lot of people. We’re very used to seeing versions with three parts, so it’s easier for everybody to keep them. In most cases, people won’t notice a package is following AltVer or SemVer.

What are the numbering syntax differences between SemVer and AltVer?

Technically none! AltVer is an alternative interpretation of the version numbers. By simply looking at a SemVer or an AltVer version number, there is no way to tell them apart. The same Backus–Naur Form Grammar can be used.