Version Numbers and Major Versions
Purpose and Scope
This page explains how Go developers should think about version numbers at two different but related layers. A module version is the release tag used by dependency management, such as v1.4.2 or v2.0.0-beta.1. A Go language or toolchain version is the version used by the go command, compiler, module file semantics, and diagnostics, such as Go 1.21 or the language version string go1.21. The distinction matters because module versions communicate API compatibility to users, while Go versions select tool behavior and language features.
For modules, the public rule is semantic versioning. Patch releases should preserve the public API and dependency meaning, minor releases may add backward-compatible public API, and major releases indicate changes that may break users. The official module guidance emphasizes that v0 is unstable, that prereleases signal evaluation builds, and that a major version update should be made only when backward compatibility cannot be preserved. In Go module practice, a new major version at v2 or later is not just a larger number; it is treated as a different module path, for example example.com/mymodule/v2.
The repository sources requested for this page show the other side of the same versioning story: how the Go toolchain records, compares, defaults, and reports Go versions. The type checker normalizes language versions to the form go1.N and uses those values to gate language features. The go command has a central gover package that defines the Go versions at which module-file semantics changed. The go version command reports toolchain and embedded build information, and telemetry code records host operating-system versions in a platform-specific way. Together, these files show why Go documentation distinguishes module release versions from Go language and toolchain versions. Sources: src/cmd/compile/internal/types2/version.go, src/cmd/go/internal/gover/version.go, src/cmd/go/internal/version/version.go
Core Concepts
A module version tells downstream users what kind of compatibility risk they are taking when they upgrade. In normal module release work, v1.2.3 means major version 1, minor version 2, and patch version 3. A patch release should be the safest kind of upgrade because it should not change the public API or dependency shape in a way that breaks callers. A minor release can add functionality while preserving compatibility. A major release communicates that users must review their code because public API or behavior may no longer be compatible with the previous major version.
Major-version module paths are Go’s way of making incompatible versions explicit in import paths and dependency graphs. When a module moves from v1 to v2, users do not merely ask for a newer release of the same import path. They update imports and requirements to the v2 module path. That design lets v1 and v2 appear as different modules from the dependency-management perspective, which helps avoid silently mixing incompatible APIs. It also means that releasing a major version creates an ongoing support decision: maintain the previous major version, deprecate it, or backport fixes selectively.
A Go version is different. In source files and module metadata, Go versions are used to describe the language and command semantics expected by the code. The compiler-side types2 version code defines an internal goVersion as a language version string of the form go1.N, explicitly excluding release numbers such as go1.20.1 from that normalized language-version value. It converts arbitrary Go version strings through go/version.Lang, compares versions with go/version.Compare, and uses allowVersion and verifyVersionf to decide whether a feature requiring a particular language version is available. Sources: src/cmd/compile/internal/types2/version.go
The go command also interprets the go line in go.mod and go.work files as a semantic switch for module behavior. The gover package defines DefaultGoModVersion as 1.16 for go.mod files without a go line, because the module graph changed at Go 1.17 to support graph pruning and the command must interpret old or hand-written files conservatively. It defines DefaultGoWorkVersion as 1.18 because workspaces were introduced then. Those defaults show that Go version handling is not cosmetic metadata; it affects how the command interprets requirements, workspaces, vendoring, and module graph pruning. Sources: src/cmd/go/internal/gover/version.go
Relevant Source Files
- src/cmd/compile/internal/types2/version.go — Defines the compiler type-checker representation of Go language versions, normalization through go/version.Lang, comparison through go/version.Compare, current-version construction from internal/goversion, and feature-gating helpers such as allowVersion and verifyVersionf.
- src/cmd/go/internal/gover/version.go — Centralizes Go-version thresholds used by the go command for module and workspace behavior, including default go.mod and go.work versions, graph-pruning-related versions, strict-version behavior, vendoring changes, and the go.mod tool directive threshold.
- src/cmd/go/internal/version/version.go — Implements the go version command, including flag handling for -m, -v, and -json, reporting the current toolchain version, scanning files or directories, and printing embedded runtime/debug.BuildInfo module information when available.
- src/cmd/go/internal/telemetrystats/version_unix.go — Records host Unix-like operating-system major and minor version telemetry by reading uname data and normalizing release strings.
- src/cmd/go/internal/telemetrystats/version_windows.go — Records Windows host major, minor, and build version telemetry through internal Windows version APIs.
- src/cmd/go/internal/telemetrystats/version_other.go — Provides the fallback telemetry path for platforms where host-version collection is not supported by the Unix or Windows implementations.
System-to-Code Mapping
The practical workflow begins when a module author chooses a release number. The repository files here do not replace the public module-versioning policy, but they explain why the Go command can act predictably once a module is consumed. The module version tells dependency management which release is being selected. The go line tells the command which Go-version semantics to use while interpreting the module. When those two version systems are kept separate, a project can publish v1.8.0 while still declaring go 1.21, or publish v2.0.0 while continuing to support an older language version, provided its code and dependencies allow that.
The gover constants are the most compact map of module-behavior transitions in the supplied code. NarrowAllVersion records the Go version where the module-mode all pattern stopped closing over test dependencies outside the main module. ExplicitIndirectVersion and SeparateIndirectVersion identify Go 1.17 behavior for explicit transitive requirements and separate indirect blocks. TidyGoModSumVersion marks a Go 1.21 checksum-preservation behavior for go mod tidy. GoStrictVersion marks the point where modules at that version or later must declare a go version at least as new as their dependencies, and where too-new versions become fatal. Sources: src/cmd/go/internal/gover/version.go
The type-checker version code maps version strings to language capabilities rather than module releases. It lists language-change milestones such as go1.18, go1.20, go1.21, go1.22, and later planned milestones, then compares an effective version against a required version. A checker can allow a feature when no version is set or when the effective version is at least the feature’s required version. When the feature is not allowed, verifyVersionf reports a version-specific error. This is the mechanism behind user-facing diagnostics that say a feature requires a newer Go version. Sources: src/cmd/compile/internal/types2/version.go
The go version command gives users and tooling a way to inspect the toolchain and binaries. With no file arguments, it prints the current Go runtime version and target platform. With file arguments, the command can examine recognized Go binaries. The -m flag prints embedded module version information, and -json produces runtime/debug.BuildInfo in JSON format, but only in combination with -m. This is the bridge between module versioning and built artifacts: the module graph used to build a binary can be embedded and inspected after compilation. Sources: src/cmd/go/internal/version/version.go
Compact Reference
| Name | Kind | Meaning |
|---|---|---|
| goVersion | compiler internal type | Normalized Go language version string in the form go1.N; release numbers are removed by go/version.Lang. |
| asGoVersion(v string) | compiler helper | Converts a Go version string to a language-version value, returning an empty value for invalid versions. |
| allowVersion(want goVersion) | type-checker helper | Reports whether the effective file or package version can use a feature requiring want. |
| DefaultGoModVersion | go command constant | Assumes Go 1.16 for go.mod files without a go line. |
| DefaultGoWorkVersion | go command constant | Assumes Go 1.18 for go.work files without a go line. |
| ExplicitIndirectVersion | go command constant | Marks Go 1.17 behavior requiring explicit requirements for modules that provide transitively imported packages. |
| GoStrictVersion | go command constant | Marks Go 1.21 strict-version behavior for module go lines and too-new versions. |
| GoModToolVersion | go command constant | Marks Go 1.24 as the required version for the tool directive in go.mod. |
| go version -m | command option | Prints embedded module version information for Go binaries when available. |
| go version -m -json | command option | Emits runtime/debug.BuildInfo in JSON form. |
Use this reference when deciding what a version number means in a bug report or release review. If the number begins with v and belongs to a module requirement or repository tag, interpret it as a module release version and apply semantic-versioning rules. If the number appears in a go directive, compiler error, toolchain report, or gover threshold, interpret it as a Go language or command-semantics version. Mixing those categories leads to common mistakes, such as assuming that a module’s v2 release requires Go 1.2, or that declaring go 1.22 automatically publishes a new module release.
Execution Flow
A typical consumer workflow starts with a dependency upgrade. The developer sees that a dependency offers v1.9.1, v1.10.0, or v2.0.0. The first should be treated as a patch-level compatibility-preserving update, the second as a backward-compatible feature update, and the third as a possible breaking change that may require an import-path change. After the requirement is selected, the go command reads the module files and uses Go-version semantics from the go line, falling back to the gover defaults when a file lacks that line. Those semantics can change graph pruning, indirect requirements, vendoring, checksum retention, and strictness.
A typical author workflow starts with the public API. If the change fixes an implementation bug, a patch release is usually appropriate. If it adds API without breaking existing users, a minor release is usually appropriate. If it removes, renames, or changes API in a way that breaks users, the author should consider whether the disruption is necessary and, if so, release a new major version with the corresponding module path. Separately, the author should set the go line according to the language features and module semantics the code actually requires, not according to the module release number.
After a binary is built, version information remains observable through go version. A developer can run go version to identify the toolchain used by the current command, or run go version -m on a binary to inspect embedded module information. When JSON output is needed for automation, the command requires -m -json together. The implementation also protects users from flag combinations that do not make sense without file arguments, which helps keep the command’s output stable for scripts and diagnostics. Sources: src/cmd/go/internal/version/version.go
Platform and Telemetry Signals
The telemetry version files are not module-versioning logic, but they show another important use of version numbers in the Go command: environment diagnostics. On Unix-like systems, the telemetry code calls uname, extracts a major and minor version from the release string, handles AIX specially, and increments counters named with the host operating-system version. On Windows, it records major, minor, and build values. On unsupported platforms, it records a version-not-supported counter instead. Sources: src/cmd/go/internal/telemetrystats/version_unix.go, src/cmd/go/internal/telemetrystats/version_windows.go, src/cmd/go/internal/telemetrystats/version_other.go
These platform counters are deliberately separate from module release versions and Go language versions. They help the Go project understand the host environments where the go command runs, while module versions help developers reason about dependency compatibility and Go versions help tools select language and module-file behavior. Keeping those meanings separate makes documentation, diagnostics, and support conversations more precise. When reporting an issue, include the module version involved, the go line from the module if relevant, the output of go version, and the host platform when environment-specific behavior is suspected.
Next Steps
When choosing a version for your own module, start from compatibility: patch for compatible fixes, minor for compatible additions, and major only for intentional breaking changes. When consuming a dependency, treat v2 and later as a different module path and plan for import updates. When debugging tool behavior, inspect the go line and compare it with the thresholds in the go command’s version logic. For related workflows, read the pages on Modules Overview, go.mod Reference, Managing Dependencies, Publishing Modules, and Module Release Workflow.