Introduction

.NET MAUI applications on Android have traditionally used the Mono runtime. With the move toward CoreCLR, .NET MAUI is getting the same modern .NET runtime foundation used by other .NET application models.

That change is interesting for more than just runtime architecture. For mobile applications, startup time and memory usage directly affect how an application feels to users. A runtime migration can therefore raise practical questions:

Microsoft has been working on bringing CoreCLR to .NET MAUI across Android, iOS, and Mac Catalyst. The important point for developers is that runtime changes should be evaluated with measurements rather than assumptions.

This article focuses on Android and shows how to build a repeatable benchmark for comparing startup and memory behavior.

What Is CoreCLR?

CoreCLR is the .NET runtime used by modern .NET applications. It provides services such as garbage collection, JIT compilation, type loading, exception handling, and other runtime functionality.

.NET MAUI historically used different runtime technology for mobile targets because mobile platforms have restrictions and deployment requirements that differ from desktop and server applications.

Moving toward CoreCLR creates a more consistent runtime foundation across .NET application types.

For developers, this can eventually mean fewer runtime-specific differences to consider when building applications that target multiple platforms.

However, a runtime migration should not be evaluated only from the perspective of API compatibility. Mobile applications have very different performance characteristics from server applications, so startup, memory, application size, and device behavior are worth measuring independently.

Why Startup Time Matters on Android

When a user taps an application icon, several things can happen before the first usable screen appears.

A simplified startup sequence looks like this:

User launches app
       |
       v
Android creates process
       |
       v
.NET runtime initialization
       |
       v
.NET MAUI initialization
       |
       v
Application startup code
       |
       v
Dependency injection / services
       |
       v
First page creation
       |
       v
First usable UI

The runtime is only one part of this process.

For example, an application with a large dependency graph may spend significant time creating services before the first page is displayed. Similarly, database initialization, configuration loading, network requests, or excessive work in MauiProgram can dominate startup.

This is why a meaningful comparison should keep application code identical between runtime configurations.

Creating a Simple .NET MAUI Test Application

For a controlled experiment, start with a small application.

A basic MauiProgram might look like this:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();

        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont(
                    "OpenSans-Regular.ttf",
                    "OpenSansRegular");
            });

        return builder.Build();
    }
}

The main page can remain intentionally simple:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }
}

The purpose is not to create a production application. The goal is to establish a baseline where runtime startup overhead can be observed without introducing unnecessary application work.

Preparing the Android Build

Performance testing should be performed with a Release build.

A Debug build includes development-oriented behavior that can significantly affect startup and memory measurements.

For example:

dotnet build -c Release -f net11.0-android

The exact target framework and SDK configuration should match the .NET MAUI version being evaluated.

Before collecting results, make sure the application is deployed consistently to the same Android device or emulator.

Do not compare:

and then attribute every difference to the runtime.

Hardware, Android version, emulator configuration, background processes, and build configuration can all influence the results.

Designing a Startup Benchmark

A simple startup benchmark should measure the time between launching the application and reaching a clearly defined readiness point.

For example, the application can record a timestamp when the first page is created:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        var startupTime = DateTimeOffset.UtcNow;

        System.Diagnostics.Debug.WriteLine(
            $"MainPage created: {startupTime:O}");
    }
}

For a more useful benchmark, define a consistent application milestone.

For example:

T0 = Android process launch
T1 = .NET runtime available
T2 = MauiProgram completed
T3 = App created
T4 = MainPage loaded
T5 = First usable UI

The benchmark should use the same milestone for every runtime configuration.

If the measurement point changes between tests, the results cannot be compared reliably.

Measuring Memory Usage

Startup time tells only part of the story.

A mobile application can start quickly and still consume more memory after initialization.

Android provides tools for observing application memory, including Android Studio's profiling tools and Android debugging commands.

For example, ADB can be used to inspect process information:

adb shell dumpsys meminfo com.example.mauibenchmark

Replace the package name with the actual application package.

The output includes memory-related information about the process.

For repeatable testing, record memory after the same application state is reached.

For example:

Launch application
        |
        v
Wait for first usable screen
        |
        v
Wait for initialization to finish
        |
        v
Collect memory information

Do not take a memory reading immediately after process creation and compare it with a reading taken several seconds later in another test.

Comparing Mono and CoreCLR

The most useful experiment is an A/B comparison.

AreaRuntime ARuntime B
Application codeIdenticalIdentical
Android deviceSameSame
Android versionSameSame
Build configurationReleaseRelease
Application dataSameSame
Startup measurementSame milestoneSame milestone
Memory measurementSame pointSame point
Test iterationsMultipleMultiple

The runtime should be the primary variable being changed.

Everything else should remain as constant as practical.

This approach makes it easier to determine whether an observed difference is actually associated with the runtime migration.

Building a Repeatable Test Procedure

A practical test procedure can be organized into the following steps.

Step 1: Prepare the Device

Use the same physical Android device for all tests whenever possible.

Record:

Step 2: Install the First Build

Install the first runtime configuration and start the application.

Avoid doing other intensive work on the device during the test.

Step 3: Repeat the Launch Test

Perform several cold launches instead of relying on one measurement.

The first launch can behave differently because the device and operating system may have additional initialization work.

Step 4: Record Startup

Record the time required to reach the same application readiness point.

Step 5: Record Memory

Once the application reaches the defined steady state, collect memory information.

Step 6: Repeat With the Other Runtime

Install the second configuration and repeat the same procedure.

The goal is not to produce a single impressive number. The goal is to produce comparable measurements.

Avoiding Misleading Benchmarks

There are several easy ways to produce misleading mobile performance results.

Comparing Debug and Release

This is one of the most common mistakes.

Always compare equivalent configurations.

Changing Application Code

If the CoreCLR build has different initialization code, dependency injection configuration, or startup services, you are no longer testing only the runtime.

Using Different Devices

A high-end physical device and a low-end emulator are not comparable environments.

Measuring Only Once

One measurement can be affected by background processes, thermal conditions, Android activity, or other temporary factors.

Run multiple iterations.

Measuring After Different Application States

Memory usage can change as caches, images, services, and data are loaded.

Define the measurement point before starting the experiment.

Practical Production Considerations

A runtime migration should also be evaluated from the application-maintenance perspective.

Before moving an existing application, review third-party dependencies.

Pay particular attention to libraries that:

The application should also be tested across the Android versions and devices that matter to its users.

Performance improvements on one device do not automatically guarantee identical results on every Android device.

Advantages

More Consistent .NET Runtime Experience

Using CoreCLR across more application models can reduce the conceptual differences developers need to understand between .NET application types.

Better Performance Investigation

A common runtime foundation can make it easier to apply existing .NET performance-analysis techniques to mobile applications.

Easier Cross-Platform Runtime Strategy

For teams maintaining multiple .NET application types, a more consistent runtime model can simplify technical decisions.

Disadvantages

Migration Requires Testing

Existing applications should not be switched to a new runtime without validating libraries, startup behavior, memory usage, and platform integrations.

Mobile Performance Is Device Dependent

Results from one Android device cannot automatically represent the behavior of every target device.

Preview Technology Requires Caution

If the CoreCLR-based .NET MAUI capability is being evaluated before its final release, APIs, behavior, tooling, and performance characteristics may still change.

Troubleshooting Checklist

If the CoreCLR build does not behave as expected, check the following:

  1. Confirm that the correct .NET SDK is installed.

  2. Verify the Android target framework.

  3. Confirm that the required workload is installed.

  4. Perform a clean rebuild.

  5. Test third-party NuGet packages for compatibility.

  6. Check native Android dependencies.

  7. Compare Release builds only.

  8. Test on the same device when comparing runtimes.

  9. Review Android log output for runtime or deployment errors.

  10. Repeat the performance measurement after the application is stable.

Cleaning the project can also help eliminate stale build artifacts:

dotnet clean
dotnet restore
dotnet build -c Release

Conclusion

The move toward CoreCLR in .NET MAUI is more significant than simply changing the runtime underneath an application. It provides an opportunity to evaluate how a common .NET runtime behaves in a mobile environment.

For Android developers, startup time and memory consumption are two of the most useful areas to measure.

The key is to build a controlled experiment. Keep the application code, device, Android version, build configuration, and measurement points consistent. Then compare multiple runs rather than relying on a single result.

Most importantly, do not assume that a runtime migration automatically means better performance. Measure the application that actually matters, using the devices and workloads your users depend on.

That approach gives developers useful evidence for deciding whether and when a CoreCLR-based .NET MAUI migration makes sense for their application.