.NET MAUI applications targeting mobile platforms are moving to a unified runtime model.
Starting with .NET 11 Preview 4, CoreCLR became the default runtime for .NET MAUI applications targeting Android, iOS, and Mac Catalyst. With .NET 11 Preview 6, CoreCLR became the only runtime for those mobile targets, and the previous mechanism for selecting Mono was removed. Blazor WebAssembly remains on Mono and is not affected by this transition.
For developers, this is more than a project-file change.
A runtime migration can affect:
Microsoft's stated direction is to unify .NET MAUI mobile applications around CoreCLR, with expected benefits across debugging, profiling, Hot Reload, application size, and performance.
But a runtime-level architectural change should not be evaluated only by whether the application launches.
The more useful question is:
How do you systematically find mobile regressions after moving a .NET MAUI application from the previous runtime path to CoreCLR?
This article presents a practical regression-testing strategy.
What Changed in .NET MAUI
Historically, .NET MAUI mobile applications used Mono for Android, iOS, and Mac Catalyst.
The transition introduces:
Previous model
.NET MAUI
|
+-- Android → Mono
+-- iOS → Mono
+-- Mac Catalyst → Mono
The .NET 11 model is:
.NET MAUI
|
+-- Android → CoreCLR
+-- iOS → CoreCLR
+-- Mac Catalyst → CoreCLR
As of .NET 11 Preview 6, developers no longer select between Mono and CoreCLR for these mobile targets. The previous Mono-selection build property has been removed.
This makes regression testing particularly important.
Why Runtime Migration Can Cause Regressions
A .NET MAUI application contains several layers:
Application Code
|
v
.NET MAUI
|
v
.NET Runtime
|
v
Platform Bindings
|
v
Android / iOS / Mac Catalyst
|
v
Native OS APIs
A runtime change can expose problems at any layer.
For example:
Application
↓
Reflection
↓
Runtime behavior
↓
Native binding
A feature that worked under the previous runtime may behave differently after migration.
This does not mean CoreCLR is inherently less compatible.
It means runtime migrations should be tested as architectural changes rather than ordinary dependency upgrades.
Start With a Baseline
Before changing the target framework, record the current application's behavior.
For example:
| Metric | Existing Runtime | CoreCLR |
|---|
| Cold startup | Measure | Measure |
| Warm startup | Measure | Measure |
| Memory after launch | Measure | Measure |
| Peak memory | Measure | Measure |
| CPU during startup | Measure | Measure |
| App size | Measure | Measure |
| Screen transition | Measure | Measure |
| API request latency | Measure | Measure |
| Crash rate | Measure | Measure |
| ANR/hang rate | Measure | Measure |
The values should be collected from the actual application and target devices.
Do not publish assumed improvements.
The purpose of the baseline is to identify behavioral differences after migration.
Upgrade the Target Framework
A .NET MAUI project targeting .NET 11 will use CoreCLR for the mobile platforms.
For example:
<PropertyGroup>
<TargetFrameworks>
net11.0-android;
net11.0-ios;
net11.0-maccatalyst
</TargetFrameworks>
<UseMaui>true</UseMaui>
</PropertyGroup>
The exact project configuration depends on the existing application.
After upgrading, verify:
dotnet --info
and confirm that the expected .NET 11 SDK is being used.
Install or update the MAUI workload as required by the SDK:
dotnet workload install maui
Microsoft's CoreCLR migration guidance specifically recommends installing the .NET 11 Preview 6 SDK and the MAUI workload, then running existing applications through a validation checklist.
Do Not Treat Compilation as Migration Success
A successful build proves only that the project can be compiled.
It does not prove:
Application starts
↓
Navigation works
↓
Native APIs work
↓
Authentication works
↓
Storage works
↓
Networking works
↓
Background operations work
↓
App survives suspend/resume
A mobile runtime migration requires behavioral validation.
Create a Smoke-Test Matrix
Start with a short smoke-test suite.
| Area | Test |
|---|
| Launch | Cold start |
| Navigation | Open every primary screen |
| Authentication | Login/logout |
| Networking | API request |
| Storage | Read/write local data |
| Permissions | Request required permissions |
| Camera | Capture image |
| Location | Access location |
| Notifications | Receive notification |
| Deep links | Open application through link |
| Backgrounding | Suspend and resume |
| Rotation | Change orientation where supported |
| Offline mode | Disable network |
| Logout | Clear session |
| Upgrade | Upgrade existing installation |
The exact tests depend on the application.
The goal is to catch obvious runtime incompatibilities before performance testing.
Test Android, iOS, and Mac Catalyst Separately
Do not assume:
Android works
means:
iOS works
or:
Mac Catalyst works
The runtime is unified, but the native platform layers remain different.
Use separate test matrices:
Android
|
+-- Device A
+-- Device B
+-- Device C
iOS
|
+-- Device A
+-- Device B
+-- Device C
Mac Catalyst
|
+-- macOS configuration
At minimum, test the platforms your application actually ships.
Test Different Device Classes
A runtime migration can behave differently depending on device capability.
Test representative:
Low-memory device
Mid-range device
High-end device
The exact devices should be selected based on your supported-device policy.
Record:
Startup time
Memory
CPU
Frame responsiveness
Crashes
ANRs/hangs
Battery behavior
Avoid drawing conclusions from a single high-end development machine.
Measure Cold Startup
Startup is one of the first metrics worth measuring.
A basic measurement should distinguish:
Process launch
↓
Runtime initialization
↓
MAUI initialization
↓
Application initialization
↓
First usable screen
Define a consistent endpoint.
For example:
Cold startup =
launch command
→ first interactive application screen
Do not compare two runtimes using different definitions of "startup."
Measure Warm Startup
Also test:
Application backgrounded
↓
Application resumed
Warm startup can reveal issues that are invisible during a cold launch.
For example:
State restoration problems
Resource lifecycle problems
Native handler issues
Timer behavior
Event subscription problems
A migration test should therefore contain both cold and warm scenarios.
Watch Memory Usage
A runtime migration can change memory characteristics.
Measure at consistent points:
After launch
After navigation
After loading data
After opening media
After repeated navigation
After background/resume
For example:
| Test Stage | Baseline | CoreCLR |
|---|
| Launch | Measure | Measure |
| Login | Measure | Measure |
| Main screen | Measure | Measure |
| Large list | Measure | Measure |
| Image screen | Measure | Measure |
| After navigation loop | Measure | Measure |
The important signal is not merely peak memory.
Look for unexpected growth:
Screen A
↓
Screen B
↓
Screen C
↓
Screen A
↓
Screen B
If memory continually increases after returning to previously visited screens, investigate possible application-level leaks before attributing the issue to CoreCLR.
Test Navigation Loops
A practical memory regression test is repeated navigation.
For example:
Home
↓
Details
↓
Edit
↓
Details
↓
Home
Repeat this sequence many times.
Monitor memory.
This can expose:
Event-handler leaks
Static references
Unreleased native resources
Image retention
Handler lifecycle problems
Application-level object retention
The runtime migration may change the symptoms without necessarily being the underlying cause.
Test Native Interop
Mobile applications frequently interact with native APIs.
Examples include:
Camera
Location
Bluetooth
Push notifications
Keychain / secure storage
Sensors
Media
Files
Contacts
Platform-specific UI
Test every native integration.
A runtime migration can expose assumptions in bindings or interop code.
For example:
#if ANDROID
// Android-specific implementation
#endif
#if IOS
// iOS-specific implementation
#endif
Do not assume that shared C# code is the only relevant compatibility surface.
Review Reflection
Reflection-heavy code deserves special attention.
Search the application for:
Assembly.Load
Type.GetType
GetMethod
GetProperty
Activator.CreateInstance
MakeGenericType
reflection-based serializers
dynamic invocation
Reflection may be especially important when combined with trimming or AOT-related deployment configurations.
The goal is not to remove reflection automatically.
Instead, identify where runtime discovery is essential and verify those paths explicitly.
Test Dependency Injection
A typical MAUI application may register services in MauiProgram:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder =
MauiApp.CreateBuilder();
builder
.UseMauiApp<App>();
builder.Services.AddSingleton<
IAuthService,
AuthService>();
builder.Services.AddTransient<
MainPage>();
return builder.Build();
}
}
After migration, verify:
Service registration
↓
Resolution
↓
Constructor injection
↓
Page creation
Test startup paths that create many services.
A runtime migration should not be considered complete merely because the first page loads.
Test Async Operations
Mobile applications frequently perform:
API calls
Database access
File operations
Image processing
Background synchronization
Test both success and failure.
For example:
public async Task LoadAsync(
CancellationToken cancellationToken)
{
var response =
await httpClient.GetAsync(
"api/data",
cancellationToken);
response.EnsureSuccessStatusCode();
}
Validate:
Success
Timeout
Cancellation
Offline mode
Server error
App backgrounding
App resume
This is especially important because mobile applications can lose connectivity or be suspended while an asynchronous operation is active.
Test App Suspension and Resume
A mobile runtime test should include:
Open app
↓
Start operation
↓
Background app
↓
Resume app
↓
Validate state
Test operations such as:
API request
File upload
Database transaction
Authentication flow
Media processing
The application should handle lifecycle transitions intentionally.
Do not assume an async operation that works on desktop behaves identically during mobile suspension.
Test Background Work
Identify all background work:
Timers
Tasks
Periodic synchronization
Push notification handlers
Background services
Event subscriptions
Then verify:
Start
Stop
Suspend
Resume
Terminate
Restart
A migration regression may appear as:
Background operation
↓
App suspended
↓
Unexpected exception
↓
State lost
The exact behavior depends on the mobile platform and application architecture.
Test Secure Storage and Local Storage
Storage should be part of the regression suite.
Test:
Write
Read
Update
Delete
Clear
Upgrade
Restart
For example:
await SecureStorage.Default
.SetAsync("access_token", token);
var stored =
await SecureStorage.Default
.GetAsync("access_token");
Verify that values remain available across:
Application restart
Background/resume
Upgrade
where the application's requirements expect persistence.
Test Authentication Flows
Authentication often combines:
Web authentication
Browser
Redirect/deep link
Token storage
HTTP requests
Refresh
Logout
Test the complete flow after migration.
For example:
Login
↓
Browser
↓
Redirect
↓
Callback
↓
Token
↓
API
↓
Logout
Do not test only the initial login screen.
Runtime or platform integration issues can occur at the callback or native-browser boundary.
Test Deep Links
If the application supports:
myapp://orders/123
or universal/app links, test:
App closed
App backgrounded
App already open
Invalid link
Authenticated user
Unauthenticated user
Verify that navigation reaches the expected page.
Deep-link handling is a useful regression test because it crosses application and platform boundaries.
Test Push Notifications
If the application uses push notifications, validate:
Permission request
Token registration
Foreground notification
Background notification
Notification tap
Deep-link navigation
Cold-start notification
A runtime migration should be tested against the entire lifecycle rather than only notification receipt.
Test UI Responsiveness
A runtime change can affect perceived application performance even when API latency is unchanged.
Measure scenarios such as:
Open page
Scroll large list
Load images
Navigate between pages
Open modal
Close modal
Perform search
Record observable metrics where your tooling supports them.
At minimum, look for:
Frame drops
Input lag
Visible freezes
Long UI-thread blocks
A page that technically loads successfully can still be a regression if it becomes visibly less responsive.
Separate UI Thread From Background Work
Check for synchronous work on the UI thread.
For example:
var data =
VeryExpensiveOperation();
If this executes during a page lifecycle event, it can block rendering.
Prefer an asynchronous approach when appropriate:
var data =
await VeryExpensiveOperationAsync();
But do not automatically move CPU-heavy work to Task.Run.
Measure the actual workload and ensure that UI-affecting operations are scheduled appropriately for the platform.
Test Native Libraries
Inventory native dependencies:
Android libraries
iOS frameworks
NuGet packages with native assets
Binding libraries
Graphics libraries
Analytics SDKs
Authentication SDKs
For each dependency, verify:
Build
Package
Launch
Initialization
Runtime usage
Release build
A library that works in Debug may still fail in a release or trimmed build.
Test Release Builds
Debug builds are not sufficient.
Test:
Debug
Release
and the actual deployment configuration.
The release configuration can change:
Linking
Trimming
AOT behavior
Optimization
Native packaging
Resource handling
Microsoft's Native AOT documentation notes that AOT deployments introduce restrictions around dynamic loading and runtime code generation and rely on trimming analysis.
If your application uses AOT or aggressive trimming, these scenarios deserve dedicated testing.
Test Trimming-Sensitive Code
Search for code that depends on runtime discovery:
Reflection
Dynamic activation
Serialization
Plugin loading
Assembly scanning
Then run the release configuration and verify those paths.
A feature that works in Debug but fails after trimming should be treated as a deployment compatibility problem.
Test Third-Party Libraries
Create a dependency inventory:
| Dependency | Purpose | Android | iOS | Mac Catalyst | Release |
|---|
| Package A | Analytics | Test | Test | Test | Test |
| Package B | Authentication | Test | Test | Test | Test |
| Package C | Storage | Test | Test | Test | Test |
Do not assume a NuGet package is compatible merely because the project compiles.
Verify actual runtime behavior.
Compare Application Size
Microsoft identifies application size as one area expected to benefit from the unified CoreCLR direction.
Measure actual output size instead of assuming an improvement.
Record:
APK size
AAB size
IPA size
Installed application size
where applicable to your distribution process.
Keep packaging settings identical between comparison builds.
Compare Startup and Memory Together
A useful regression report should combine multiple dimensions.
For example:
| Metric | Previous | CoreCLR | Difference |
|---|
| Cold startup | Measure | Measure | Calculate |
| Warm startup | Measure | Measure | Calculate |
| Memory after launch | Measure | Measure | Calculate |
| Peak memory | Measure | Measure | Calculate |
| Package size | Measure | Measure | Calculate |
| API latency | Measure | Measure | Calculate |
Do not optimize one metric while ignoring another.
A smaller package with higher memory usage may be a different trade-off from a faster startup with larger storage requirements.
Create a Regression Test Matrix
A practical matrix can be:
Platform
├── Android
│ ├── Debug
│ └── Release
│
├── iOS
│ ├── Debug
│ └── Release
│
└── Mac Catalyst
├── Debug
└── Release
For each configuration:
Launch
Navigation
Authentication
Networking
Storage
Native APIs
Background/resume
Deep links
Notifications
Performance
Memory
This converts an ambiguous migration into a repeatable test plan.
Automate the Smoke Tests
Automated UI testing can cover stable flows.
For example:
Launch application
↓
Login
↓
Open dashboard
↓
Open details
↓
Navigate back
↓
Logout
The exact automation framework depends on your application and platform.
The goal is to run the same critical flow against the old and new runtime configurations.
Automated smoke tests are especially valuable when the migration affects multiple platform targets.
Build a Before-and-After Performance Harness
For performance-sensitive applications, create a standard test scenario.
Example:
Scenario: Open Customer Dashboard
1. Launch application.
2. Authenticate.
3. Open dashboard.
4. Load 100 records.
5. Scroll through list.
6. Open detail screen.
7. Return to dashboard.
8. Repeat five times.
Measure:
Startup
API latency
Rendering responsiveness
Memory
CPU
Crash count
Run the same scenario against both runtime versions.
This is much more useful than testing random screens manually.
Classify Regressions
Not every difference is a runtime bug.
Use categories:
| Category | Example |
|---|
| Runtime | Changed execution behavior |
| MAUI | Framework behavior |
| Native platform | Android/iOS behavior |
| Dependency | Third-party library |
| Application | Existing application bug exposed |
| Configuration | Build or deployment setting |
| Tooling | SDK/workload issue |
This prevents every migration problem from being incorrectly attributed to CoreCLR.
Create a Minimal Reproduction
If a problem appears after migration, reduce it.
For example:
Full application
↓
Remove unrelated pages
↓
Remove networking
↓
Remove database
↓
Keep failing native operation
↓
Create minimal MAUI project
A small reproduction makes it easier to determine whether the problem is:
MAUI
CoreCLR
Native platform
Third-party library
Application code
Microsoft is specifically asking developers to test applications during the .NET 11 preview period so migration feedback can influence work before general availability.
Common Regression Patterns
Application Launches but Crashes Later
Check:
Lazy initialization
Reflection
Native libraries
Dependency injection
Platform-specific code
Debug Works but Release Fails
Investigate:
Trimming
AOT
Reflection
Native packaging
Conditional compilation
Android Works but iOS Fails
Inspect:
Memory Increases After Navigation
Use a repeated navigation test and investigate retained references before concluding that the runtime is responsible.
Startup Is Slower
First separate:
Runtime initialization
MAUI initialization
Application initialization
Network initialization
Database initialization
Without this separation, "startup is slower" is difficult to diagnose.
Troubleshooting
The Project Still References Mono Settings
Review the project file for old runtime-selection properties.
As of .NET 11 Preview 6, the previous build property used to select Mono for MAUI mobile targets has been removed because CoreCLR is now the only runtime path.
The Application Builds but Does Not Start
Start with:
dotnet clean
dotnet restore
dotnet build
Then verify:
dotnet --info
and confirm the correct SDK/workload installation.
Also test a minimal MAUI project targeting the same platform.
A Native Library Stops Working
Verify:
Package version.
Native binary architecture.
Platform support.
Initialization sequence.
Release configuration.
Trimming/AOT requirements.
Reflection-Based Code Fails
Search for runtime-discovered types and test them explicitly under the release configuration.
If trimming or AOT is involved, inspect the relevant analyzer warnings rather than suppressing them blindly.
Performance Changed but There Is No Crash
This is still a regression candidate.
Measure:
Startup
Memory
CPU
UI responsiveness
Async operations
API latency
Then compare against the baseline.
Best Practices
Record a baseline before migration.
Test Android, iOS, and Mac Catalyst independently.
Test both Debug and Release configurations.
Test representative physical devices.
Measure startup and memory rather than relying on subjective impressions.
Test all native integrations.
Review reflection-heavy code.
Test dependency compatibility.
Test background/resume behavior.
Test authentication and deep links.
Test push notifications.
Run repeated navigation tests for memory leaks.
Test release builds with trimming/AOT configurations used by the application.
Keep application and dependency changes controlled during regression analysis.
Build minimal reproductions for confirmed runtime issues.
Record the exact SDK, workload, platform version, device, and build configuration.
Frequently Asked Questions
Is CoreCLR now the only runtime for .NET MAUI mobile apps?
For .NET 11 Preview 6, CoreCLR is the only runtime for .NET MAUI applications targeting Android, iOS, and Mac Catalyst. The previous Mono runtime-selection path was removed. Blazor WebAssembly continues to use Mono.
Do I need to manually configure CoreCLR?
For .NET 11 MAUI mobile targets, CoreCLR is the runtime path. The older project configuration used to select Mono is no longer the model in Preview 6.
Does moving to CoreCLR guarantee better performance?
No.
Microsoft describes benefits including performance, debugging, profiling, Hot Reload, and application size as goals of the unified runtime direction, but application-level performance must be measured against the actual workload.
Should I test only Android?
No.
Android, iOS, and Mac Catalyst are separate deployment targets with different native platform integrations. Test every platform your application supports.
What should I measure first?
Start with:
Cold startup
Warm startup
Memory
CPU
UI responsiveness
API latency
Crash rate
Package size
Then investigate platform-specific functionality.
Can existing MAUI applications require code changes?
Potentially.
The official migration guidance asks developers to build existing applications against Preview 6 and report compatibility problems. The exact changes depend on the application's APIs, dependencies, native integrations, and runtime assumptions.
Does this change affect Blazor WebAssembly?
No. Microsoft explicitly states that Blazor WebAssembly continues to use Mono and is not affected by the .NET 11 MAUI CoreCLR transition.
Conclusion
The move to CoreCLR represents a significant runtime change for .NET MAUI mobile applications.
As of .NET 11 Preview 6, Android, iOS, and Mac Catalyst applications use CoreCLR exclusively, replacing the previous runtime-selection model involving Mono. Microsoft is encouraging developers to test their existing applications during the preview period so compatibility issues can be identified before general availability.
The correct migration strategy is not:
Update TargetFramework
↓
Build succeeds
↓
Migration complete
Instead, use:
Baseline
↓
Upgrade
↓
Smoke Test
↓
Platform Test
↓
Release Test
↓
Performance Test
↓
Memory Test
↓
Native Integration Test
↓
Dependency Test
↓
Regression Analysis
The most important lesson is that a runtime migration should be evaluated as a behavioral compatibility project, not merely a compilation exercise.
Measure startup, memory, CPU, responsiveness, package size, native integrations, asynchronous operations, lifecycle behavior, and release builds.
When a regression appears, isolate it before assigning blame:
Application
↓
.NET MAUI
↓
CoreCLR
↓
Native Platform
↓
Third-Party Dependency
That process gives teams a defensible way to determine whether a problem is caused by the runtime migration, an existing application assumption, a native platform integration, or a third-party dependency.
For applications moving toward .NET 11, the preview period is therefore not just a chance to try the new runtime. It is the appropriate time to establish a regression baseline, exercise real applications, and identify compatibility issues while the runtime transition is still being validated.