Edit

Runtimes and compilation in .NET MAUI

.NET Multi-platform App UI (.NET MAUI) apps can use different runtimes and compilation strategies depending on the target platform, build configuration, and deployment model. This article explains the key terms and the runtime and compilation strategies that .NET MAUI uses on each platform.

Runtimes

A .NET runtime is the execution environment that manages your app's memory, type system, garbage collection, and code execution. .NET MAUI apps use one of the following runtimes:

Mono

Mono is the cross-platform .NET runtime that has historically powered Xamarin apps and .NET MAUI apps on Android, iOS, and Mac Catalyst. Mono supports both just-in-time (JIT) compilation and ahead-of-time (AOT) compilation. Mono is the default runtime for .NET MAUI apps on mobile and Mac Catalyst platforms.

Mono isn't supported for .NET MAUI apps that target .NET 11+.

CoreCLR

CoreCLR is the .NET Common Language Runtime used by .NET on desktop and server platforms. It powers ASP.NET Core, console apps, and Windows desktop apps. CoreCLR features a highly optimizing JIT compiler, tiered compilation, and a full set of runtime diagnostics.

In .NET MAUI, CoreCLR is the runtime used on Windows. In .NET 10, CoreCLR is also available as an experimental option for Android.

In .NET 11+, CoreCLR is the default runtime for .NET MAUI apps on Windows, Android, iOS, and Mac Catalyst. NativeAOT is an opt-in alternative for publishing on all .NET 11 targets. NativeAOT support is experimental on Android.

NativeAOT

When you publish with Native AOT, your app doesn't run on the CoreCLR runtime. Instead, it runs on a minimal NativeAOT runtime that's statically linked into your native binary. This runtime includes a garbage collector and type system but no JIT compiler or interpreter. All code is compiled to native machine code at build time.

Compilation strategies

A compilation strategy determines how your C# code is turned into machine code that the processor can execute. .NET MAUI uses several compilation strategies, depending on the runtime and deployment scenario.

JIT (Just-in-Time) compilation

JIT compilation translates Microsoft Intermediate Language (MSIL) into native machine code at runtime as methods are called for the first time. JIT compilation supports fast build-deploy-debug cycles and features such as Edit and Continue.

  • Used by: CoreCLR on Windows and experimentally on Android; Mono in Android debug builds
  • Advantages: Fast build times, full runtime diagnostics, and dynamic code generation support
  • Disadvantages: Slower startup because code is compiled on the device at runtime
  • Used by: CoreCLR on Windows and Android
  • Advantages: Fast build times, full runtime diagnostics, and dynamic code generation support
  • Disadvantages: Slower startup when code isn't already compiled by ReadyToRun

Apple CoreCLR device targets don't use JIT because Apple platform restrictions prevent dynamically generated code. They use the CoreCLR interpreter instead.

Mono AOT (Ahead-of-Time) compilation

Mono AOT compilation precompiles MSIL into native code at build time using Mono's AOT compiler. This is the default compilation mode for .NET MAUI release builds on iOS, Mac Catalyst, and Android.

  • Used by: Mono runtime on iOS, Mac Catalyst, and Android
  • Advantages: Faster startup than JIT and required for platforms that restrict dynamic code generation
  • Disadvantages: Larger app size than JIT-only builds and limited support for some dynamic features

Mono AOT is not the same as NativeAOT. With Mono AOT, your app still includes the Mono runtime and can optionally use the Mono interpreter for code that wasn't AOT-compiled. NativeAOT compiles all code ahead of time and doesn't include a JIT or interpreter. Mono also supports a Full AOT mode where no interpreter or JIT is available. Full AOT is used by default for Mono release builds on iOS and Mac Catalyst.

NativeAOT (Native Ahead-of-Time) compilation

NativeAOT compiles your entire app, its dependencies, and a minimal runtime into a native binary at build time. There is no JIT compiler or interpreter at runtime. NativeAOT performs full trimming and static analysis, which can produce smaller app sizes and faster startup times, but it places more restrictions on the code patterns you can use.

  • Available on: .NET MAUI iOS and Mac Catalyst apps when explicitly enabled for publishing
  • Advantages: Small app size, fast startup, and a single native binary
  • Disadvantages: Longer build times, no dynamic code generation or dynamic loading, and all code must be trim-safe and AOT-compatible
  • MSBuild property: <PublishAot>true</PublishAot>
  • Available on: All .NET MAUI targets when explicitly enabled for publishing. NativeAOT support is experimental on Android.
  • Advantages: Small app size, fast startup, and a single native binary
  • Disadvantages: Longer build times, no dynamic code generation or dynamic loading, and all code must be trim-safe and AOT-compatible
  • MSBuild property: <PublishAot>true</PublishAot>

NativeAOT is a publish-only deployment model. Use dotnet publish to produce and validate a NativeAOT app. A regular debug build doesn't use NativeAOT.

For more information, see Native AOT deployment on iOS and Mac Catalyst and Native AOT deployment.

ReadyToRun (R2R)

ReadyToRun is a form of ahead-of-time compilation for CoreCLR that precompiles assemblies into a format containing both the original MSIL and a native code representation. At startup, the runtime can use the precompiled native code instead of compiling methods from MSIL. On platforms where JIT is available, the runtime can still JIT-compile methods that aren't precompiled and optimize frequently used methods at runtime.

  • Used by: CoreCLR on Windows and CoreCLR Android builds that opt into the experimental runtime
  • Advantages: Improved startup time while retaining JIT compatibility
  • Disadvantages: Larger assembly sizes because assemblies contain both MSIL and native code
  • MSBuild property: <PublishReadyToRun>true</PublishReadyToRun>

ReadyToRun is enabled by default for .NET MAUI apps on Windows in Release mode and for Android apps that use CoreCLR (experimental) in Release mode. The R2R images are packed inside the .dll files.

  • Used by: CoreCLR on Windows, Android, iOS, and Mac Catalyst
  • Advantages: Improved startup time while retaining the appropriate runtime compilation behavior
  • Disadvantages: Larger application and download sizes

For Android apps, ReadyToRun is enabled by default in the Release configuration. The default is composite partial ReadyToRun. Full ReadyToRun can be enabled with MauiEnableFullReadyToRun=true. It can improve startup or runtime performance, but it increases package size, so measure the result for your app.

For iOS and Mac Catalyst apps, composite partial ReadyToRun is used in Debug builds and composite full ReadyToRun is used in Release builds. The CoreCLR interpreter is always enabled and executes code that isn't precompiled because these platforms don't permit JIT compilation.

Composite R2R compiles assemblies together, enabling cross-assembly optimizations. Partial R2R precompiles selected methods while leaving the remaining methods for runtime compilation, while full R2R precompiles all methods.

For more information, see ReadyToRun compilation.

Profile-guided optimization (PGO)

Profile-guided optimization (PGO) uses profiling data to guide the compiler toward producing more efficient code. In the context of .NET MAUI, there are two forms of PGO:

  • Static PGO with MIBC profiles: .NET MAUI ships .mibc profile files containing data about methods frequently called during typical app startup and usage. When ReadyToRun compilation is enabled, the R2R compiler uses these profiles to decide which methods to precompile.
  • Dynamic PGO: On CoreCLR, the JIT compiler can collect profile data at runtime and use it when methods are recompiled with more optimizations.

For more information, see Profile-guided optimization and ReadyToRun compilation.

Mono interpreter

The Mono interpreter enables an app to interpret MSIL at runtime without generating native code dynamically. It is used alongside Mono AOT on platforms that don't allow JIT compilation, such as iOS devices.

The Mono interpreter also enables .NET Hot Reload for apps running on the Mono runtime. UseInterpreter is set to true by default in Debug mode on Android, iOS, and Mac Catalyst.

  • Used by: Mono runtime
  • Advantages: Supports dynamic features that AOT can't handle, enables .NET Hot Reload, and can reduce app size
  • Disadvantages: Interpreted code runs slower than compiled code
  • MSBuild property: <UseInterpreter>true</UseInterpreter>

For more information, see Mono interpreter on iOS and Mac Catalyst.

CoreCLR interpreter

CoreCLR includes an interpreter on iOS and Mac Catalyst. The interpreter is always enabled and executes code that isn't precompiled because these platforms don't permit JIT compilation. This behavior is part of the runtime and doesn't require an interpreter MSBuild property.

What .NET MAUI uses by default

The following table summarizes which runtime and compilation strategy .NET MAUI uses on each platform, by build configuration:

Platform Debug Release
Android Mono + JIT + interpreter Mono + Mono AOT
iOS Mono + JIT (x64) / Mono + AOT + interpreter (ARM64) Mono + Mono AOT
Mac Catalyst Mono + JIT (x64) / Mono + AOT + interpreter (ARM64) Mono + Mono AOT
Windows CoreCLR + JIT CoreCLR + JIT + ReadyToRun

Note

When you opt in to CoreCLR on Android or iOS by setting <UseMonoRuntime>false</UseMonoRuntime> in your project file, builds use ReadyToRun by default. On iOS and Mac Catalyst with CoreCLR, composite ReadyToRun is enabled for both debug and release builds.

Tip

You can opt in to NativeAOT on iOS and Mac Catalyst by setting <PublishAot>true</PublishAot> in your project file. NativeAOT only takes effect when publishing.

The following table summarizes the runtime and compilation strategy used by .NET MAUI on each platform:

Platform Debug Release
Android CoreCLR + JIT CoreCLR + composite partial ReadyToRun + JIT
iOS CoreCLR + composite partial ReadyToRun + interpreter CoreCLR + composite full ReadyToRun + interpreter
Mac Catalyst CoreCLR + composite partial ReadyToRun + interpreter CoreCLR + composite full ReadyToRun + interpreter
Windows CoreCLR + JIT CoreCLR + JIT + ReadyToRun

Important

Don't set UseMonoRuntime to true when targeting .NET 11+. Mono isn't supported, and setting this property produces a build error.

Tip

NativeAOT is enabled with <PublishAot>true</PublishAot> and takes effect when you use dotnet publish. NativeAOT has no JIT or interpreter and requires full trimming.

Comparison

The following table compares the compilation strategies available for .NET MAUI apps:

Strategy JIT Mono AOT ReadyToRun NativeAOT
Compilation time At runtime At build time At build time At build time
Startup speed Slowest Fast Fast Fastest
Steady-state speed Fastest (optimized JIT) Good Fastest (tiered recompilation) Good
App size Smallest Larger Larger Smallest
Dynamic code Full support Limited Full support Not supported
Trimming required No Partial (default) No Full
Diagnostics Full Limited Full Limited

Typical performance

The following benchmarks are from a dotnet new maui app and are hardware-dependent. They illustrate relative differences between runtimes and aren't absolute guarantees.

Startup time (milliseconds):

Runtime Android iOS macOS
Mono (default) ~642 ~275 ~311
NativeAOT ~274 ~130 ~255

App size on iOS (MB):

Deployment model Size
Mono (default) ~14.3
Mono (full trimming) ~11.5
NativeAOT ~5.4

The following table compares the compilation strategies available for .NET MAUI apps targeting .NET 11:

Strategy CoreCLR JIT CoreCLR interpreter ReadyToRun NativeAOT
Compilation time At runtime At runtime At build time At build time
Startup speed Slower Slower Fast Fastest
App size Smaller Smaller Larger Smallest
Dynamic code Full support Full support Full support (through either JIT or interpreter) Not supported
Diagnostics Full Full Limited Limited

CoreCLR on Android and iOS

Starting in .NET 10, you can opt in to running an Android app on CoreCLR instead of Mono.

<PropertyGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
    <UseMonoRuntime>false</UseMonoRuntime>
</PropertyGroup>

In .NET 10, CoreCLR on Android is an experimental feature and isn't intended for production use.

In .NET 11+, CoreCLR is the default runtime for Android, iOS, and Mac Catalyst apps. NativeAOT is an opt-in alternative that doesn't use CoreCLR and is experimental on Android. Don't set UseMonoRuntime=true; Mono isn't supported for .NET 11+, and setting the property produces a build error.

Inside an Android APK

The contents of an APK differ depending on which runtime and compilation strategy the app uses:

Mono:

  • classes.dex — Java/Kotlin code
  • lib/arm64-v8a/libmonosgen-2.0.so — Mono runtime
  • lib/arm64-v8a/libmonodroid.so — Android-specific runtime startup glue
  • lib/arm64-v8a/libassemblies.arm64-v8a.blob.so — Compressed, packed MSIL assemblies
  • lib/arm64-v8a/libaot-*.dll.so — Mono AOT native images in release builds

CoreCLR:

  • classes.dex — Java/Kotlin code
  • lib/arm64-v8a/libcoreclr.so — CoreCLR runtime
  • lib/arm64-v8a/libclrjit.so — CoreCLR JIT compiler
  • lib/arm64-v8a/libmonodroid.so — Android-specific runtime startup glue
  • lib/arm64-v8a/libassemblies.arm64-v8a.so — Packed MSIL with ReadyToRun images

The contents of an Android APK differ depending on whether the app uses CoreCLR or NativeAOT:

CoreCLR:

  • classes.dex — Java/Kotlin code
  • lib/arm64-v8a/libcoreclr.so — CoreCLR runtime
  • lib/arm64-v8a/libclrjit.so — CoreCLR JIT compiler
  • lib/arm64-v8a/libmonodroid.so — Android-specific runtime startup glue
  • lib/arm64-v8a/libassembly-store.so — Packed MSIL with ReadyToRun images

NativeAOT on Android (experimental):

  • classes.dex — Java/Kotlin code
  • lib/arm64-v8a/libhellomaui.so — Native library containing the runtime, managed code, and startup glue

Trimming

Trimming is a build step that removes unused code from your app to reduce its size. .NET MAUI uses the ILLink trimmer, which analyzes your code and removes types, methods, and fields that aren't statically referenced.

For CoreCLR Release builds, .NET MAUI uses TrimMode=partial by default, which trims framework assemblies but not your code or NuGet references. To use full trimming, set TrimMode to full:

<PropertyGroup>
    <TrimMode>full</TrimMode>
</PropertyGroup>

NativeAOT automatically performs full trimming. Don't set TrimMode to select a different trimming mode when using NativeAOT.

For more information, see Trim a .NET MAUI app.

MSBuild property reference

The following table summarizes the MSBuild properties that control runtime and compilation behavior:

Property Description Default
PublishAot Enable NativeAOT compilation. false
PublishReadyToRun Enable ReadyToRun precompilation for CoreCLR. true (Windows Release and applicable CoreCLR builds)
PublishTrimmed Enable ILLink trimming. true (Release)
TrimMode Set trimming aggressiveness (partial or full). partial
UseInterpreter Enable the Mono interpreter. true (iOS/Mac Catalyst Debug)
UseMonoRuntime Use Mono instead of CoreCLR. true on supported Mono targets

The following table summarizes the MSBuild properties relevant to .NET 11 runtime and compilation behavior:

Property Description Default
PublishAot Enable NativeAOT compilation during dotnet publish. false
MauiEnableFullReadyToRun Enable full ReadyToRun for Android CoreCLR apps. false
TrimMode Set trimming aggressiveness for non-NativeAOT builds. partial

See also