
Go 1.25 shipped in August 2025, and the headline feature is not a language change. It is that your Go app running in Kubernetes will finally behave correctly without third-party patches. GOMAXPROCS now reads your container’s CPU limit automatically. If you have ever imported go.uber.org/automaxprocs as a blank import in main.go and wondered why that was not just built into the runtime — the answer is: now it is.
A Ten-Year Gap in the Runtime
Go has been setting GOMAXPROCS to the host machine’s total CPU count since Go 1.5 in 2015. In a container with a CPU limit, this created a quiet performance problem. Your app believed it had 128 cores available (the node), but the Linux kernel enforced a 4-CPU limit by throttling execution — suspending the process for up to 100ms at a time whenever it burned through its CPU budget for the period. Average latency looked fine. Tail latency was a disaster.
The Go community’s answer was go.uber.org/automaxprocs, a library from Uber that reads cgroup limits and sets GOMAXPROCS at startup. It earned 2,000+ GitHub stars — a reliable proxy for how widespread the pain was. Java fixed the same problem in Java 10 (2018) with UseContainerSupport enabled by default in Java 11. Go took until 2025. That gap is worth naming.
Go 1.25 fixes it cleanly. The runtime now reads cgroup v2 CPU bandwidth limits on Linux, sets GOMAXPROCS to the container’s CPU limit (or the host core count if no limit is set), and periodically re-checks in case Kubernetes adjusts limits at runtime. No code changes required — upgrade and it just works. The Go team credited feedback from the automaxprocs maintainers in their design, which is a decent way to retire a decade-old workaround. You can drop the blank import.
One nuance: the feature is based on CPU limits, not CPU requests. A pod with a request but no limit can still consume idle node CPU, so capping at the request value would be too conservative. If your container has no CPU limit set, behavior is unchanged from previous releases. To opt out entirely, set GODEBUG=containermaxprocs=0.
WaitGroup.Go(): Finally
Concurrency ergonomics get a meaningful quality-of-life upgrade with sync.WaitGroup.Go(). The classic WaitGroup pattern had two failure modes that have generated enough blog posts to fill a library: calling Add(1) inside the goroutine body (a race condition if the scheduler fires before Add executes), and forgetting defer wg.Done() (a deadlock). WaitGroup.Go() eliminates both.
// Before Go 1.25
var wg sync.WaitGroup
for _, task := range tasks {
wg.Add(1)
go func(t Task) {
defer wg.Done()
process(t)
}(task)
}
wg.Wait()
// Go 1.25
var wg sync.WaitGroup
for _, task := range tasks {
wg.Go(func() { process(task) })
}
wg.Wait()
If you need error propagation — where one failing goroutine should cancel the rest — golang.org/x/sync/errgroup remains the right tool. It has offered this API style for years and adds context cancellation on failure. WaitGroup.Go() is for the simpler case: fire goroutines, wait for all to complete, no error bubbling needed.
Built-In CSRF Protection
Go 1.25 adds net/http.CrossOriginProtection, a header-based CSRF defense that needs no tokens, no secret keys, and no shared state. It validates the Sec-Fetch-Site header (supported by all major browsers since around 2023) and rejects cross-origin state-mutating requests. Safe methods — GET, HEAD, OPTIONS — are always allowed through.
cop := &http.CrossOriginProtection{}
http.ListenAndServe(":8080", cop.Handler(mux))
Two caveats: browsers released before approximately 2020 may not send Sec-Fetch-Site, leaving those requests unprotected. And the feature requires HTTPS in production to function correctly. Treat it as a defense-in-depth layer rather than a standalone solution for APIs serving a heterogeneous client base.
Tooling and Experiments Worth Watching
Several smaller tooling additions are worth adding to your workflow. go doc -http spins up a local documentation server and opens your browser — handy during development or in air-gapped environments. Two new go vet analyzers ship: waitgroup catches incorrect WaitGroup.Add() placement at build time, and hostport flags malformed host:port string construction. The new ignore directive in go.mod lets monorepo teams exclude specific directories from module resolution.
On the experimental side, two features deserve attention in non-critical environments. The new encoding/json/v2 package (GOEXPERIMENT=jsonv2) delivers faster JSON decoding and fixes behavioral quirks that the v1 package has carried since its early days — case-insensitive key matching being one of the more surprising ones. The green tea garbage collector (GOEXPERIMENT=greenteagc) targets 10–40% reduction in GC overhead for workloads with heavy small-object allocation. That range is wide by design — results depend entirely on your allocation patterns, so measure before assuming the upper end. Full details in the Go 1.25 release notes.
Before You Upgrade
Go 1.25 requires macOS 12 Monterey or later — macOS 11 Big Sur is no longer supported. Windows 32-bit ARM has this as its final Go release; support ends in 1.26.
There is also a nil pointer bug fix to audit for. Go 1.21 through 1.24 had a compiler bug that could incorrectly delay nil pointer panics — specifically when you dereference a value before checking whether the preceding call returned an error. Go 1.25 fixes this so the panic fires immediately at the dereference. Search your codebase for the pattern: result, err := someCall() followed by accessing result before the err != nil check. If your code happened to rely on the deferred panic (unlikely but possible), those sites will now panic at the right moment.
After upgrading, drop go.uber.org/automaxprocs and verify your Kubernetes pods have explicit CPU limits set — otherwise the new default has nothing to read and behavior is unchanged. Upgrade path and compatibility notes are at go.dev/doc/go1.25.













