What Is .NET Native AOT? - How It Differs from JIT and Trimming

· Updated: · · C#, .NET, Native AOT, Publishing, Design

Revision history (1 updates, last updated Sep 1, 2026)

A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.

Retranslated as a full translation of the Japanese original. The previous English version was an abridgement that carried only part of the source, so sections, tables, Mermaid diagrams, figure captions and FAQ entries were missing. All of them have been restored to match the Japanese original, and the technical claims are the same as in the Japanese version. Read the version before this update (DOI: 10.5281/zenodo.21614491)
First published
Cite this article(DOI: 10.5281/zenodo.21614490)

This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.

Go Komura (2026). What Is .NET Native AOT? - How It Differs from JIT and Trimming. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21614490 https://comcomponent.com/en/blog/2026/03/13/001-dotnet-native-aot-what-is/

DOI (latest version)
10.5281/zenodo.21614490
DOI (this version)
10.5281/zenodo.22217138

In Calling a C# Native AOT DLL from C/C++ we already covered using Native AOT to call C# from C/C++. Honestly, though, it would have been kinder to put what Native AOT actually is first. The order got slightly reversed.

Discussions of Native AOT are notorious for terms blurring together right at the start.

  • Is this about getting rid of the JIT?
  • How does it differ from self-contained and single-file?
  • Is it in the same family as ReadyToRun?
  • What is actually happening when a flood of trimming warnings appears?
  • Can WPF / WinForms / ASP.NET Core all use it with the same level of comfort?

When these blur together, Native AOT starts to look like either magic that just makes things faster or, conversely, a scary thing full of restrictions. Both views are a bit sloppy.

What happens when the terms blur togetherWhen terms such as JIT, self-contained, ReadyToRun, and trimming all blur together, Native AOT looks either like magic that just makes things faster or like a scary pile of restrictions, and both views are sloppy.Terms blur togetherLooks like speed magicLooks full of scary restrictionsSeparate the terms firstAn option with a visible place

Figure 1: The root of the confusion is tangled vocabulary. Start by pulling the words apart.

In this article, assuming the current practical landscape of .NET 8 and later, we sort out these four things first.

  • What Native AOT really is
  • What you gain, and where things get tough
  • How it differs from ReadyToRun and trimming
  • Which kinds of apps make for a gentle first attempt

Table of Contents

  1. The Conclusion First (In One Line)
  2. The Tables to Look at First
    • 2.1. The Vocabulary Around Native AOT
    • 2.2. JIT vs. ReadyToRun vs. Native AOT
  3. The Big Picture of Native AOT (Diagram)
  4. What Native AOT Gives You
    • 4.1. Startup Tends to Get Lighter
    • 4.2. No Need to Assume a Pre-Installed Runtime
    • 4.3. Good Fit for Restricted Execution Environments
  5. Where Native AOT Gets Tough
    • 5.1. Reflection and Dynamic Code Generation
    • 5.2. You Have to Think Trimming-First
    • 5.3. Publishing per Platform
    • 5.4. Windows Desktop / COM Contexts Call for Real Caution
  6. Minimal Steps
    • 6.1. The csproj
    • 6.2. Publishing
    • 6.3. How to Write JSON
  7. Cases Where It Fits
  8. Cases Where It Doesn’t
  9. Pitfalls
  10. Summary
  11. References

In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (16 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle

1. The Conclusion First (In One Line)

  • Native AOT is a way of publishing a .NET app that ahead-of-time compiles it to native code at publish time and ships that.
  • Because it does not use a runtime JIT, startup time and memory footprint tend to improve, and it is easy to deploy to machines without the .NET runtime installed.
  • In exchange, it gets along poorly with unrestricted reflection, dynamic code generation, built-in COM, and libraries that do not support trimming.
  • In other words, rather than magic that makes things faster, it is a publishing model that gives up a little of the dynamic world and leans toward the static one, for the sake of startup, distribution, and execution-environment constraints.

Native AOT is a mechanism for shipping .NET like a native app, not a simple checkbox for faster compilation.

What you gain and what you give upThrough ahead-of-time compilation at publish time, Native AOT gains startup time, memory footprint, and runtime-free distribution, and in exchange gives up compatibility with unrestricted reflection, dynamic code generation, and built-in COM.Native AOTWhat you gainWhat you give upLighter startup, memory, and deploymentUnrestricted reflectionDynamic code generation and built-in COM

Figure 2: Not magic that makes things faster, but a trade that gives up a little of the dynamic world for the static one.

2. The Tables to Look at First

2.1. The Vocabulary Around Native AOT

Separating these terms up front makes everything that follows easier.

Term What it does Relationship to Native AOT
JIT Generates native code from IL at runtime Native AOT does this work ahead of time
self-contained Ships the .NET pieces needed to run along with the app Native AOT belongs to this family of thinking
single-file Bundles the deliverable into one file Distinct from the essence of Native AOT, but the end result often looks similar
trimming Removes unused code Practically a prerequisite under Native AOT
ReadyToRun Keeps the IL but front-loads some of the JIT’s work Similar-sounding but a genuinely different thing
source generator Moves runtime dynamic behavior into build-time code generation Pairs well with Native AOT

The confusing part is that Native AOT is less a single feature and more a publishing model that works hand in hand with self-contained, trimming, source generation, and RID-pinned publishing.

The machinery that moves along with Native AOTNative AOT is not a single feature but a publishing model that works in combination with self-contained, trimming, source generation, and RID-pinned publishing.Native AOT (publishing model)Same family as self-containedTrimming is practically a prerequisitePairs well with source generatorsPublish pinned to a RID

Figure 3: Native AOT is not a standalone feature but a publishing model that moves as a set with the machinery around it.

2.2. JIT vs. ReadyToRun vs. Native AOT

This too is fastest to absorb as one table.

Aspect Ordinary JIT execution ReadyToRun Native AOT
Runtime JIT Used Still used in some cases Not used
Contents of the deliverable Mostly IL IL + pre-generated code Mostly a native executable
Startup Baseline Easy to improve Very easy to improve
Compatibility Broadest Broad Strongly constrained
Dynamic features Easy to use Mostly easy to use Heavily restricted
Best suited for General .NET development Improving startup as a first step Aggressively pursuing startup, distribution, and restricted environments

If ReadyToRun is the direction of making the JIT’s life a little easier, Native AOT is the direction of not assuming a runtime JIT at all. Both carry the letters AOT, but the two are worlds apart in practice.

The difference in direction between ReadyToRun and Native AOTReadyToRun keeps the IL and front-loads a little of the JIT work to make it easier, while Native AOT does not assume a runtime JIT at all, so the same word AOT covers two quite different things.Make its job a little easierDo not assume it at allWhat do you want from the JITReadyToRun (IL stays)Native AOT (native-centric)Compatibility stays broadFast startup but strong constraints

Figure 4: Even under the same AOT label, making the JIT easier and not assuming the JIT are different things.

With that in place, the question of which deployment form to actually pick fits on one page like this.

You canNot that muchYou doYou do not want to / cannotYou doYou do notYesNoDecide the deployment formCan you install the .NET runtime on the target?Do you need to cut startup time?framework-dependentordinary publishReadyToRunstartup gains with compatibility intactDo you depend on reflection, dynamic code generation, or built-in COM?self-containedbundle with single-file if neededIs the target a console app, worker, or small API?Native AOTself-contained firstrevisit after reducing dynamic dependencies

Figure 5: How to pick a deployment form. Branch first on whether the runtime can be placed there, then on how far dynamic machinery can be reduced.

The first branch is whether you can place the runtime on the deployment target, and the next one is how far you can reduce the dynamic machinery. self-contained and single-file are about how you ship; ReadyToRun and Native AOT are about when native code gets generated. In practice you end up combining them.

3. The Big Picture of Native AOT (Diagram)

Roughly sketched, Native AOT looks like this.

Normal executiondotnet publish + PublishAotC# / .NET source codeIL assembliesRuntime JITApp runsAOT / trim analysisRemoval of unused codeNative code generationRID-specific executable

Figure 6: Normal execution JIT-compiles at runtime, while Native AOT front-loads analysis, removal, and native generation to publish time.

Ordinary .NET first produces IL, then JIT-compiles only what is needed at runtime. Native AOT front-loads a large portion of that later stage to publish time.

The crucial point here is that at publish time, the toolchain has to know essentially all of the code that will be needed at runtime. That is where the ground rules for how you are allowed to write code change.

  • Discovering types at runtime
  • Growing new code at runtime
  • Loading assemblies at runtime
  • Deferring resolution on the assumption that it will somehow work out at runtime

Code written in these styles suddenly stops getting along with Native AOT.

Everything has to be known at publish timeNative AOT needs to know essentially all of the code required at runtime by publish time, so styles such as finding types at runtime, generating code at runtime, loading assemblies at runtime, and deferring resolution get along badly with it.Required code fixed at publish timeFind types at runtimeGenerate code at runtimeLoad assemblies at runtimeGet by with deferred resolutionAll of these clash with AOT

Figure 7: The core of the changed ground rules. The more a style decides things at runtime, the harder it collides with Native AOT.

4. What Native AOT Gives You

4.1. Startup Tends to Get Lighter

The most visible payoff of Native AOT is, unsurprisingly, startup.

  • CLI tools
  • Short-lived processes
  • Serverless-style startup
  • Container startup and rollover
  • Monitoring tools and small resident processes

In these scenarios the JIT cost is easy to see, and because Native AOT front-loads it, the first moments get lighter.

Memory footprint also tends to improve, which helps when you want to pack more onto the same number of machines. On the cloud side in particular, where many copies of the same process spin up, this difference compounds steadily.

4.2. No Need to Assume a Pre-Installed Runtime

An app published with Native AOT is easy to run on machines without the .NET runtime installed.

This is quietly significant.

  • You do not want to tell the deployment target to install the .NET 9 Runtime first
  • You want slimmer container images
  • You want to drop a single small tool somewhere and have it run
  • You do not want, or are not allowed, to permit JIT in the execution environment

In situations like these, simply removing the assumption that a runtime has to be provisioned separately takes a great deal of noise out of the whole discussion.

The phrase “no runtime needed” here means the deployment target does not need a separate .NET install. It does not mean that the runtime-equivalent parts inside the app disappear entirely.

What no runtime needed actually meansWith Native AOT, no runtime needed means the deployment target does not need a separate .NET install; it does not mean that the runtime-equivalent parts inside the app disappear entirely.The phrase no runtime neededNo separate .NET install on the targetThe runtime-equivalent parts do not disappearFewer prerequisites for deployment and startup

Figure 8: No runtime needed is a statement about the deployment target. The runtime-equivalent parts are still inside the app.

4.3. Good Fit for Restricted Execution Environments

Because Native AOT does not use a runtime JIT, it is easier to run in environments where JIT is not permitted.

This lands more on the cloud, container, and mobile side than on desktop. Still, even in a Windows development context, it is a genuine win in the sense of reducing extra prerequisites at the deployment target.

5. Where Native AOT Gets Tough

5.1. Reflection and Dynamic Code Generation

This is the heart of Native AOT’s constraints.

  • Dynamic loading such as Assembly.LoadFile
  • Runtime code generation such as System.Reflection.Emit
  • Reflection that walks types without limits at runtime
  • Code that composes generics freely at runtime

These make it hard to pin down the required code at publish time, so they become breeding grounds for AOT warnings.

Of course, it is not as simple as one line of reflection putting you out of the game. But the trend holds without exception: the more a design leans toward looking at runtime and deciding then, the tougher things get.

The warning names you will see most often are the RequiresDynamicCode family. They mean that the call in question may break under AOT, so it is safer not to suppress them casually.

When you take on Native AOT, a useful mental model is that you reduce runtime cleverness and increase build-time explicitness.

From runtime cleverness to build-time explicitnessDynamic loading, runtime code generation, and unrestricted reflection are breeding grounds for AOT warnings, so shift the design toward less runtime cleverness and more build-time explicitness.Design that relies on runtime clevernessWarnings in the RequiresDynamicCode familyAdd build-time explicitnessRequired code is fixed at publish timeDo not suppress casually

Figure 9: There is only one direction for facing the central constraint: turn deciding at runtime into declaring at build time.

5.2. You Have to Think Trimming-First

Native AOT is deeply intertwined with trimming. What is easy to miss here is that not just your own code but the way your dependency libraries are written matters too.

The usual suspects are these.

  • Reflection-based serializers
  • DI / plugin setups that gather types through runtime scanning
  • Mechanisms that look up types by string name and instantiate them
  • Libraries that lean on dynamic proxies or IL generation

If warnings appear here and you shrug them off because the publish succeeded, the bill arrives later and it is steep. Under Native AOT, warnings generally deserve a serious read.

Trimming is not decided by your code aloneWith trimming, not only your own code but also how your dependency libraries are written matters, so reflection-based serializers, DI that scans at runtime, type resolution from string names, and libraries leaning on dynamic proxies all need attention.Whether trimming succeedsHow your own code is writtenHow dependency libraries are writtenReflection-based and runtime-scanning librariesRead the warnings seriously and check

Figure 10: What is easy to miss is the dependency side. The attitude of “the publish succeeded, so we are fine” gets expensive later.

That said, “read them seriously” alone does not tell you what to do, so here is the order of priority for dealing with them. The official documentation recommends trying them in this order too.

Order What to do When to use it
1 Stop using reflection Activator.CreateInstance(Type) can be replaced with a generic argument, or the work can move to a source generator
2 Add DynamicallyAccessedMembers Reflection is necessary, but the target type is known at compile time
3 Add RequiresUnreferencedCode The type name is decided from a runtime string, so static analysis is fundamentally impossible
4 Suppress with UnconditionalSuppressMessage The last resort, when none of the above works and you have confirmed it is safe

The most common first step by far is case 2: the type is known, yet the warning still fires. For example, the following code produces IL2070.

// IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods'
void PrintMethodNames(Type type)
{
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

Declare with an attribute that calling GetMethods() requires the public methods to be kept, and the warning goes away.

using System.Diagnostics.CodeAnalysis;

void PrintMethodNames(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

// If the caller passes the type with typeof, the requirement is satisfied automatically
PrintMethodNames(typeof(DateTime));

The API you call and the annotation you need line up fairly directly. GetMethod / GetMethods need PublicMethods, GetProperty / GetProperties need PublicProperties, and Activator.CreateInstance needs PublicParameterlessConstructor or PublicConstructors. DynamicallyAccessedMemberTypes.All is convenient, but it keeps every member of the target type, which inflates size, and the members it keeps can drag in warnings of their own, so specify the minimum you actually need as a rule.

Also, when adding the attribute does not clear the warning, trace back from the place that uses reflection to its callers and check that every step on the path carries it. If a single step is missing, the requirement is cut off there.

When the warning survives after adding the attributeSpecify DynamicallyAccessedMembers as narrowly as possible, and when a warning does not clear, trace back from the place using reflection to its callers and check that every step on the path carries the attribute, because one missing step cuts the requirement off there.Warning remains after adding the attributeTrace back to the callersCheck every step on the path carries itOne missing step cuts the requirement offAs a rule specify the minimum needed

Figure 11: Annotate the path, not a single point. One gap along the way and the requirement stops right there.

5.3. Publishing per Platform

Native AOT publishes pinned to a RID (Runtime Identifier). That is, this is not a world where something built for win-x64 runs as is on linux-x64.

  • Windows x64
  • Windows Arm64
  • Linux x64
  • Linux Arm64
  • macOS Arm64

You produce a separate publish output per target, like that.

This feels much more like a native app than ordinary framework-dependent .NET does.

5.4. Windows Desktop / COM Contexts Call for Real Caution

In KomuraSoft’s line of work, this part matters especially.

On Windows, Native AOT has no built-in COM. On top of that, WPF gets along poorly with trimming and WinForms depends heavily on built-in COM marshalling, so at least at present, neither belongs anywhere near the top of a list of first Native AOT candidates.

It is worth stating what “at present” is based on here. The statements in this article follow the official documentation for the .NET 8 / 9 / 10 generation, as of July 2026. For WPF and WinForms, Microsoft’s “Known trimming incompatibilities” page states explicitly that trimming support is disabled on the .NET SDK side for both - WPF because its heavy dependence on reflection and runtime code inspection leaves it barely functional after trimming, and WinForms because of its heavy dependence on built-in COM marshalling. So it is not a matter of proceeding with caution: as things stand, the SDK stops you. That description may change in the future, so check the same page at the time you read this.

Why WPF and WinForms are blockedNative AOT on Windows has no built-in COM, WPF is barely functional after trimming because of its dependence on reflection and runtime code inspection, and WinForms depends heavily on built-in COM marshalling, so trimming support is disabled on the .NET SDK side for both.Native AOT on WindowsNo built-in COMWPFHeavy reflection dependencyWinFormsHeavy COM marshalling dependencyTrimming disabled on the SDK side

Figure 12: Not proceed with caution but blocked by the SDK today. Do not make the desktop app body your first candidate.

In short, the following

  • Converting a WPF / WinForms app body straight to Native AOT
  • Bringing COM interop along with your usual instincts intact

tend to pile up publish-time warnings and runtime constraints all at once, and the cost of dealing with them spikes.

Conversely, the following

  • Console apps
  • Workers
  • Small web APIs
  • Native-interop pieces that can be flattened onto a C function boundary

are far more straightforward as entry points.

If you need COM, there are cases where staying on the JIT, or redesigning around ComWrappers / source-generated COM, is the sounder route.

6. Minimal Steps

Before any of that, publishing with Native AOT requires a native toolchain. If you just add PublishAot and run dotnet publish, it fails not at compilation but at the final native link step. That is the first gate, so install these first.

Environment What you need
Windows Visual Studio 2022 or later. Install the Desktop development with C++ workload with all of its default components
Ubuntu 18.04 or later sudo apt-get install clang zlib1g-dev
Alpine 3.15 or later sudo apk add clang build-base zlib-dev
Fedora 39 or later / RHEL 8 or later sudo dnf install clang zlib-ng-devel zlib-ng-compat-devel zlib-devel
macOS Xcode Command Line Tools (supported on .NET 8 and later)

In short, it is the compiler toolchain plus the development packages for the libraries the .NET runtime depends on. If you run this in CI, the Native AOT samples in dotnet/samples ship Dockerfiles for both Linux and Windows, so lifting the prerequisite install steps from there is the quick path.

Note that a binary built on Linux runs only on the same Linux version or newer. Something built on Ubuntu 20.04 runs on 20.04 and later, but not on 18.04. Your choice of build environment directly defines how far you can distribute.

The gate before publish and the reach of distributionPublishing with Native AOT requires a native toolchain, and without it the build fails at the final native link step. A binary built on Linux runs only on the same version or newer, so the choice of build environment defines the reach of distribution.NoYesIs the toolchain installedFails at the native link stepOne binary per RIDLinux builds run only on the same version or newerBuild environment defines the reach

Figure 13: The first gate is the toolchain. On Linux, how old the build environment is decides how far you can distribute.

6.1. The csproj

First, add PublishAot to the project file.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <PublishAot>true</PublishAot>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>

net8.0 is fine for the samples. The thinking itself is essentially the same on .NET 9 / 10.

What matters is not to tack it temporarily onto the dotnet publish command line, but to keep it in the project all the time, so you see the build / publish analysis on a daily basis.

Note that adding <PublishAot>true</PublishAot> does not suddenly turn your everyday local runs into Native AOT. Day-to-day dotnet run and normal execution stay on the JIT; the real Native AOT compilation happens at publish time.

Daily life after adding PublishAotEven with PublishAot in the project, everyday dotnet run stays on the JIT and the real Native AOT compilation happens at publish time, which is why keeping the setting in the project and watching the build and publish analysis daily matters.Keep PublishAot in the csprojEveryday dotnet run stays on the JITAOT compilation happens at publish timeWatch the analysis and warnings daily

Figure 14: JIT day to day, AOT at publish. That is exactly why the setting belongs in the project permanently, so you keep seeing the analysis.

6.2. Publishing

For Windows x64, for example, it looks like this.

dotnet publish -c Release -r win-x64

For Linux x64, like this.

dotnet publish -c Release -r linux-x64

The output is RID-pinned. The mindset shifts from one .NET DLL that runs anywhere to an executable built for that OS and architecture.

If you are approaching this from the web API side, starting from a Native AOT template is the easy way in.

dotnet new webapiaot -o MyFirstAotWebApi

For a worker, this one.

dotnet new worker -o WorkerWithAot --aot

6.3. How to Write JSON

A quietly frequent collision point under Native AOT is JSON. Used with your usual instincts, System.Text.Json leans toward reflection, so leaning into source generation is calmer.

using System.Text.Json;
using System.Text.Json.Serialization;

[JsonSerializable(typeof(AppConfig))]
internal partial class AppJsonContext : JsonSerializerContext
{
}

public sealed class AppConfig
{
    public string? Name { get; init; }
    public int RetryCount { get; init; }
}

var config = new AppConfig
{
    Name = "sample",
    RetryCount = 3
};

string json = JsonSerializer.Serialize(config, AppJsonContext.Default.AppConfig);

In practice, the rule of thumb that rarely misses is less “make it Native AOT compatible” and more “do not make the runtime go hunting for types.”

7. Cases Where It Fits

Native AOT tends to click pleasantly in cases like these.

  • CLI tools where startup is the star
  • Small APIs deployed at scale in containers
  • Workers / background services
  • Serverless and short-lived processes
  • Small .NET components slotted into native apps
  • Situations where you do not want to require a pre-installed .NET runtime

What they share is that the boundaries are relatively clear and the dynamic machinery is easy to reduce.

8. Cases Where It Doesn’t

Conversely, there are clear cases where Native AOT should not be your main battlefield from the start.

  • The body of an existing large WPF / WinForms application
  • Setups premised on built-in COM interop
  • Apps where runtime plugin loading is the centerpiece
  • Heavy dependence on frameworks that discover types through reflection
  • Libraries that use System.Reflection.Emit or dynamic proxies as a matter of course
  • Designs with C++/CLI in the middle

For these, ordinary JIT-based .NET, or ReadyToRun, or rethinking where the design boundaries sit, is the sounder route.

9. Pitfalls

Finally, here are the things that are easy to step on in your first round with Native AOT.

  • Taking publish warnings lightly
    • As noted above, Native AOT warnings are meant to be read seriously.
  • The build passes but the publish breaks
    • At publish time the analysis runs in earnest across your dependency libraries too, and some things only become visible there.
  • Treating ReadyToRun and Native AOT with the same mindset
    • The words are similar, but the strength of the constraints is quite different.
  • Starting straight from the body of a desktop app
    • A console app, a worker, or a small API first is much calmer.
  • Writing JSON or configuration binding with your usual habits
    • Reflection-premised code comes back to bite later.
  • Shipping as if the output were platform-independent
    • Native AOT deliverables are RID-pinned.
  • Believing that Native AOT makes everything faster
    • The stars here are startup, distribution, and the execution environment. Miss that and expectations drift.

With Native AOT, the final verdict comes from dotnet publish, not dotnet build. Start running it early and the late game gets much less painful.

The publish decides the verdictThe build can pass while the publish breaks because at publish time the analysis runs in earnest across dependency libraries too, so what finally decides whether Native AOT works is dotnet publish rather than dotnet build.dotnet build passesThe verdict is not in yetdotnet publishFull analysis including dependenciesThis is where the verdict first showsSo run publish early

Figure 15: The common thread across the pitfalls. Do not relax because the build passed; run the publish early.

10. Summary

In one sentence, Native AOT is a mechanism that shifts a .NET app from a dynamic execution model toward a distribution model that can be pinned down statically.

The points worth keeping in view come down to these five.

  1. Native AOT ahead-of-time compiles to native code at publish time
  2. It works very well for startup, memory, and distribution concerns
  3. In exchange, it is hard on reflection, dynamic code generation, built-in COM, and trimming-incompatible code
  4. As a first target, a console app, a worker, or a small API is calmer than a desktop app body
  5. Clearing warnings and verifying early on a publish basis is what matters

Native AOT is not a standard switch to flip on every .NET app. But where startup matters, you want lighter distribution, and you want fewer execution-environment prerequisites, it is a genuinely powerful weapon.

Conversely, in the dense world of WPF / WinForms / COM, ordinary .NET is still often the sounder choice. Once you can tell these apart, Native AOT stops being a difficult new feature and becomes an option with a clearly defined place.

11. References

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

What is Native AOT?
It is a publishing model that ahead-of-time compiles a .NET app to native code at publish time and ships that. Because there is no runtime JIT, startup time and memory footprint tend to improve, and the app is easy to hand to machines with no .NET runtime installed. In exchange, it gets along poorly with unrestricted reflection, dynamic code generation, built-in COM, and libraries that do not support trimming. Rather than magic that makes things faster, it is a publishing model that gives up a little of the dynamic world and leans toward the static one, for the sake of startup, distribution, and execution-environment constraints.
What is the difference between Native AOT and ReadyToRun?
ReadyToRun keeps the IL and front-loads a little of the JIT's work, so the runtime JIT still comes into play in some places. Compatibility stays broad, and dynamic features remain mostly easy to use. Native AOT, by contrast, does not assume a runtime JIT at all: the deliverable is centered on a native executable, startup is much easier to improve, and the constraints get considerably stronger in return. Both carry the letters AOT, but ReadyToRun points toward making the JIT's job a little easier while Native AOT points toward not assuming a JIT at all, and the two feel quite different.
Can I use Native AOT in WPF or WinForms apps?
For now, treat that with real caution. Native AOT on Windows has no built-in COM, WPF gets along poorly with trimming, and WinForms depends heavily on built-in COM marshalling, so neither is a good first Native AOT candidate. If you need COM, staying on the JIT or redesigning around ComWrappers and source-generated COM is sometimes the sounder route. As an entry point, console apps, workers, and small web APIs are far more straightforward.
What kinds of apps is Native AOT a good fit for?
It suits CLI tools where startup is the star, small APIs deployed at scale in containers, workers and background services, serverless and short-lived processes, small .NET components slotted into native apps, and situations where you do not want to require a pre-installed .NET runtime. What these share is relatively clear boundaries and dynamic machinery that is easy to reduce. It does not suit apps whose centerpiece is runtime plugin loading, or setups that lean heavily on frameworks that discover types through reflection.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog