Skip to content
JavaGo
Part I · Orientation

Dependency Management

Modules, go.mod and go.sum in place of Maven or Gradle: adding dependencies, major versions, local development and private repositories.

Section 02 of 31 6 min read

Every Java project starts with a pom.xml or build.gradle and a build tool to interpret it. Go has one built-in module system driven by the go command itself: no plugins, no wrapper scripts, no build language.

Initialising a module

Java — Maven or Gradle scaffolding
# Maven archetype
mvn archetype:generate \
  -DgroupId=com.mycompany \
  -DartifactId=my-app \
  -DarchetypeArtifactId=maven-archetype-quickstart

# Gradle
gradle init --type java-application
Go — go mod init
# One command, one file
go mod init github.com/mycompany/my-app

# go.mod now contains:
#   module github.com/mycompany/my-app
#
#   go 1.27.1

go.mod and go.sum

Java — pom.xml
<project>
  <groupId>com.mycompany</groupId>
  <artifactId>my-app</artifactId>
  <version>1.0.0</version>

  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.19.0</version>
    </dependency>
  </dependencies>
</project>
Go — go.mod
module github.com/mycompany/my-app

go 1.26.0

require (
    github.com/jackc/pgx/v5 v5.7.5
    golang.org/x/sync v0.16.0
)

require (
    // Pulled in by the modules above
    github.com/jackc/puddle/v2 v2.2.2 // indirect
)
JavaGo
pom.xml / build.gradlego.mod
gradle.lockfile (opt-in)go.sum: always present, commit it
~/.m2/repositoryThe module cache: go env GOMODCACHE
mvnw / gradlewThe go command; a toolchain line can pin the exact Go version
mvn package / gradle buildgo build ./...
mvn dependency:resolvego mod download
mvn dependency:treego mod graph or go list -m all
mvn dependency:analyzego mod tidy: adds missing and removes unused requirements

Minimal version selection

Maven resolves conflicts with "nearest definition wins", which can silently pick an older version than a library asked for. Go uses minimal version selection: for each module, the build uses the highest version that any require line in the graph asks for, and never anything newer. The result is deterministic, needs no lock-file solver, and changes only when a go.mod file changes.

Maven: nearest definition wins

Go: minimal version selection

Fig. 2.1The same dependency graph resolved two ways. Maven's nearest definition picks the C closest to your project, even though D needs a newer one. Go's minimal version selection uses the highest version any require line asks for, and never anything newer.

Adding and removing dependencies

day-to-day dependency commands
# Add or upgrade a dependency (updates go.mod and go.sum)
go get github.com/jackc/pgx/v5@v5.7.5
go get github.com/jackc/pgx/v5@latest

# Upgrade all direct and indirect dependencies (minor/patch)
go get -u ./...

# Add missing requirements, drop unused ones
go mod tidy

# Pre-fetch everything into the module cache (useful in CI images)
go mod download

# Why is this module in my build?
go mod why -m github.com/jackc/puddle/v2

# Which dependencies have newer versions?
go list -m -u all

Semantic import versioning

Go has a rule with no Maven or Gradle equivalent: a module that releases a breaking v2 or later changes its module path to end in /v2. The major version is part of the import path, so two major versions are simply two different packages.

v1 and v2 of the same library in one build
// go.mod
require (
    github.com/some/lib v1.5.0    // v0/v1: no suffix
    github.com/some/lib/v2 v2.1.0 // v2+: suffix in the path
)

// Each major version is a separate import path
import (
    libv1 "github.com/some/lib"
    libv2 "github.com/some/lib/v2"
)

// Both compile into the same binary without clashing.
// In Java, two versions of one artifact on the classpath
// means shading or a runtime NoSuchMethodError.

Working on several modules at once

Java — mvn install a SNAPSHOT
# Build and install the library locally
cd ../shared-lib
mvn install   # publishes 1.0-SNAPSHOT to ~/.m2

# Then depend on the SNAPSHOT version
# from the application's pom.xml
Go — a go.work workspace
# From a parent directory holding both repos
go work init ./my-app ./shared-lib

# go.work now says: build my-app against the
# local shared-lib checkout, not the published one.
# Keep go.work out of version control.

cd my-app && go build ./...

The older alternative is a replace directive in go.mod, for example replace github.com/mycompany/shared-lib => ../shared-lib. It works, but it is easy to commit by accident. go.work (Go 1.18+) is the better tool for local multi-module development because it lives outside any module.

Pinning tools with the tool directive (Go 1.24+)

Code generators and linters need pinned versions just like libraries. Before Go 1.24 the idiom was a tools.go file with a //go:build tools constraint and blank imports. Go 1.24 added a tool directive to go.mod.

Java — pinned build plugin
<build>
  <plugins>
    <plugin>
      <groupId>org.openapitools</groupId>
      <artifactId>openapi-generator-maven-plugin</artifactId>
      <version>7.14.0</version>
    </plugin>
  </plugins>
</build>
Go 1.24+ — tool directive
// Add a tool: records it in go.mod and go.sum
//   go get -tool github.com/sqlc-dev/sqlc/cmd/sqlc@latest

// go.mod then contains
tool github.com/sqlc-dev/sqlc/cmd/sqlc

// plus a matching require line. Run it with
//   go tool sqlc generate
// Everyone on the team gets the same version.

Private modules

GOPRIVATE for company repositories
# Modules matching GOPRIVATE bypass the public proxy
# (proxy.golang.org) and the public checksum database.
go env -w GOPRIVATE='github.com/mycompany/*'

# GOPRIVATE is the default for both GONOPROXY and GONOSUMDB;
# set those separately only if you run a private proxy.

# Authentication is plain git. For example, force SSH:
git config --global url."git@github.com:".insteadOf "https://github.com/"
↑ ↓ to navigateEnter to openEsc to close