Native AOT stopped being an experimental curiosity a couple of releases ago. In .NET 10 it's a genuinely production-ready option for the right kind of workload — but "the right kind of workload" is doing a lot of work in that sentence, and picking it for the wrong service will cost you more than it saves.

Key takeaways
  • Native AOT in .NET 10 delivers real numbers: startup times measured in single-digit to low-double-digit milliseconds, and minimal API binaries under 5 MB, down from 18–25 MB on earlier versions.
  • It trades away runtime reflection, dynamic assembly loading, and most plugin architectures — know your dependency tree before committing.
  • dotnet publish will tell you what's incompatible before you ship — run a trial publish early, not the week before launch.
  • The best fits are Lambda functions, CLI tools, sidecar containers, and latency-sensitive APIs with a small, well-known dependency set.

What Native AOT actually does

Normally, a .NET app ships as IL that the JIT compiles to machine code at startup and, for hot paths, recompiles more aggressively as it runs. Native AOT compiles straight to native machine code at publish time — there's no JIT, no IL, and the .NET runtime itself is statically linked into your executable. That's where the startup and memory wins come from: there's simply less work happening the moment your process starts.

Enabling it

xml
<PropertyGroup>
  <PublishAot>true</PublishAot>
  <InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
bash
dotnet publish -c Release -r linux-x64 --self-contained \
  -p:PublishAot=true -o ./publish

Run that publish early in a spike, not at the end of a project. The compiler will emit trim and AOT warnings for anything it can't statically analyze — that list is your real compatibility report, and it's far more reliable than guessing from a library's README.

What breaks

Native AOT is incompatible with runtime code generation, so anything that depends on it needs a different approach:

  • Reflection-based JSON serializationSystem.Text.Json needs a source-generated JsonSerializerContext instead of runtime reflection over your DTOs.
  • Dynamic proxies — some ORMs and mocking libraries generate proxy types at runtime; check your data-access and DI libraries specifically.
  • Assembly.LoadFrom / plugin architectures — anything that loads assemblies dynamically at runtime is fundamentally at odds with ahead-of-time compilation.
  • Some third-party libraries — increasingly rare, but not eliminated; the publish warnings will surface these.
csharp
[JsonSerializable(typeof(OrderDto))]
[JsonSerializable(typeof(List<OrderDto>))]
internal partial class AppJsonContext : JsonSerializerContext { }

// Program.cs
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});

Where it earns its keep

We reach for Native AOT in four situations specifically: AWS Lambda functions (cold start is the entire game there — see our post on taming Lambda cold starts for the full picture), small CLI tools distributed to developer machines, sidecar containers where image size and memory footprint directly affect cluster density, and public-facing APIs where p99 latency includes cold-start scenarios from scale-out events.

We don't reach for it on internal line-of-business APIs with a long-lived process, a plugin-style extensibility model, or a dependency tree we haven't fully audited. The JIT's warm-path performance is excellent, and for a service that stays up for hours or days, the startup-time argument mostly evaporates.

Deployment gotchaNative AOT compiles per-target-runtime — you can't publish a single artifact for both linux-x64 and win-x64. If your deployment pipeline currently produces one portable build, factor that change into the rollout, not just the code changes.

Decision checklist

QuestionLeans toward AOT
Does cold start matter to users or cost?Yes
Do you control most of the dependency tree?Yes
Do you need runtime plugin loading?No
Is the process long-lived (hours+)?No

If most answers point the same direction, the decision's usually easy. The projects worth debating are the ones split down the middle — and those are worth a half-day spike with a real publish, not a guess from documentation.

Need help with this in production?

astrivo helps teams design, build, and modernize systems like this. Let's talk about your context.