· 8 min read

Go 1.27 Shipped Yesterday. One Change Deletes a Dependency, Another Swaps Your JSON Engine Without Asking.

Go 1.27 was released on August 19. The coverage led with generic methods, which is the language change people have wanted for years and which most application code will never touch. Buried further down are two changes that affect almost every Go service in production: a uuid package in the standard library, and a full replacement of the JSON implementation that now sits underneath encoding/json by default.

Here is the upgrade in the order I would actually do it, sorted by how much attention each part needs.

Free: the standard library grew a UUID package

Go 1.27 adds uuid to the standard library. For a lot of projects that removes github.com/google/uuid from go.mod, which is one fewer third-party dependency in the path between a request and a database row.

This is the kind of change that sounds trivial and compounds. Every direct dependency is a thing you have to watch for advisories, a thing that can go unmaintained, and a line in a supply chain review if you ever sell to someone who runs one. Deleting one for free is a good trade.

Two other free wins in the same category. The compiler now emits size-specialized allocation routines, cutting the cost of some allocations under 80 bytes by up to 30%. The Go team's own estimate is roughly 1% overall improvement in allocation-heavy programs, at the cost of about 60KB of binary size. That is a fair characterization and I appreciate that they published the modest number rather than the flattering one. And strings.CutLast and bytes.CutLast finally exist, which collapses a common LastIndex dance into one call.

Needs a test run: the JSON engine changed underneath you

Go 1.27 adds encoding/json/v2 and encoding/json/jsontext as new packages. That part is opt-in and unremarkable.

The part that matters: encoding/json is now backed by the v2 implementation. You do not import anything new. You do not change a line. Your existing json.Marshal and json.Unmarshal calls now run through different code.

The Go team preserved marshaling and unmarshaling behavior deliberately, and unmarshal performance is meaningfully faster as a result. But the release notes are explicit that the exact text of error messages may differ. If you have tests asserting on JSON error strings, or code branching on error text, those break. If you have a handler that returns a parse error to a client and you have a test pinning that string, that breaks too.

There is an escape hatch: build with GOEXPERIMENT=nojsonv2 and you get the old implementation back. Read the second half of that sentence carefully, because the release notes say the opt-out is expected to be removed in a future release. It is a bridge, not a setting.

My recommendation: upgrade in a branch, run your full suite, and grep for string comparisons against JSON errors before you do anything else. If the suite is green, ship it. If it is not, you have found real coupling to error text that was always fragile and is now visibly so.

Worth adopting deliberately: the goroutine leak profile

The goroutine leak detector was an experiment in Go 1.26. In 1.27 it graduates to a regular profile named goroutineleak, available through runtime/pprof and exposed at /debug/pprof/goroutineleak.

It works by reachability. If a goroutine is blocked on a channel or mutex, and that primitive is unreachable from any runnable goroutine, the goroutine can never wake up, and the profile reports it. That is a real class of bug that normally shows up as memory that creeps up over days and a service you restart on a schedule without ever finding out why.

It does not catch everything. The release notes are upfront that leaks involving primitives reachable through global variables, or through locals of runnable goroutines, will be missed. So it is a detector for a large subset, not a proof of absence. Still worth wiring the endpoint into any long-running service you own, because the cost is one import and the alternative is guessing.

Check before you upgrade: macOS 13 and PowerPC

Go 1.27 requires macOS 13 Ventura or later. This was announced in the 1.26 notes, so it should not be a surprise, but if you have a build agent on an older macOS it will stop working and the error will not obviously point at the Go version.

Also removed: bzr support in the go command, and several GODEBUG settings including asynctimerchan, tlsrsakex, tls3des, and gotypesalias. If you pinned any of those, go will now fail the build rather than ignore them. That is deliberate and it is the right call, but it fails at build time rather than gracefully.

The parts you will read about and not use

Generic methods are the marquee feature. A method can now declare its own type parameters, so you can put generic functions in a type's namespace instead of the package's. math/rand/v2 uses it for (*Rand) N. It is a genuine language improvement and if you maintain a library it may simplify your API. If you write handlers and business logic, you will read about it and move on, and that is fine.

The new crypto/mldsa package implements ML-DSA post-quantum signatures per FIPS 204, and crypto/tls now supports ML-DSA signature schemes in TLS 1.3. Unless you have a compliance requirement with a date attached, this is not yours to act on yet. The experimental simd package is behind GOEXPERIMENT=simd and the API is explicitly not stable.

The honest take

Most Go releases are safe to take blind. This one is not, and the reason is a single line: encoding/json is backed by a new implementation by default, with an opt-out flag scheduled for removal.

That is the correct engineering decision. A rewritten JSON package that nobody adopts is wasted work, and defaulting it is the only way to get real-world coverage. But it means the upgrade deserves a branch and a full test run rather than a version bump in CI on a Friday.

Budget an hour. Delete the UUID dependency while you are in there.

Author

Sources

Stay in the Loop

Get new posts delivered to your inbox. No spam, unsubscribe anytime.

Newsletter coming soon. Set PUBLIC_CONVERTKIT_FORM_ID in .env to activate.

Related Posts