<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Go1.27 on WebDevStation</title>
    <link>https://webdevstation.com/tags/go1.27/</link>
    <description>1 article tagged Go1.27 — tutorials, code examples and notes from building real systems, newest first.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <managingEditor>Alex</managingEditor>
    <webMaster>Alex</webMaster>
    <copyright>© 2026 WebDevStation</copyright>
    <lastBuildDate>Tue, 01 Sep 2026 18:30:00 +0200</lastBuildDate>
    <atom:link href="https://webdevstation.com/tags/go1.27/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>What&#39;s New in Go 1.27: Generic Methods, JSON v2 and uuid in the Standard Library</title>
      <link>https://webdevstation.com/posts/whats-new-in-go-1-27/</link>
      <pubDate>Tue, 01 Sep 2026 18:30:00 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/whats-new-in-go-1-27/</guid>
      <description>Go 1.27 finally adds generic methods to the language, promotes encoding/json/v2 into the standard library, ships a uuid package, and makes goroutine leak profiles…</description>
      <content:encoded><![CDATA[<p>Most Go releases are a handful of library additions and a compiler that got a bit faster. Go 1.27, out in August 2026, is not that release. It changes the language — generic methods are finally here — and it lands two things people have been waiting years for: a second-generation JSON package and a <code>uuid</code> package, both in the standard library.</p>
<h2 id="generic-methods">Generic Methods</h2>
<p>This is the headline, and it is a real language change: <strong>a method declaration may now declare its own type parameters.</strong></p>
<p>Before 1.27, type parameters could only appear on a function or on the type itself. If you wanted a generic helper that logically belonged to a type, your only option was a package-level function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Go 1.26 and earlier: the helper lives in the package namespace,</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// even though it only makes sense for *Rand.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">N</span>[<span style="color:#a6e22e">Int</span> <span style="color:#a6e22e">intType</span>](<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Rand</span>, <span style="color:#a6e22e">n</span> <span style="color:#a6e22e">Int</span>) <span style="color:#a6e22e">Int</span>
</span></span></code></pre></div><p>Now it can live where it belongs. <code>math/rand/v2</code> is the standard library&rsquo;s own example — it gained a generic <em>method</em> alongside the existing generic function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Go 1.27</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Rand</span>) <span style="color:#a6e22e">N</span>[<span style="color:#a6e22e">Int</span> <span style="color:#a6e22e">intType</span>](<span style="color:#a6e22e">n</span> <span style="color:#a6e22e">Int</span>) <span style="color:#a6e22e">Int</span>
</span></span></code></pre></div><p>Which means this now compiles:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">NewPCG</span>(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">2</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">d</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span> = <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">N</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)  <span style="color:#75715e">// method, inferred as time.Duration</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">i</span> <span style="color:#66d9ef">int32</span>         = <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">N</span>(int32(<span style="color:#ae81ff">100</span>))   <span style="color:#75715e">// same method, different instantiation</span>
</span></span></code></pre></div><p>The practical effect is that you can stop writing <code>pkg.DoThing(x, ...)</code> for operations that are conceptually <code>x.DoThing(...)</code>. Anywhere you have a container, a client, or a builder that needed a package-level generic helper, that helper can now be a method.</p>
<p>Two limits worth knowing before you plan a refactor around it:</p>
<ul>
<li><strong>Interfaces cannot declare type parameters on their methods</strong>, and an interface method cannot be satisfied by a generic method. So this does not give you generic interfaces through the back door; the method set of an interface is still fully concrete.</li>
<li>It is a <em>method</em> type parameter, distinct from the receiver&rsquo;s. <code>func (c *Cache[K, V]) MapTo[T any](...)</code> has three type parameters in play, two from the receiver and one from the method.</li>
</ul>
<p>If you have been writing the awkward package-level workarounds I described in <a href="/posts/example-of-how-generics-simplify-golang/">how Golang generics minimize the amount of code you need to write</a>, this is the release where a lot of that gets to move inside the type.</p>
<p>Two smaller language changes shipped alongside it. A key in a struct literal may now be <strong>any valid field selector</strong>, not just a top-level field name — which makes literals for structs with embedded types far less irritating. And function type inference is <strong>generalised to every context</strong> where a generic function is assigned to, or converted to, a matching function type, so a class of &ldquo;cannot infer&rdquo; errors simply disappears.</p>
<h2 id="encodingjsonv2">encoding/json/v2</h2>
<p>The JSON rewrite is in, as two packages:</p>
<ul>
<li><strong><code>encoding/json/v2</code></strong> — the high-level API you already know, redesigned.</li>
<li><strong><code>encoding/json/jsontext</code></strong> — lower-level syntactic processing, with an <code>Encoder</code> and <code>Decoder</code> that work in terms of <code>Token</code> and <code>Value</code> and hold a state machine guaranteeing the JSON is valid.</li>
</ul>
<p>The v2 entry points take variadic options:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;encoding/json/v2&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Marshal</span>(<span style="color:#a6e22e">v</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Deterministic</span>(<span style="color:#66d9ef">true</span>),          <span style="color:#75715e">// stable map ordering</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">OmitZeroStructFields</span>(<span style="color:#66d9ef">true</span>),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>(<span style="color:#a6e22e">data</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">v</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">RejectUnknownMembers</span>(<span style="color:#66d9ef">true</span>),   <span style="color:#75715e">// strict decoding, no silent drops</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Alongside <code>Marshal</code> and <code>Unmarshal</code> there are <code>MarshalWrite</code> / <code>UnmarshalRead</code> for an <code>io.Writer</code> / <code>io.Reader</code>, and <code>MarshalEncode</code> / <code>UnmarshalDecode</code> for driving a <code>jsontext.Encoder</code> / <code>Decoder</code> directly. That last pair is what you want for streaming, and it is a much better story than v1&rsquo;s <code>json.Decoder</code> ever was.</p>
<p><strong>The defaults are stricter</strong>, which is the whole point. v2 rejects invalid UTF-8 in strings and rejects duplicate names within an object — both of which v1 quietly accepted. That is more interoperable and it closes a genuine class of parser-differential bugs.</p>
<p><code>RejectUnknownMembers(true)</code> deserves a callout for anyone writing an API. v1&rsquo;s default of silently discarding unknown fields is how a typo in a client&rsquo;s request body turns into a field that is quietly zero, and then into a support ticket. Making that an error at the boundary is the same instinct as the sentinel errors in <a href="/posts/error-handling-in-go/">error handling in Go</a>: fail where the information is, not three layers down.</p>
<p><code>encoding/json</code> v1 is not going anywhere, and it gained a matching set of options with names that tell you exactly what they are for — <code>CallMethodsWithLegacySemantics</code>, <code>OmitEmptyWithLegacySemantics</code>, <code>FormatDurationAsNano</code>, <code>ParseTimeWithLooseRFC3339</code> and friends. That is the migration path: move to v2, and where behaviour differs in a way you depend on, opt back into the v1 semantics explicitly rather than discovering the difference in production.</p>
<p>One thing to be deliberate about: <strong>v2&rsquo;s stricter parsing is a behaviour change at your HTTP boundary.</strong> A client that has been sending duplicate keys or malformed UTF-8 and getting away with it will start getting errors. That is correct, and it is still worth knowing before you flip it on a live service. Point a load test at it first — the setup in <a href="/posts/an-easy-way-to-loadtest-your-web-apps/">an easy way to load test your web apps</a> is enough to see whether real traffic trips the new rules.</p>
<h2 id="a-uuid-package">A uuid Package</h2>
<p>There is now a <code>uuid</code> package in the standard library. The whole API is small enough to quote:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">UUID</span> [<span style="color:#ae81ff">16</span>]<span style="color:#66d9ef">byte</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">New</span>() <span style="color:#a6e22e">UUID</span>                        <span style="color:#75715e">// the recommended default</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewV4</span>() <span style="color:#a6e22e">UUID</span>                      <span style="color:#75715e">// random</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewV7</span>() <span style="color:#a6e22e">UUID</span>                      <span style="color:#75715e">// time-ordered</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Parse</span>(<span style="color:#66d9ef">string</span>) (<span style="color:#a6e22e">UUID</span>, <span style="color:#66d9ef">error</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">MustParse</span>(<span style="color:#66d9ef">string</span>) <span style="color:#a6e22e">UUID</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Nil</span>() <span style="color:#a6e22e">UUID</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Max</span>() <span style="color:#a6e22e">UUID</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">UUID</span>) <span style="color:#a6e22e">String</span>() <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">UUID</span>) <span style="color:#a6e22e">Compare</span>(<span style="color:#a6e22e">UUID</span>) <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">UUID</span>) <span style="color:#a6e22e">MarshalText</span>() ([]<span style="color:#66d9ef">byte</span>, <span style="color:#66d9ef">error</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">UUID</span>) <span style="color:#a6e22e">AppendText</span>([]<span style="color:#66d9ef">byte</span>) ([]<span style="color:#66d9ef">byte</span>, <span style="color:#66d9ef">error</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#f92672">*</span><span style="color:#a6e22e">UUID</span>) <span style="color:#a6e22e">UnmarshalText</span>([]<span style="color:#66d9ef">byte</span>) <span style="color:#66d9ef">error</span>
</span></span></code></pre></div><p><code>NewV7</code> is the one to reach for when the UUID is a database key. Version 7 is time-ordered, so generated values sort roughly by creation time — which keeps B-tree index inserts near the right-hand edge instead of scattering them across the whole index the way v4 does. If you have ever watched write throughput degrade on a table with a random-UUID primary key, that is the problem v7 exists to solve.</p>
<p><code>Compare</code> returning an <code>int</code> means it drops straight into <code>slices.SortFunc</code>, and the <code>TextMarshaler</code> / <code>TextUnmarshaler</code> implementation means it round-trips through JSON and <code>database/sql</code> without a wrapper type.</p>
<p>The obvious question: does this kill <code>github.com/google/uuid</code>? Not immediately — plenty of code depends on its wider surface. But for the common case of &ldquo;I need a v4 or v7 UUID, and I need to parse one&rdquo;, that is now a dependency you can delete.</p>
<h2 id="goroutine-leak-profiles-generally-available">Goroutine Leak Profiles, Generally Available</h2>
<p>The <code>goroutineleak</code> profile, experimental in 1.26, is now GA in <code>runtime/pprof</code> and exposed at <code>/debug/pprof/goroutineleak</code>.</p>
<p>It reports goroutines blocked on a concurrency primitive that <strong>cannot possibly become unblocked</strong>. The detection is clever: it piggybacks on the garbage collector. If goroutine G is blocked on primitive P, and P is unreachable from any runnable goroutine — or from any goroutine those could unblock — then nothing can ever signal P, so G is leaked.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#a6e22e">_</span> <span style="color:#e6db74">&#34;net/http/pprof&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// then:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//   go tool pprof http://localhost:6060/debug/pprof/goroutineleak</span>
</span></span></code></pre></div><p>The reachability trick is also the limitation, and the release notes say so plainly: leaks caused by blocking on a primitive that is still reachable through a global, or through a runnable goroutine&rsquo;s locals, will not be detected. It catches a large class, not all of them.</p>
<p>Still, this is the first tool that turns &ldquo;we think we have a goroutine leak somewhere&rdquo; into a list. The failure modes it finds are exactly the ones from <a href="/posts/worker-pools-in-go-with-errgroup/">worker pools in Go with errgroup</a> — a worker blocked forever on a send to a channel nobody is reading — and the ones a missing <code>&lt;-ctx.Done()</code> produces, which I wrote about in <a href="/posts/understanding-golang-context/">understanding Golang context</a>.</p>
<h2 id="free-performance">Free Performance</h2>
<p>The compiler now emits calls to <strong>size-specialised allocation routines</strong>, cutting the cost of small allocations (under 80 bytes) by up to 30%. The release notes are honest about what that means end to end: roughly <strong>1% on allocation-heavy programs</strong>, and about 60 KB more binary, regardless of workload.</p>
<p>One percent for a recompile is a good trade. If it causes you trouble, <code>GOEXPERIMENT=nosizespecializedmalloc</code> turns it off at build time — but note that escape hatch is expected to be <strong>removed in Go 1.28</strong>, so treat it as a window to file a bug, not a setting to keep.</p>
<h2 id="smaller-things-worth-knowing">Smaller Things Worth Knowing</h2>
<table>
	<thead>
			<tr>
					<th>Change</th>
					<th>Why you care</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>strings.CutLast(s, sep)</code></td>
					<td><code>Cut</code> from the right. The one you kept writing by hand with <code>LastIndex</code>.</td>
			</tr>
			<tr>
					<td><code>testing/synctest.Sleep(d)</code></td>
					<td>Sleep inside a synthetic-time bubble, without the awkward dance around the fake clock.</td>
			</tr>
			<tr>
					<td><code>hash/maphash.Hasher[T]</code> and <code>ComparableHasher[T]</code></td>
					<td>A real interface for hashing your own types — building a custom hash map stops meaning &ldquo;reimplement hashing&rdquo;.</td>
			</tr>
			<tr>
					<td><code>net/http.Server.MaxHeaderValueCount</code> (default 500)</td>
					<td>A cap on header value count, defending against header-flood requests.</td>
			</tr>
			<tr>
					<td><code>net/http.Server.DisableClientPriority</code></td>
					<td>Ignore client-supplied HTTP/2 priority hints.</td>
			</tr>
			<tr>
					<td><code>database/sql/driver.RowsColumnScanner</code></td>
					<td>Drivers can take over per-column scanning, which is how you avoid a round trip through <code>interface{}</code> for every value.</td>
			</tr>
			<tr>
					<td><code>go doc pkg@version</code></td>
					<td>Read the docs for a <em>specific</em> version without changing your module.</td>
			</tr>
			<tr>
					<td><code>go test</code> runs <code>stdversion</code> by default</td>
					<td>Catches use of stdlib symbols newer than the Go version your <code>go.mod</code> declares.</td>
			</tr>
	</tbody>
</table>
<p>That last one is quietly excellent. It is the failure mode where your code builds on your machine and breaks on a builder pinned to an older Go — the same class of problem I hit deploying this blog, where a template function that did not exist in the CI toolchain failed a build that was green locally.</p>
<h2 id="two-for-later">Two for Later</h2>
<p><strong><code>crypto/mldsa</code></strong> implements ML-DSA, the post-quantum signature scheme standardised as FIPS 204, with the three parameter sets <code>MLDSA44()</code>, <code>MLDSA65()</code> and <code>MLDSA87()</code>. Unless you have a compliance requirement you will not touch it this year, but it being in the standard library is what makes migration a normal task rather than a project.</p>
<p><strong><code>simd</code></strong> is an experimental package behind <code>GOEXPERIMENT=simd</code>, providing portable, vector-size-agnostic SIMD types like <code>Int8s</code> and <code>Float32s</code> that use hardware instructions where available. Experimental means experimental — but portable SIMD in Go is a genuinely interesting direction.</p>
<h2 id="should-you-upgrade">Should You Upgrade?</h2>
<p>Yes, and the calculus is unusually simple:</p>
<ul>
<li>The Go 1 compatibility promise holds; almost everything compiles unchanged.</li>
<li>You get ~1% on allocation-heavy code for a recompile.</li>
<li><code>goroutineleak</code> is worth the upgrade on its own for any service running long-lived goroutines.</li>
<li>Generic methods and JSON v2 are opt-in. Nothing forces you to rewrite anything.</li>
</ul>
<p>The only change that needs a moment&rsquo;s thought is JSON v2&rsquo;s stricter parsing, and only if you adopt it at a public boundary. Everything else is additive.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Generic methods close a gap that has been awkward since generics landed in 1.18, and they will quietly improve a lot of library APIs over the next year. JSON v2 is the rarer thing: a second attempt at a core package that gets to fix the defaults, with an explicit escape hatch back to the old behaviour. And a <code>uuid</code> package removes a dependency from nearly every service I have written.</p>
<p>Not bad for a release the notes describe as &ldquo;mostly implementation&rdquo;.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Backend Development</category>
    </item>
  </channel>
</rss>
