Skip to content
Go forJava Developers

Tooling & Deployment

One go command instead of a build tool, formatting and linting, static binaries and small containers, embedded assets, and tuning the GC instead of -Xmx.

Section 28 of 314 min read

On this page
  1. The go command
  2. Formatting and linting
  3. Building: one static binary
  4. Containers
  5. Embedding files in the binary
  6. Tuning the garbage collector

A Java project assembles its toolchain from parts: a JDK, Maven or Gradle, plugins for formatting, linting, coverage and packaging, and a JVM at runtime. Go puts almost all of it in one go command and produces a single native executable with no runtime to install.

The go command

JavaGo
mvn compile / gradle buildgo build ./...
mvn exec:java / gradle rungo run ./cmd/api
mvn testgo test ./...
mvn install (a CLI tool)go install example.com/tool@latest
Spotless / google-java-formatgofmt (and goimports)
Compiler warnings + ErrorPronego vet ./...
OpenRewrite migrationsgo fix ./... (modernizers, Go 1.26+)
Javadocgo doc, and pkg.go.dev for published modules
Annotation processorsgo generate + //go:generate comments
OWASP dependency-checkgovulncheck ./...

Formatting and linting

  • gofmt settles style. Tabs, spacing and alignment are not configurable, so they are never debated in code review. Run it on save; goimports also adds and removes import lines.
  • go vet catches real bugs: mismatched Printf verbs, copied mutexes, unreachable code, lost context cancel functions, malformed struct tags. go test runs a subset automatically.
  • staticcheck is the most respected third-party analyser. golangci-lint runs it together with dozens of other linters (errcheck, gosec, revive, exhaustive) from one config file.
  • go fix (revamped in Go 1.26) rewrites code to use newer idioms and APIs, for example replacing hand-written loops with slices.Contains or interface{} with any.
  • govulncheck reports known vulnerabilities only when your code actually calls the affected function, which keeps the signal high.

Building: one static binary

build flags you will actually use
# A static binary with no libc dependency (pure-Go code)
CGO_ENABLED=0 go build -o bin/api ./cmd/api

# Cross-compile: no toolchain to install for the target
GOOS=linux GOARCH=arm64 go build -o bin/api-linux-arm64 ./cmd/api
GOOS=windows GOARCH=amd64 go build -o bin/api.exe ./cmd/api

# Stamp a version at link time; strip paths for reproducible builds
go build -trimpath -ldflags "-s -w -X main.version=v1.4.2" -o bin/api ./cmd/api

# Which module versions went into a binary? (build info is embedded)
go version -m bin/api

What changes when there is no JVM

Startup takes milliseconds rather than seconds, with no warm-up: code is compiled ahead of time, so there is no JIT and no class loading. That makes Go a good fit for CLIs, serverless functions and fast autoscaling.

Memory for a small service is typically tens of megabytes, not hundreds. There is no heap to pre-size and no metaspace.

Deployment is one file. The same idea as a GraalVM native image, except it is the default and builds in seconds.

Containers

a multi-stage Dockerfile for a Go service
# ---- build stage ----
FROM golang:1.27 AS build
WORKDIR /src

# Cache dependencies separately from source changes
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o /out/api ./cmd/api

# ---- runtime stage: no shell, no package manager ----
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/api /api
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/api"]

Embedding files in the binary

Java — classpath resources
// src/main/resources/templates/welcome.html
try (InputStream in = getClass()
        .getResourceAsStream("/templates/welcome.html")) {
    String html = new String(in.readAllBytes(), UTF_8);
}
Go — //go:embed (Go 1.16+)
import "embed"

// The files are compiled into the binary at build time
//go:embed templates/*.html
var templates embed.FS

//go:embed schema.sql
var schema string

//go:embed static
var static embed.FS

html, err := templates.ReadFile("templates/welcome.html")

// embed.FS plugs into http.FileServerFS and html/template
mux.Handle("GET /static/", http.FileServerFS(static))

Tuning the garbage collector

JavaGo
-Xmx2g: a hard heap limitGOMEMLIMIT=1800MiB: a soft limit; GC works harder as it approaches
-XX:+UseG1GC, ZGC, Shenandoah…One concurrent, low-latency GC (Green Tea, the default since Go 1.26)
Dozens of GC tuning flagsGOGC (default 100): heap growth before the next cycle
-XX:ActiveProcessorCountGOMAXPROCS, container-aware by default since Go 1.25
JIT profiling at runtimeProfile-guided optimisation: commit a default.pgo CPU profile
↑ ↓ to navigateEnter to openEsc to close