Skip to content
Yuvraj 🧢
Github - yindiaGithub - tqindiaContact

Go 1.26: Green Tea GC by Default, new(expr), and a Revamped go fix

— go, golang, go-1.26, release-notes, performance — 10 min read

Picture the version bump landing on a production Go service you own: an allocation-heavy API that burns real CPU inside the garbage collector. Go 1.26 is a runtime-and-tooling release, so most of what it gives you arrives the moment you rebuild. This walkthrough follows that upgrade in the order you would actually do it: bump the toolchain, watch what the compiler and runtime now do differently, adopt the standard library pieces you will reach for, check the one removal that can bite, and then decide whether the upgrade is worth scheduling.

The headline is the Green Tea garbage collector, now on by default. The Go team reports a 10 to 40 percent reduction in GC (garbage collection) overhead on programs that stress the collector, and about 10 percent more on newer amd64 CPUs; you get it by rebuilding. Two small language changes come along: new can take an expression to set the initial value, and a generic type may now refer to itself in its own type parameter list. go fix is rebuilt into a code modernizer with a source-level inliner driven by //go:fix inline. The standard library adds errors.AsType (a generic errors.As), slog.MultiHandler to fan out logs, and a new crypto/hpke package. The things to watch on the way in: go mod init now defaults to a lower go version in new go.mod files, cmd/doc was removed in favour of go doc, and net/http.ServeMux trailing-slash redirects now return HTTP 307.

Previous release: Go 1.25. Next: Go 1.27 (coming soon).

Getting your service building on 1.26

The first thing the upgrade touches is your build. Go keeps large codebases current by shipping tools that migrate old patterns, and 1.26 leans into that.

go fix is now a modernizer

go fix used to apply a fixed set of ancient migrations and was mostly forgotten. In 1.26 it is rebuilt on the same analysis framework as go vet and becomes the home of Go's modernizers, including a source-level inliner (release notes). A library author marks a function with //go:fix inline, and go fix rewrites callers to the inlined form.

go fix ./...

Notice this is now a maintenance tool you run periodically, not a one-time legacy fixer. The obsolete historical fixers were removed.

go mod init defaults to a lower go version

This is the toolchain change most likely to surprise you. go mod init no longer writes the current toolchain's version into a new go.mod. A toolchain 1.N.X now writes go 1.(N-1).0 (release notes).

toolchain go 1.26.X ──▶ go mod init ──▶ go.mod says: go 1.25.0
toolchain go 1.26 rc ──▶ go mod init ──▶ go.mod says: go 1.24.0

Caption: new modules start on a slightly older go directive, for wider compatibility.

The reasoning is that a brand-new module should not immediately require the newest toolchain from everyone who builds it. Bump the go directive explicitly when you want new-version features.

Other tool changes

Several smaller items: cmd/doc and go tool doc were removed, so use go doc. The pprof -http web UI now opens on the flame graph view. On windows/arm64, the linker supports internal linking of cgo (C-Go interop) programs. And the compiler places more slice backing stores on the stack, cutting heap allocation.

What the compiler now accepts

Go 1.26 makes two changes to the spec, both small and backward compatible.

new can take an expression

Before, new(T) allocated a zero value of type T and returned a *T. If you wanted a pointer to a specific value, you needed a named variable, because you cannot take the address of a literal. Now new accepts an expression and returns a pointer to a variable initialized with it (release notes).

// Before: a pointer to 42 needed a temporary.
x := 42
p := &x
// Go 1.26: one step.
p := new(42) // p is *int, pointing to a variable holding 42

Notice this is most useful for optional struct fields, where you often want *int or *bool set to a specific value: Age: new(yearsSince(born)).

Generic types can refer to themselves

The restriction that a generic type could not name itself in its own type parameter list is lifted. This lets you express recursive constraints, like a type that adds to values of its own kind (release notes).

// Now valid: A is constrained to be an Adder of itself.
type Adder[A Adder[A]] interface {
Add(A) A
}

Notice this simplifies self-referential interfaces and data structures that previously needed workarounds.

What your rebuilt binary does differently

This release's efficiency work lands here, and you get all of it by rebuilding, with no code change.

Green Tea GC is on by default

The garbage collector's mark phase scans live memory to find what is still reachable. Green Tea redesigns that scan to work over larger contiguous spans of memory rather than chasing objects one at a time, which improves cache locality. It was opt-in (GOEXPERIMENT=greenteagc) in 1.25 and is the default in 1.26 (release notes).

Old scan: object ──▶ object ──▶ object (pointer chasing, poor locality)
Green Tea: scan a span of objects together (better cache use)

Caption: Green Tea scans memory in spans instead of chasing individual objects.

The Go team reports a 10 to 40 percent reduction in GC overhead on programs that stress the collector, and about 10 percent more on newer amd64 CPUs (Intel Ice Lake or AMD Zen 4 and later). You get this by rebuilding with 1.26. If it regresses your workload, opt out with GOEXPERIMENT=nogreenteagc, but note that opt-out is expected to be removed in 1.27, so report problems now.

The design behind the span-based scan

The classic Go mark phase followed pointers from object to object, which is simple but cache-unfriendly: each hop can be a cache miss. Green Tea reorganizes scanning around spans of memory so the collector touches nearby objects together, which modern CPUs handle far better, and it can use wide vector instructions on newer amd64. The trade is added complexity in the runtime, which is why it spent a release as an experiment before becoming the default. If you run allocation-heavy services, this is the release where that work pays off without any code change (release notes).

Faster cgo and a leaner heap

Two more runtime items. The baseline overhead of a cgo call dropped by about 30 percent, which helps code that crosses the Go and C boundary often. And 64-bit builds now randomize the heap base address, a security hardening you can disable with GOEXPERIMENT=norandomizedheapbase64.

Experimental goroutine leak profile

A new experimental profile helps find leaked goroutines. Build with GOEXPERIMENT=goroutineleakprofile and read /debug/pprof/goroutineleak (release notes). The Go team aims to enable it by default in 1.27.

Standard library APIs you will reach for

A few additions are worth adopting the moment you upgrade.

errors.AsType: a generic errors.As

errors.As unwraps an error chain into a target variable, but its API is awkward: you declare a variable, pass its address, and read it after. errors.AsType is the generic version, returning the value and a boolean (pkg.go.dev).

Its signature is func AsType[E error](err error) (E, bool).

// Before:
var pe *fs.PathError
if errors.As(err, &pe) {
log.Printf("failed on %s", pe.Path)
}
// Go 1.26:
if pe, ok := errors.AsType[*fs.PathError](err); ok {
log.Printf("failed on %s", pe.Path)
}

Notice the new form is a single expression with no pre-declared variable, which reads better in an if.

slog.MultiHandler: send logs to more than one place

Structured logging with log/slog had no built-in way to fan a record out to multiple handlers, so people wrote their own. slog.NewMultiHandler does it (pkg.go.dev).

Its signature is func NewMultiHandler(handlers ...Handler) *MultiHandler.

text := slog.NewTextHandler(os.Stdout, nil)
json := slog.NewJSONHandler(logFile, nil)
logger := slog.New(slog.NewMultiHandler(text, json))
logger.Info("login", slog.Int("id", 42))

Notice each record now goes to every enabled handler, so you can log human-readable text to stdout and JSON to a file at once, without a custom handler.

Other additions worth knowing

  • crypto/hpke implements Hybrid Public Key Encryption (RFC 9180), including post-quantum hybrid KEMs (Key Encapsulation Mechanisms).
  • net.Dialer.DialTCP, DialUDP, DialIP, DialUnix add context-aware, protocol-specific dialing.
  • bytes.Buffer.Peek reads ahead without consuming.
  • reflect.Type.Fields, Methods, Ins, Outs and Value.Fields, Methods expose iterator-based reflection.
  • Performance: io.ReadAll is often about twice as fast, fmt.Errorf allocates less, crypto/mlkem is about 18 percent faster, and image/jpeg has a faster, more accurate encoder and decoder.
  • crypto/tls enables the post-quantum hybrid curves SecP256r1MLKEM768 and SecP384r1MLKEM1024 by default.

There are also two experimental packages behind GOEXPERIMENT: simd/archsimd for amd64 SIMD (Single Instruction, Multiple Data) intrinsics, and runtime/secret.

The changes that can bite you

What was removed or changed

ItemReplacementMigration actionSource
cmd/doc, go tool docgo docCall go doc insteadrelease notes
Historical go fix fixersThe new modernizersNone; they were obsoleterelease notes
net/http.ServeMux trailing-slash redirect statusNow HTTP 307 (was 301)Verify clients follow 307 correctlyrelease notes
go mod init default go versionWrites go 1.(N-1).0Bump the go directive when you need new featuresrelease notes

GOEXPERIMENT knobs to know

SettingEffectNoteSource
nogreenteagcDisable the Green Tea GCOpt-out expected removed in 1.27release notes
norandomizedheapbase64Disable heap base randomizationSecurity hardening is on by defaultrelease notes
goroutineleakprofileEnable the goroutine leak profileExperimental; may default in 1.27release notes

Behaviour that shifts when you rebuild

Most of 1.26 is transparent, but a few things change behaviour:

  • go mod init writes an older go directive. New modules start on go 1.25.0 (or 1.24.0 for release candidates). If your CI assumes the newest directive, adjust it.
  • ServeMux redirects use 307. A trailing-slash redirect now returns 307 instead of 301, which preserves the request method. Confirm clients and proxies handle it.
  • Green Tea changes GC timing. Behaviour is transparent, but throughput and pause patterns shift. Benchmark latency-sensitive services.
  • cmd/doc is gone. Scripts calling go tool doc must switch to go doc.

Nothing here is gated by the go directive in go.mod; the GC and tool changes apply as soon as you build with 1.26.

Upgrading without drama

1. install Go 1.26 (go install golang.org/dl/go1.26@latest, then go1.26 download)
2. run: go build ./... && go test ./...
3. run go vet ./... and try go fix ./... for modernizations
4. benchmark GC-sensitive and latency-sensitive paths under Green Tea
5. update scripts that call `go tool doc` to `go doc`
6. bump the go directive only when you want 1.26 features
7. rollback: pin the previous toolchain in go.mod if needed

Caption: recommended upgrade path to Go 1.26.

Specifics:

  1. Rebuild and test. The compatibility promise means almost all code builds unchanged.
  2. Benchmark before trusting or blaming Green Tea; opt out with GOEXPERIMENT=nogreenteagc only if a measured regression appears.
  3. Rollback is a toolchain pin; 1.26 does not change the language in a way that strands code.

Five experiments for your first week

  1. Rebuild a GC-heavy service with 1.26 and compare CPU and p99 latency against 1.25.
  2. Replace an errors.As call with errors.AsType and see if the code reads better.
  3. Wire slog.NewMultiHandler to log text to stdout and JSON to a file.
  4. Run go fix ./... on a module and review the modernizations it proposes.
  5. On a service that leaks goroutines, try GOEXPERIMENT=goroutineleakprofile and read /debug/pprof/goroutineleak.

So, is it worth upgrading?

Go 1.26 gives you a faster garbage collector for free, two small language conveniences, and a go fix that keeps code current. The upgrade is low risk: rebuild, test, benchmark GC-sensitive paths, and adjust scripts that used cmd/doc. Watch the go mod init default and the ServeMux 307 change.

Next in this series: Go 1.27.

Further reading

Release and dates:

Standard library:

© 2026 by Yuvraj 🧢. All rights reserved.
Theme by LekoArts