Microsoft shipped .NET 8 in November 2023 as the year’s long term support release, timed with .NET Conf 2023. As an LTS release, it gets three years of support, which makes it the safer target for teams that do not want to upgrade every single year. This piece walks through what actually changed under the hood, not just the marketing bullet points, and calls out where each feature is genuinely production ready versus where it is still preview material.
Performance work that you can actually measure
.NET 8 ships with a code generator called Dynamic Profile Guided Optimization, or Dynamic PGO, enabled by default. It watches how your code actually behaves at runtime and recompiles hot paths with that information, and Microsoft’s own numbers put the gain at up to 20 percent depending on the workload. Support for the AVX-512 instruction set also lands, letting the runtime operate on 512 bit wide vectors in one go, which matters for numeric heavy code such as image processing or scientific workloads.
Primitive types now implement a formattable and parsable interface that works directly against UTF-8, skipping the usual transcode from UTF-16 string to bytes and back. If your API does a lot of JSON serialization, this removes a genuinely wasteful step that most developers never noticed was happening.

The chart above is from the TechEmpower benchmark suite, which is an independent, widely cited comparison across web frameworks and languages, not a number Microsoft made up internally. The JSON scenario, a simple serialize-and-return test, improved by 18 percent to nearly a million requests per second on Minimal APIs. The Fortunes scenario is more useful as a real world signal because it includes database access and server side HTML rendering, and it improved by 24 percent to just over 300K requests per second. If your service does mostly JSON echo work you will see gains closer to the JSON number; if it talks to a database on every request, expect something closer to the Fortunes figure.
.NET Aspire: useful direction, not yet a production tool
.NET Aspire is a new opinionated stack for building cloud native applications, bundling telemetry, resilience policies, configuration, and health checks by default instead of leaving you to wire OpenTelemetry and Polly together by hand. The first preview shipped alongside .NET 8. Worth knowing upfront: this is a preview, and you should treat it as an evaluation project rather than something you put behind a production workload on day one. The value proposition is real for teams tired of re-solving the same cross-cutting concerns on every new microservice, but wait for at least one or two stable releases before betting a production system on it.
Smaller, more secure container images
Every official .NET container image now ships with a non-root user by default, and the SDK can publish a container image directly without you writing a Dockerfile at all. That second point is a genuine time saver for teams that just want a working image without maintaining a separate Dockerfile per service.

The new Chiseled Ubuntu variants strip the image down to almost nothing, no shell, no package manager, no unnecessary binaries, which reduces your attack surface considerably. The trade-off is that you cannot exec into a chiseled container to poke around when something misbehaves in production, since there is no shell to exec into. Use chiseled images once your deployment is stable and your logging and diagnostics are solid; keep the regular slim images around while you are still actively debugging a new service.
Native AOT: real numbers, but check compatibility first
Native AOT compiles your app straight to native machine code ahead of time, so there is no JIT warm-up cost and you do not need to ship the JIT compiler or IL alongside your app. That means smaller deployments and near-instant startup, which is particularly valuable for serverless functions and containers that scale from zero.

On Linux, the published size drops to under 10 MB and the app starts in 34 milliseconds, against roughly 300 milliseconds and a much larger footprint for the equivalent JIT-compiled app. Working set memory also drops noticeably, which directly affects how many instances you can pack onto a node. The catch that the marketing slide will not tell you: Native AOT does not support runtime reflection-based scenarios like most traditional ORMs, dynamic assembly loading, or many third party DI containers, so before you plan a Native AOT migration, check that every library in your dependency tree is trim and AOT compatible. For a lot of existing enterprise codebases built around reflection-heavy patterns, this is still a nontrivial rewrite, not a flag you flip on a Friday afternoon.
AI gets first-class treatment in the SDK
.NET 8 adds Tensor Primitives to System.Numerics, aimed squarely at making numeric operations common in AI workloads faster and more ergonomic to write. Microsoft also worked with partners including Azure OpenAI, Azure Cognitive Search, Milvus, Qdrant, and Microsoft Teams so that .NET developers get proper SDK access to these platforms rather than having to reach for community wrappers of varying quality.
The open source Semantic Kernel SDK is the piece that ties this together, giving you a consistent abstraction for plugging LLMs and vector search into an existing .NET application. Reference templates for a customer chatbot, a Retrieval Augmented Generation pipeline, and general Azure AI service integration ship alongside this release, which is a reasonable starting point if you are building your first AI feature and do not want to architect the plumbing from scratch.
Blazor becomes genuinely full stack
Blazor Server and Blazor WebAssembly can now live in the same app and the framework decides at runtime when to shift a user from server rendering to client side WebAssembly. In practice this means you get fast initial page loads from server rendering and then a snappier, more interactive experience once the WebAssembly payload is ready, without maintaining two separate projects.

The WebAssembly runtime itself got faster through a new Jiterpreter based execution model, which partially compiles hot loops instead of pure interpretation. Blazor also gained a built-in Identity UI, so basic login, registration, and account management screens no longer need to be hand rolled or copy-pasted from a template project every time you start a new app.
C# 12 trims real boilerplate
Primary constructors now work on any class or struct, not just records, which removes a lot of the repetitive constructor-then-assign-field pattern. One thing worth flagging for teams adopting this early: primary constructor parameters behave differently from fields when captured in lambdas or local functions, and mixing primary constructor parameters with regular fields in the same class can get confusing fast, so pick one style per class and stay consistent.
Collection expressions are the more broadly useful addition. The example below shows creating a List, a Span, and using the spread operator to concatenate arrays.
// Create a list:
List<int> a = [1, 2, 3, 4, 5, 6, 7, 8];
// Create a span
Span<char> b = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'];
// Use the spread operator to concatenate
int[] array1 = [1, 2, 3];
int[] array2 = [4, 5, 6];
int[] array3 = [7, 8, 9];
int[] fullArray = [..array1, ..array2, ..array3];
// fullArray contents: [1, 2, 3, 4, 5, 6, 7, 8, 9]
This syntax works across List, array, Span, and any type that implements the right collection builder pattern, so the same bracket notation applies whether you are building a list, a span, or a custom collection type. The spread operator, written as two dots before a variable name, replaces the usual AddRange or Concat calls and reads more naturally once you get used to it. One small gotcha: collection expressions infer the target type from context, so if you assign one to var without a clear left hand type, the compiler will complain, and you need to be explicit about the target type.
Day-one tooling support, which is not always guaranteed
Visual Studio 2022 17.8 shipped alongside .NET 8 with full support for the new C# 12 syntax, and Visual Studio Code’s C# Dev Kit works across Linux, macOS, and GitHub Codespaces if you want a lighter setup. A dedicated GitHub Codespaces template for .NET is also available, which is a fast way to try .NET 8 without touching your local machine at all, particularly useful if you are evaluating it on a locked down corporate laptop.
Other changes worth knowing about
- ASP.NET Core: cookie-based identity for SPA and Blazor apps, plus form binding and antiforgery support added to Minimal APIs.
- Entity Framework Core: complex types as value objects, primitive collections, and hierarchical data support on SQL Server.
- NuGet: built-in auditing of packages in your project or solution for known security vulnerabilities.
- .NET Runtime: a new AOT compilation mode targeting WebAssembly and Android.
- System.Text.Json: can now populate read-only members and gives you more control over unmapped member handling.
- F# 8: language changes, new diagnostics, and faster project compilation.
How to approach the upgrade
Do not treat this as a drop-in version bump for anything beyond a small side project. Read through the documented default behavior changes first, since some of these change runtime behavior silently rather than throwing a compile error, and that is the category of bug that shows up in production three weeks after a deployment. .NET 8 also ships a set of opt-in behavior changes that will likely become the default in a future release, so testing your app against those now saves you a harder migration later.
The Upgrade Assistant tool handles a good chunk of the mechanical work like updating target frameworks and package references, but it will not catch logic that depends on the runtime’s old behavior. Budget real testing time for any app that leans on reflection, dynamic code generation, or older third party libraries that have not been updated for trimming and AOT compatibility, since those are the areas most likely to break quietly.
Leave a Reply