Skip to main content
Go for JVM Engineers

Modules and Tooling

Ravinder··5 min read
GoJVMJavaModulesToolingBuild
Share:
Modules and Tooling

Java 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/v5
flowchart TD A[go.mod\nrequire block] -->|MVS resolution| B[go.sum\nhash verification] B -->|go build| C[module cache\n~/.cache/go/pkg/mod] C -->|compile| D[static binary] E[go get] -->|updates| A F[go mod tidy] -->|prunes+adds| A

The 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 sync

Build, 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/server

The 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.go
go 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.mod is the module manifest equivalent to pom.xml; Minimal Version Selection makes builds reproducible without a separate lock-file format beyond go.sum.
  • go mod tidy is the equivalent of reconciling pom.xml dependencies after editing — run it before committing dependency changes.
  • The module cache at ~/go/pkg/mod is content-addressed and immutable; cache it in CI by hashing go.sum.
  • Go workspaces (go.work) enable multi-module local development without replace hacks — keep go.work in .gitignore, it is a developer artifact.
  • Cross-compilation to any target OS/architecture is a one-liner with GOOS and GOARCH — the output is a static binary with no runtime dependency to ship.
  • Formatting is canonical and non-negotiable — gofmt eliminates style debates, and go vet catches correctness issues; add golangci-lint for deeper analysis.
Share: