Semantic Versioning cannot classify a release until the project defines what consumers are allowed to depend on. A library may expose functions, types, command-line flags, file formats, environment variables, network schemas, and error behavior at the same time. Calling a change “minor” without naming that surface is a label, not a compatibility decision.
Use versions as a statement about a documented public contract. Then make the release process test that contract from the consumer's side.
Define the public API before assigning numbers
Semantic Versioning 2.0.0 requires a declared public API. For a package, that API may include more than exported symbols:
- import paths and exported types;
- accepted inputs and returned values;
- documented errors and side effects;
- configuration keys and defaults;
- command names, flags, exit codes, and output intended for machines;
- persisted or transmitted schemas;
- supported runtime and platform ranges.
Separate contractual behavior from internal implementation. A private helper can change without a consumer-visible version consequence, while changing a documented default can break users even if no function signature moves.
Write the support boundary down. If undocumented behavior is intentionally not stable, say so, but do not use that disclaimer to dismiss widespread, foreseeable dependencies. Compatibility is partly technical and partly a product promise.
Apply major, minor, and patch to observable effects
The Semantic Versioning specification defines X.Y.Z: major for incompatible public API changes, minor for backward-compatible functionality, and patch for backward-compatible bug fixes. It also states that released version contents must not be modified; corrections require a new version.
Classify from the perspective of a supported consumer:
- Removing or renaming a public member is normally major.
- Making a previously accepted input invalid can be major.
- Adding an optional capability without changing existing behavior is normally minor.
- Fixing an implementation defect while preserving the documented contract is normally patch.
- Changing a documented output order, error code, default timeout, or serialization detail may be breaking even when source code still compiles.
A bug fix can reveal ambiguity. If consumers rely on behavior that contradicts the documentation, the team must decide whether the behavior has become part of the practical contract. Do not call it patch solely because an issue labels the old behavior a bug.
Treat zero-major and pre-release versions explicitly
Under SemVer, 0.y.z indicates initial development and the public API should not be considered stable. That does not mean changes are free of consequence. Consumers still need release notes and migration guidance, and a project can adopt stricter compatibility rules during zero-major development.
Pre-release identifiers such as 2.0.0-rc.1 have lower precedence than the associated normal version. They are useful for testing a candidate contract, but they do not convert incompatible behavior into a compatible change for users who adopt them. State which support, upgrade, and data-compatibility expectations apply to pre-releases.
Build metadata such as 2.0.0+build.17 does not affect SemVer precedence. Do not use metadata to distribute different functional behavior under what dependency resolution treats as the same precedence. Every artifact for one version should have a clear, immutable relationship to its source and build record.
Detect compatibility beyond compilation
Compile tests catch only some source-level breaks. A release gate should exercise the public surface in the ways consumers use it:
- compile a small consumer against the prior and candidate versions;
- run contract tests for requests, responses, and documented errors;
- read old persisted data with the candidate and, when promised, read new data with the old version;
- invoke command-line interfaces and validate machine-readable output and exit codes;
- test supported runtime and platform ranges;
- compare generated schemas or API descriptions;
- test mixed-version operation during rolling deployment when it is part of the support model.
The previous release is a useful baseline, not the only one. A compatibility promise may cover an entire major line. Test the oldest supported consumer state when migrations or cumulative changes can expose different failures.
Separate deprecation from removal
Deprecation communicates that a public element remains available but has a planned replacement or end of support. It should include:
- the deprecated behavior;
- the supported alternative;
- migration steps;
- the earliest release in which removal may occur;
- limitations of the replacement.
A warning without a usable migration path shifts work to consumers without reducing uncertainty. Conversely, leaving deprecated behavior forever expands the support surface. Establish a policy tied to release cadence and consumer needs, then follow it consistently.
Removal remains a breaking change even after a long deprecation period. The warning improves preparedness; it does not change the version category.
Make the version decision reviewable
For each release, create a change inventory linked to the public API definition. Ask of every entry:
- Can an existing supported consumer observe this change?
- Can that consumer continue without code, configuration, or data migration?
- Does the release add capability without altering existing outcomes?
- Is a reported fix restoring the written contract or changing an established practical one?
- Which tests demonstrate the classification?
Choose the highest version impact found in the release. A bundle containing ten patches and one incompatible change is major.
Release notes should explain behavior, affected consumers, migration, and rollback rather than merely repeat “major,” “minor,” or “patch.” The number helps automation choose candidates; it cannot carry the complete compatibility argument.
Semantic Versioning works when public API ownership, compatibility evidence, immutable artifacts, and clear communication reinforce one another. Without that system, precise-looking numbers create confidence that the project has not earned.
Was this guide useful?
Your rating helps us prioritize clearer, more practical technical content.
Review diffs, run checks, and prepare the release in Arezgit.
Keep Git review, security scanning, API checks, database inspection, and release preparation together in one local desktop application.
Explore Arezgit