Question
Given a Go module at a fixed commit, several commands produce different go.mod files:
git clone git://github.com/walles/moar
cd moar
git checkout d24acdbf
On a clean clone:
go get -u
produces a go.mod file with 19 lines.
go get -u
go mod tidy
produces 14 lines, while:
go mod tidy
produces 13 lines.
Deleting all entries in the require block before running either command produces still different results. Why do these approaches produce different dependency requirements, and what is the correct way to update a Go module to the newest compatible versions of its dependencies?
Short Answer
You will learn the separate responsibilities of go get -u and go mod tidy, why Go does not simply select the newest version of every module, and a practical workflow for upgrading dependencies while keeping go.mod and go.sum consistent.
Concept
Go modules track dependencies in two related files:
go.moddeclares the module's dependency requirements and Go version.go.sumstores checksums for module content used during builds and dependency resolution.
Two commands are often confused because they both can modify these files, but they solve different problems:
go get -uchanges versions. It upgrades eligible modules in the dependency graph, subject to Go's module version rules.go mod tidycleans the module definition. It adds requirements needed by the source code and removes requirements no longer needed.
Go uses Minimal Version Selection (MVS). Rather than choosing the newest release of every module blindly, it selects one version of each module: the highest version required anywhere in the complete dependency graph. This gives repeatable builds and avoids unnecessary upgrades.
As a result, an upgrade is not guaranteed to mean “the latest version of every indirect module.” A direct dependency may require a particular version of another module, and MVS selects the highest required version that is actually reachable from the build list.
The exact contents and formatting of go.mod can also vary across Go releases. In particular, modern Go versions may record indirect dependencies differently because of module graph pruning and explicit indirect requirements.
Mental Model
Think of go.mod as a packing list for a trip.
go get -uis like replacing items on the list with newer models. It focuses on upgrading versions.go mod tidyis like comparing the packing list with your actual itinerary. It removes items you no longer need and adds items that the itinerary proves you need.- MVS is the rule that says: if several people request different versions of the same item, bring the newest version that anyone explicitly requires—not necessarily the newest item sold in the store.
Deleting the require block is like throwing away the packing list and asking Go to reconstruct it from the source code. It may work, but it is an unnecessarily disruptive way to manage normal dependency updates.
Syntax and Examples
The usual commands are:
# Upgrade dependencies used by packages in this module.
go get -u ./...
# Recalculate required modules and remove unused requirements.
go mod tidy
# Verify that dependencies and checksums are consistent.
go mod verify
# Run the project tests after upgrading.
go test ./...
For a smaller, lower-risk upgrade that only permits patch releases:
go get -u=patch ./...
go mod tidy
go test ./...
To upgrade one dependency deliberately:
go get example.com/library@latest
go mod tidy
go test ./...
For example, if your code imports:
import "github.com/acme/widget"
you can update just that dependency:
go get github.com/acme/widget@latest
go mod tidy
Use ./... when you intend to consider packages throughout the current module. It makes the command's package scope explicit.
Step by Step Execution
Consider this module:
// cmd/report/main.go
package main
import (
"fmt"
"example.com/colors"
)
func main() {
fmt.Println(colors.Blue("ready"))
}
Suppose its go.mod initially contains:
module example.com/report
go 1.22
require example.com/colors v1.2.0
Now run:
go get -u ./...
Step by step:
- Go loads packages represented by
./.... - It sees that
cmd/reportimportsexample.com/colors. - It examines the module graph rooted at the current requirements.
- It upgrades eligible modules to newer compatible versions according to
-uand MVS. - It writes changed version requirements to
go.modand needed checksums togo.sum.
Next, run:
go mod tidy
- Go examines imports in packages in the main module, including relevant test dependencies.
- It ensures each needed imported package has a module requirement.
- It removes requirements that are no longer needed under the current module graph rules.
- It updates
go.sumto retain checksums required for reproducible module operations.
Therefore, tidy after an upgrade can make go.mod shorter: the upgrade command may have left requirements that are no longer necessary after the graph is recalculated.
Real World Use Cases
Common dependency-management tasks include:
- Routine maintenance: Run patch upgrades, test the application, and commit the resulting
go.modandgo.sumchanges. - Security remediation: Upgrade a vulnerable direct dependency to a fixed version, then run
go mod tidyand the test suite. - Library upgrades: Update one API client, database driver, or framework at a time to make regressions easier to identify.
- Removing code: After deleting a feature and its imports, run
go mod tidyto remove dependencies that the module no longer needs. - CI validation: Use
go mod tidyin a check that fails when developers forgot to commit required module-file updates. - Investigating a version: Use
go list -m -versions example.com/moduleto inspect available versions before choosing an upgrade target.
Real Codebase Usage
A conservative dependency-update workflow in a production repository is usually:
# Inspect what can change first.
go list -m -u all
# Prefer low-risk patch updates when appropriate.
go get -u=patch ./...
go mod tidy
go test ./...
go vet ./...
git diff -- go.mod go.sum
For a major or behavior-changing dependency, teams commonly upgrade it separately:
go get github.com/acme/client/v2@v2.4.0
go mod tidy
go test ./...
Useful project patterns include:
- Small upgrade commits: Keep one dependency or a related set of upgrades per pull request.
- Tests after every update: Successful module resolution does not prove API compatibility or runtime correctness.
- Reviewing diffs: Check unexpected major version changes, replaced modules, and large indirect dependency changes.
- Reproducible builds: Commit both
go.modandgo.sum; do not treatgo.sumas disposable output. - Explicit version pinning when needed: Use a specific version such as
@v1.8.3when an upgrade must be controlled rather than automatically selected.
Common Mistakes
Treating go get -u as a cleanup command
go get -u
This is primarily an upgrade operation. It may not remove every requirement that is no longer necessary.
Avoid it: Follow upgrades with:
go mod tidy
Expecting “latest” to be universally safe
go get -u ./...
A newer minor version can change behavior, deprecate APIs, or expose assumptions in your code. Major versions can require import-path changes, such as /v2.
Avoid it: Review the diff, read release notes for important modules, and run tests.
Deleting the entire require block for normal upgrades
require (
// deleted manually
)
This can force Go to reconstruct requirements and may create a large, confusing diff. It also obscures the intentional dependency history of the change.
Avoid it: Use go get to change versions and go mod tidy to remove unused requirements. Manual removal is best reserved for deliberate repair or investigation.
Forgetting test-only dependencies
A package used only in _test.go files can still be required by the module.
Avoid it: Run go mod tidy and go test ./..., not only go build for one command.
Editing go.sum to fix dependency problems
Deleting checksum lines may only hide the real issue temporarily and can cause unnecessary downloads or diffs.
Avoid it: Let Go manage checksums through normal commands such as go mod tidy, go get, and go mod download.
Assuming line count indicates correctness
A shorter go.mod is not automatically better. The correct file is the one that accurately represents the dependencies needed by the module under your Go toolchain's rules.
Avoid it: Judge changes by builds, tests, intended versions, and a reviewed diff—not by the number of lines.
Comparisons
| Command or action | Primary purpose | Changes versions? | Removes unused requirements? | Typical use |
|---|---|---|---|---|
go get -u ./... | Upgrade eligible dependencies | Yes | Not its main job | Routine dependency upgrade |
go get -u=patch ./... | Upgrade only patch versions | Yes, patch only | Not its main job | Lower-risk maintenance |
go get module@version | Select a specific dependency version | Yes | Not its main job | Targeted upgrade or downgrade |
go mod tidy | Synchronize module files with source imports | It can resolve required versions, but is not an “upgrade all” command | Yes | Cleanup after code or dependency changes |
go mod download | Download modules into the cache | No intended version selection | No | Prefetching dependencies in automation |
Manual require deletion | Force requirements to be rediscovered | Indirectly | Indirectly | Debugging or exceptional repair, not routine maintenance |
go mod tidy answers: What dependencies does this source code need?
go get -u answers: What newer versions should Go select for the dependencies in scope?
Cheat Sheet
# See selected modules with available upgrades.
go list -m -u all
# Upgrade dependencies used by all packages in this module.
go get -u ./...
# Only permit patch upgrades.
go get -u=patch ./...
# Upgrade one module to its latest version.
go get example.com/module@latest
# Choose an exact version.
go get example.com/module@v1.2.3
# Add missing requirements and remove unnecessary ones.
go mod tidy
# Test all packages.
go test ./...
# Check downloaded modules against go.sum.
go mod verify
Key rules:
- Commit both
go.modandgo.sum. - Run
go mod tidyafter dependency or import changes. - Run tests after any upgrade.
- MVS selects the highest version required in the graph; it does not blindly install every latest release.
- Prefer targeted or patch-only upgrades when reducing upgrade risk matters.
- Module-file details can differ between Go toolchain versions.
FAQ
Why does go get -u change more modules than the one I care about?
Dependencies have their own dependencies. Updating one selected module can change the version requirements in the overall module graph.
Does go mod tidy update every dependency to the latest version?
No. Its main purpose is to make go.mod match the imports used by the module. Use go get -u ./... when you explicitly want upgrades, then run go mod tidy.
What is the right command to update all Go dependencies?
A practical command sequence is:
go get -u ./...
go mod tidy
go test ./...
Use go get -u=patch ./... when you want patch-only upgrades.
Why does go.mod contain indirect dependencies?
Some indirect requirements are recorded to make version selection reproducible and to support Go's module graph rules. The exact representation can differ by Go version.
Should I delete unused dependencies manually from go.mod?
Usually no. Run go mod tidy, which calculates what is needed from your source code and module graph.
Why is my go.sum still large after go mod tidy?
go.sum can contain checksums needed for reproducible resolution of modules and their go.mod files. Its size is not a reliable sign of a problem.
Can dependency upgrades break a Go program even when it compiles?
Yes. A dependency may change runtime behavior, defaults, protocol behavior, or edge-case handling. Run unit, integration, and relevant end-to-end tests.
Why do I get different results with another Go version?
The Go toolchain evolves, including how it records module requirements and prunes dependency graphs. Use a pinned Go version in CI and teams when reproducibility matters.
Mini Project
Description
Create a repeatable dependency-update script for a Go module. The script performs patch-level upgrades, tidies the module files, verifies downloaded modules, and runs the test suite. This reflects a cautious workflow often used in application repositories.
Goal
Produce a shell script that safely performs a patch dependency update and stops if any verification or test step fails.
Requirements
Use go get -u=patch ./... to request patch-level updates.
Run go mod tidy after the upgrade.
Run go mod verify before testing.
Run go test ./....
Print a success message only after every command succeeds.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.