When you compile with NativeAOT (PublishAot=true), your application does not use CoreCLR at all at runtime.
Instead, it uses a minimal, statically linked runtime that’s fundamentally different from CoreCLR, that is a lightweight static runtime called the NativeAOT Runtime (derived from the old CoreRT project)
๐ How it works conceptually
When you publish a .NET app normally:
Standard .NET (JIT/CoreCLR path):
When you publish using NativeAOT, the process changes drastically:
NativeAOT (AOT path):
⚙️ What replaces CoreCLR in NativeAOT?
Instead of the CoreCLR runtime, NativeAOT uses a lightweight static runtime called the NativeAOT Runtime (derived from the old CoreRT project).
This runtime:
- Includes a minimal GC, type system, and exception handling system.
- Does not include JIT compilation logic.
- Does not support full reflection or dynamic assembly loading.
It’s statically linked into the final executable, so the entire runtime becomes part of your .exe file.
๐ง Deeper Comparison: CoreCLR vs NativeAOT Runtime
| Feature | CoreCLR | NativeAOT Runtime |
|---|---|---|
| JIT Compiler | ✅ Present (RyuJIT) | ❌ Not included |
| GC (Garbage Collector) | ✅ Full-featured, generational | ✅ Simplified GC (same core concepts, smaller) |
| Type Loader | ✅ Dynamic | ✅ Static (known at build time) |
| Reflection | ✅ Full | ⚠️ Limited (metadata stripped) |
| Dynamic Assembly Load | ✅ Supported | ❌ Not supported |
| Code Generation at Runtime (Emit) | ✅ Supported | ❌ Not supported |
| Startup Time | ⚡ Moderate | ⚡⚡ Super fast |
| Binary Size | ๐งฑ Larger | ๐ก Smaller |
| Runtime Required | .NET runtime (CoreCLR) | None — self-contained |
| Use Cases | General-purpose apps | Cloud, microservices, serverless, CLI tools |
๐งฎ Example Build Flow
๐ธ CoreCLR-based publish:
Produces:
-
app.dll -
dotnethost loadsCoreCLRruntime -
JIT compiles IL on startup
๐ธ NativeAOT publish:
Produces:
-
A single
appexecutable -
Contains native code + stripped-down runtime
-
Runs directly via
./app(nodotnetneeded)
๐งฉ Why this matters
- Blazing-fast cold starts (critical for AWS Lambda, Azure Functions, CLI tools)
- No runtime dependency (great for container deployment)
- Reduced attack surface (no JIT, less metadata)
- No runtime code generation (System.Reflection.Emit)
- Limited dynamic loading
- Reflection-heavy frameworks (like some ORMs or JSON serializers) need trimming-safe versions
๐ก Internal Implementation Hint
In .NET 7/8, the AOT toolchain (crossgen2) builds an object file (.obj) that includes:
-
Compiled IL → native code
-
Static GC and metadata tables
-
Lightweight runtime startup code (from NativeAOT runtime)
Then it uses the system’s C++ linker to produce a final executable:
So the “runtime” is baked in, not “loaded dynamically” like CoreCLR.
๐ง Analogy
| Mode | Analogy |
|---|---|
| CoreCLR (JIT) | Like a chef who cooks each dish when ordered — flexible, but slower startup. |
| R2R (Partial AOT) | Chef preps ingredients ahead of time — faster serving. |
| NativeAOT (Full AOT) | Meals fully cooked, sealed, and ready to serve — instant serving, no kitchen needed. |
๐งพ Summary
| Aspect | CoreCLR (.NET Runtime) | NativeAOT |
|---|---|---|
| Uses JIT? | ✅ Yes | ❌ No |
| Runtime Engine | CoreCLR | NativeAOT runtime (based on CoreRT) |
| Startup Speed | ⚡ Moderate | ⚡⚡ Instant |
| Reflection Support | ✅ Full | ⚠️ Limited |
| Runtime Size | ๐งฑ Larger | ๐ก Small and static |
| Output | IL + runtime | Native binary |
| Target Scenarios | General apps, APIs | Cloud-native, microservices, serverless, tools |


