go build is the whole build system
You are used to a build tool that is also a dependency manager that is also a plugin host. Go splits that into
one tool that does all three badly-named things, plus gofmt which ends every formatting argument you have ever had.
The command map
| Maven / Gradle | Go | Notes |
|---|---|---|
mvn compile |
go build ./... |
compiles to a throwaway cache, not target/ |
mvn package |
go build -o bin/app ./cmd/app |
produces one static binary |
mvn test |
go test ./... |
tests are source files, not a plugin |
mvn dependency:tree |
go mod graph |
far smaller, because there is no transitive config |
mvn versions:display-dependency-updates |
go list -m -u all |
|
mvn spring-boot:run |
go run ./cmd/app |
no devtools restart needed |
mvn deploy / Nexus |
go get module@version + commit go.sum |
the module proxy is the registry |
checkstyle + spotless |
gofmt -l . |
one canonical format, zero config |
pom.xml |
go.mod + go.sum |
10 lines vs 200 |
-Xmx2g at runtime |
build flags / GOMEMLIMIT |
tuning is a build+runtime knob, covered in M9 |
Dependencies are pinned by content hash in go.sum and by the Minimum Version Selection rule: your build uses
the maximum of the minimum versions anyone asked for. There is no "nearest-wins" conflict resolution to reason about,
which is why Go projects do not have dependency hell stories.
The same program, three toolchains
package main
import "fmt"
func main() {
fmt.Println("hello from a static binary")
}
public final class Hello {
public static void main(String[] args) {
System.out.println("hello from the JVM");
}
}
// needs tsconfig + node + a bundler (or tsx) to become runnable
const greet = (): string => 'hello from node';
console.log(greet());
Build and run each:
# Go: 8 MB self-contained binary, no runtime on the target
go build -o bin/hello . && ./bin/hello && file bin/hello
# Java: 5 KB class + a 200 MB JVM on every host
javac Hello.java && java Hello
# TS: transpile, then run under node
npx tsx index.ts
Module layout you will actually use
go.mod is the whole project config:
module github.com/vlad/gopherpath
go 1.27
require (
github.com/yuin/goldmark v1.7.4
github.com/alecthomas/chroma/v2 v2.15.0
)
The go 1.27 line is not cosmetic — it sets language semantics for the module (loop-variable scoping, generics
behaviour). Bumping it is a deliberate, reviewed change, the way you treat <maven.compiler.release>.
Trap:
go getwrites to yourgo.mod. There is no separate "install" step and no lockfile to regenerate by hand. If a build works on your machine and not in CI, the answer is almost always a missinggo.sumentry — commit both files.
Proof checkpoint
go mod init example.com/helloin an empty dir, paste the Go program above intomain.go, rungo build -o bin/hello .andfile bin/hello— note "statically linked".- Run
go vet ./...andgofmt -l .— both should be silent. These two commands are yourspotlessApply. - Copy the binary to another directory (or a scratch container) and run it there with no Go installed.
- Break the formatting on purpose, run
gofmt -w ., and look at the diff. That is the last formatting debate you will have.