Xamarin.Forms hit end of life in May 2024. Microsoft is no longer patching security vulnerabilities, and the ecosystem has fully moved on. If you’re still shipping a Xamarin.Forms app, migrating to .NET MAUI isn’t optional anymore — it’s a matter of when, not if.
The good news: most of the migration is mechanical. The architecture concepts carry over, the XAML is familiar, and Microsoft built tooling to help. This guide gives you the full picture — no hand-waving, just the actual steps.
Table of Contents
- What Actually Changed Between Xamarin.Forms and MAUI
- Before You Start: Inventory Your Dependencies
- Using the .NET Upgrade Assistant
- Project Structure and File Changes
- Namespace and API Changes
- Custom Renderers → Handlers
- Xamarin.Essentials → .NET MAUI Essentials
- Testing the Migrated App
- FAQ
- Conclusion
What Actually Changed Between Xamarin.Forms and MAUI
Understanding the architectural differences prevents nasty surprises mid-migration.
Single Project Architecture
Xamarin.Forms used a solution with multiple platform projects (iOS, Android) plus a shared project. .NET MAUI collapses this into a single project with a Platforms/ folder for platform-specific code. The build system handles the rest via multi-targeting.
New Handler Architecture
Custom renderers are gone. MAUI replaced them with a lighter Handler system. Handlers are faster, composable, and don’t carry the overhead of the old renderer inheritance chain. Any custom renderer you have needs to be rewritten as a handler — this is typically the most work-intensive part of migration.
Startup Model
Xamarin.Forms initialized via Forms.Init() calls scattered in each platform’s startup class. MAUI uses a single MauiProgram.cs with a fluent builder pattern — the same pattern used by ASP.NET Core and the .NET Generic Host.
Before You Start: Inventory Your Dependencies
Run this before touching any code. It will save you from migrating 80% of the app only to discover a critical dependency has no MAUI equivalent.
Check NuGet Compatibility
- Visit each package on nuget.org and check if it targets
net8.0-android/net8.0-iosor has a MAUI-specific version - Xamarin-specific packages (e.g.,
Xamarin.Forms,Xamarin.Essentials) are replaced — don’t bring them along - Most popular plugins (Shiny, Plugin.Maui.Audio, etc.) have MAUI ports
Audit Custom Renderers
List every custom renderer in your shared project. Each one needs either a handler replacement or a third-party control. Prioritize this list — it’s your migration risk register.
Check Platform APIs Used
If you access native APIs (camera, Bluetooth, push notifications), verify MAUI provides the equivalent or that a community library covers it. [INTERNAL_LINK: .NET MAUI platform-specific code guide]
Using the .NET Upgrade Assistant
Microsoft’s Upgrade Assistant automates the mechanical parts of migration:
# Install the tool
dotnet tool install -g upgrade-assistant
# Analyze your solution first (dry run)
upgrade-assistant analyze MySolution.sln
# Run the migration
upgrade-assistant upgrade MySolution.sln
What It Does Automatically
- Converts project files from legacy format to SDK-style
.csproj - Merges platform projects into a single MAUI project
- Updates NuGet package references where direct equivalents exist
- Renames namespaces where the mapping is 1:1
- Moves platform-specific files into
Platforms/subdirectories
What It Does NOT Do
The tool doesn’t rewrite custom renderers, fix broken XAML that uses removed APIs, or resolve dependency conflicts. Think of it as a 60% solution — essential, but you still need to manually finish the job.
Project Structure and File Changes
Here’s how the file layout changes after migration:
# Xamarin.Forms (before)
MyApp/
MyApp/ # Shared code
App.xaml
MainPage.xaml
MyApp.Android/ # Android project
MyApp.iOS/ # iOS project
# .NET MAUI (after)
MyApp/
App.xaml
MainPage.xaml
MauiProgram.cs # New entry point
Platforms/
Android/
MainActivity.cs
iOS/
AppDelegate.cs
Program.cs
Windows/
App.xaml
Resources/
Fonts/
Images/
Splash/
App Startup Rewrite
// MauiProgram.cs
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMaui()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
builder.Services.AddSingleton<MainPage>();
builder.Services.AddSingleton<MainViewModel>();
return builder.Build();
}
}
App.xaml Changes
Remove xmlns:forms references and update the root namespace from Xamarin.Forms to Microsoft.Maui.Controls. The Application class structure stays the same.
Namespace and API Changes
The most common find-and-replace operations you’ll run across your XAML and C# files:
// Namespace changes
Xamarin.Forms → Microsoft.Maui.Controls
Xamarin.Forms.Xaml → Microsoft.Maui.Controls.Xaml
Xamarin.Essentials → Microsoft.Maui.ApplicationModel (and others)
// Color changes
Color.FromHex("#FF5733") → Color.FromArgb("#FF5733")
Color.Default → null
// Thickness / Padding
new Thickness(10, 0, 10, 0) → same (no change)
// Device class
Device.RuntimePlatform → DeviceInfo.Platform
Device.BeginInvokeOnMainThread → MainThread.BeginInvokeOnMainThread
Device.OpenUri → Launcher.OpenAsync
// Layout changes
StackLayout → VerticalStackLayout / HorizontalStackLayout
AbsoluteLayout bounds syntax → updated (Rect instead of Rectangle)
XAML Namespace Headers
Update your XAML file headers from:
<!-- Xamarin.Forms -->
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
<!-- .NET MAUI -->
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Custom Renderers → Handlers
This is the hardest part. A custom renderer in Xamarin.Forms looks like:
// Xamarin.Forms custom renderer (before)
[assembly: ExportRenderer(typeof(MyEntry), typeof(MyEntryRenderer))]
public class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
Control.BackgroundColor = UIColor.Clear;
}
}
The equivalent MAUI handler:
// .NET MAUI handler (after)
public class MyEntryHandler : EntryHandler
{
protected override void ConnectHandler(MauiTextField platformView)
{
base.ConnectHandler(platformView);
platformView.BackgroundColor = UIColor.Clear;
}
}
// Register in MauiProgram.cs
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<MyEntry, MyEntryHandler>();
});
Mapper-Based Customizations
For simple property customizations, you don’t even need a full handler subclass — use the mapper:
Microsoft.Maui.Controls.Handlers.Compatibility.FrameRenderer.FrameMapper.AppendToMapping(
"NoElevation", (handler, view) =>
{
// platform-specific tweak
});
Xamarin.Essentials → .NET MAUI Essentials
Xamarin.Essentials is built directly into .NET MAUI — no separate package needed. But the namespaces changed:
// Old
using Xamarin.Essentials;
var location = await Geolocation.GetLocationAsync();
var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
// New
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Devices.Sensors;
var location = await Geolocation.GetLocationAsync();
var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
Most method signatures are identical — it’s mainly a namespace update. The SecureStorage, Connectivity, DeviceInfo, and Clipboard APIs all follow the same pattern.
Testing the Migrated App
Build First, Then Run
After migration, expect build errors. Work through them systematically: fix namespace errors first, then API mismatches, then UI rendering issues. Don’t try to run until it compiles cleanly.
Platform-by-Platform Testing
- Test Android first — it has the fastest emulator loop
- Test iOS on simulator before physical device
- Test Windows if you target it — Windows UI behaviors differ
- Check all custom controls / renderers/handlers explicitly
Visual Regression Checklist
- Font sizes and weights render correctly
- Colors and themes match the original app
- Navigation gestures work (back swipe on iOS)
- Keyboard avoidance behavior on forms
- Safe areas and notch handling on modern devices
FAQ
Is it worth migrating an app with a small user base?
Yes — not for the users, but for your dependencies. Xamarin.Forms packages are unmaintained, which means security vulnerabilities accumulate silently. Even small apps process data that deserves protection.
Can I run Xamarin.Forms and .NET MAUI side by side during migration?
Not in the same project, but you can migrate feature by feature by keeping separate branches. Some teams also ship a MAUI rewrite as a new app and deprecate the Xamarin version — this works well for apps with small user bases.
Does Xamarin.Forms XAML work in MAUI without changes?
Mostly yes, but not entirely. The XML namespace header must change, deprecated controls must be replaced, and some binding syntax edge cases behave differently. Expect to touch most XAML files but rarely rewrite them from scratch.
How long does migration typically take?
Small apps (under 10 screens, no custom renderers): 1–3 days. Medium apps (10–30 screens, a few renderers): 1–3 weeks. Large apps with extensive platform code and custom controls: 1–3 months. The Upgrade Assistant cuts the mechanical work but doesn’t change these estimates much for complex apps.
Are Shell navigation and MAUI Shell the same as Xamarin.Forms Shell?
Conceptually yes — the flyout/tab/stack structure is identical. But some route registration syntax changed and the CSS styling for Shell chrome differs. Plan for 1–2 days of Shell-specific testing even if your navigation looks identical post-migration.
Conclusion
Migration from Xamarin.Forms to .NET MAUI is mandatory, not optional — and 2026 is late to still be on Xamarin. The Upgrade Assistant handles the mechanical parts. Your real work is in the handler rewrites, the namespace sweeps, and the platform-by-platform QA pass.
The reward is real: MAUI is faster, better maintained, and aligned with the broader .NET ecosystem. Once you’re on MAUI, you’re on a platform Microsoft is actively investing in through .NET 9 and beyond.
[INTERNAL_LINK: .NET MAUI Shell navigation deep dive] — after migration, Shell is worth learning properly to get the most out of MAUI’s navigation system.

Leave a Reply