Modules and Tooling
Series
Go for JVM EngineersJava engineers new to Go often underestimate the toolchain because it ships as a single binary. No Maven wrapper, no Gradle daemon, no plugin ecosystem to configure. That simplicity is intentional and has real consequences for build reproducibility, dependency management, and CI pipelines.
This post maps Maven/Gradle concepts to their Go equivalents, covers the module graph, and walks through workspaces for multi-module development.
go.mod — The pom.xml Equivalent
A Go module is a collection of packages with a single root go.mod file. The module path is the canonical import prefix for all packages in the module.
module github.com/acme/myservice
go 1.23
require (
github.com/go-chi/chi/v5 v5.1.0
github.com/jackc/pgx/v5 v5.7.1
)Maven's pom.xml equivalent fields:
| Maven | go.mod |
|---|---|
<groupId>/<artifactId> |
module path |
<version> |
module version tag on VCS |
<dependencies> |
require block |
<dependencyManagement> |
go.sum + MVS |
Go uses Minimal Version Selection (MVS) instead of range-based resolution. If package A requires lib v1.2 and package B requires lib v1.3, Go selects v1.3 — the minimum version that satisfies all requirements. There is no "latest" resolution by default, which makes builds reproducible without a lock file format.
go.sum records the expected hash of every dependency. It is the equivalent of a lock file but is committed to source control and verified on every build.
Common Module Commands
# initialise a new module
go mod init github.com/acme/myservice
# add a dependency (downloads and updates go.mod + go.sum)
go get github.com/go-chi/chi/v5@v5.1.0
# tidy: remove unused, add missing deps
go mod tidy
# vendor: copy all deps into ./vendor (useful for air-gapped builds)
go mod vendor
# list the module dependency graph
go mod graph
# explain why a dependency is required
go mod why github.com/jackc/pgx/v5The Module Cache
Dependencies are downloaded once into $GOPATH/pkg/mod (default: ~/go/pkg/mod). Unlike Maven's ~/.m2/repository, the module cache is content-addressed and immutable — a version downloaded once is never mutated. In CI, cache this directory to avoid re-downloading.
# GitHub Actions cache example
- uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: go-mod-${{ hashFiles('**/go.sum') }}Workspaces — Multi-Module Local Development
Go 1.18 introduced workspaces for developing multiple modules together without replace directives. This is the equivalent of Maven's local reactor build or Gradle's composite build.
# In the parent directory containing both modules
go work init ./myservice ./mylib
# go.work file created:
# go 1.23
# use (
# ./myservice
# ./mylib
# )With go.work present, import paths that resolve to a used module are served from the local directory rather than the module cache. Commit go.work.sum but not go.work itself (add it to .gitignore) — it is a local development artifact.
# Add another module to the workspace
go work use ./anotherlib
# Sync go.work.sum
go work syncBuild, Test, and Install
The Go toolchain ships commands that cover what Maven plugins handle through configuration:
| Maven/Gradle task | Go command |
|---|---|
mvn compile |
go build ./... |
mvn test |
go test ./... |
mvn package |
go build -o bin/server ./cmd/server |
mvn install |
go install ./... |
./gradlew lint |
go vet ./... |
| Checkstyle / SpotBugs | staticcheck ./... (external) |
mvn dependency:tree |
go mod graph |
Cross-compilation is a first-class feature — no plugins required:
# Build a Linux AMD64 binary from a Mac
GOOS=linux GOARCH=amd64 go build -o dist/server-linux ./cmd/server
# Build for ARM64 (e.g., AWS Graviton)
GOOS=linux GOARCH=arm64 go build -o dist/server-arm64 ./cmd/serverThe output is a single static binary with no runtime dependency. No JVM, no classpath, no lib/ directory to ship alongside it.
Code Generation
Go uses go generate as an explicit, tool-agnostic generation step. It reads //go:generate directives in source files and runs arbitrary commands.
//go:generate go run github.com/sqlc-dev/sqlc/cmd/sqlc generate
//go:generate mockgen -source=service.go -destination=mocks/service_mock.gogo generate ./...This is the Go equivalent of Maven's code generation plugins (JAXB, Lombok annotation processing) but explicit and transparent — the generation command is in the source file and visible in code review.
Linting and Formatting
Go ships gofmt — formatting is not optional and is not configurable (unlike Checkstyle, which requires extensive rule files). Running gofmt -w . reformats all files to the canonical style. Most editors run it on save.
# Format all files in place
gofmt -w .
# Vet: catches common mistakes (shadowed variables, wrong Printf args, etc.)
go vet ./...
# golangci-lint: aggregates 50+ linters, widely used in CI
golangci-lint run ./...Add golangci-lint to CI but accept its defaults first — the default enabled linters catch real bugs without noisy style opinions.
Key Takeaways
go.modis the module manifest equivalent topom.xml; Minimal Version Selection makes builds reproducible without a separate lock-file format beyondgo.sum.go mod tidyis the equivalent of reconcilingpom.xmldependencies after editing — run it before committing dependency changes.- The module cache at
~/go/pkg/modis content-addressed and immutable; cache it in CI by hashinggo.sum. - Go workspaces (
go.work) enable multi-module local development withoutreplacehacks — keepgo.workin.gitignore, it is a developer artifact. - Cross-compilation to any target OS/architecture is a one-liner with
GOOSandGOARCH— the output is a static binary with no runtime dependency to ship. - Formatting is canonical and non-negotiable —
gofmteliminates style debates, andgo vetcatches correctness issues; addgolangci-lintfor deeper analysis.