Blog hero background

How to Migrate from Xamarin.Forms to .NET MAUI: Complete Guide (2026)

Hafiz Muhammad Hassan03 May 2026

Why migrate, and why now

Microsoft ended support for Xamarin.Forms on May 1, 2024. That means no security patches, no bug fixes, and no OS compatibility updates. Every new iOS and Android release from here on is a potential breakage point you'll have to patch yourself — without any upstream help.

.NET MAUI (Multi-platform App UI) isn't just "Xamarin with a new name." It's a ground-up architectural rebuild on top of .NET 6+, with a single project structure, proper dependency injection, a more capable rendering pipeline, and genuine performance improvements on both iOS and Android. The migration effort is real, but the payoff — maintainability, performance, and access to the modern .NET ecosystem — is worth it.

If your app depends on third-party native SDKs (Swift frameworks or Android AARs), you'll also need to rebuild those bindings for .NET MAUI targets. We cover that in depth in our separate guide: Binding Native iOS and Android SDKs for .NET MAUI.

Area

Xamarin.Forms

.NET MAUI

Supported until

May 2024 (EOL)

Active development

Project structure

Multi-project (iOS, Android, shared)

Single project, multi-target

Minimum .NET

.NET Standard 2.0

.NET 6+ (MAUI 8 = .NET 8)

Custom renderers

ViewRenderer

IViewHandler (lighter)

DI / hosting

Manual / Prism

Built-in MauiAppBuilder

Hot reload

Limited

Full XAML + C# hot reload

Startup performance

Slower cold start

Faster startup, less memory

Desktop support

No

Windows + macOS (Catalyst)

Note: If you're on Xamarin.iOS / Xamarin.Android (not Forms), the migration path is different and more involved. This guide covers Xamarin.Forms → .NET MAUI specifically.

Before you start: audit your dependencies

The single most important step — and the one most teams skip — is auditing every NuGet package in your solution before touching a line of code. Many Xamarin.Forms-era packages have MAUI equivalents; some don't exist yet; some have been absorbed into .NET MAUI itself.

Run the .NET Upgrade Assistant first

Microsoft's .NET Upgrade Assistant performs a compatibility scan and generates a migration report. Install it and run the analyze command on your solution:

dotnet tool install -g upgrade-assistant upgrade-assistant analyze MySolution.sln --target-tfm-support LTS

The report will flag incompatible packages, deprecated APIs, and platform-specific code that needs manual attention. Treat this as your migration backlog — not a to-do list to ignore.

Common packages: what to do with each

Xamarin.Forms package

MAUI replacement

Xamarin.Essentials

Built into MAUI as Microsoft.Maui.Essentials

Xamarin.CommunityToolkit

CommunityToolkit.Maui

Prism.Forms

Prism.Maui (v8.x)

ReactiveUI.XamForms

ReactiveUI.Maui

SkiaSharp.Views.Forms

SkiaSharp.Views.Maui.Controls

Plugin.Permissions

Microsoft.Maui.Essentials (built-in)

Xam.Plugin.Media

MediaPicker in Essentials

CarouselView.FormsPlugin

Built-in CarouselView in MAUI

Important: Garmin Health SDK, native SDK bindings, and custom native libraries need manual binding projects rebuilt for .NET 8 targets. Budget significant time for these — they cannot be automated.

Project structure changes

Xamarin.Forms used a multi-project structure: a shared PCL or .NET Standard project, plus a platform-specific project for each target (iOS, Android, sometimes UWP). MAUI collapses all of this into a single project with multi-targeting via the <TargetFrameworks> property.

Old structure (Xamarin.Forms)

MySolution/ ├── MyApp/ # Shared .NET Standard project │ ├── App.xaml │ ├── MainPage.xaml │ └── MyApp.csproj ├── MyApp.iOS/ # iOS head project │ ├── AppDelegate.cs │ └── MyApp.iOS.csproj └── MyApp.Android/ # Android head project ├── MainActivity.cs └── MyApp.Android.csproj

New structure (.NET MAUI)

MySolution/ └── MyApp/ # Single project, multi-targeted ├── Platforms/ │ ├── Android/ │ │ └── MainActivity.cs │ ├── iOS/ │ │ └── AppDelegate.cs │ ├── MacCatalyst/ │ └── Windows/ ├── Resources/ # Unified assets: fonts, images, raw ├── App.xaml ├── MauiProgram.cs # New entry point └── MyApp.csproj

The new .csproj format

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> <UseMaui>true</UseMaui> <RootNamespace>MyApp</RootNamespace> <ApplicationId>com.yourcompany.myapp</ApplicationId> <ApplicationVersion>1</ApplicationVersion> <ApplicationDisplayVersion>1.0.0</ApplicationDisplayVersion> </PropertyGroup> </Project>

Step-by-step migration walkthrough

Step 1 — Create a new .NET MAUI project alongside your existing solution

Don't try to convert your existing Xamarin project in-place. Create a fresh MAUI project, then migrate code into it incrementally. This keeps your existing app shippable throughout the migration.

dotnet new maui -n MyApp.Maui

Step 2 — Update namespaces throughout your shared code

The most mechanical part of the migration. The Xamarin.Forms namespace becomes Microsoft.Maui and Microsoft.Maui.Controls. Run a global find-and-replace across your shared project:

Xamarin.Forms → Microsoft.Maui.Controls Xamarin.Forms.Xaml → Microsoft.Maui.Controls.Xaml Xamarin.Essentials → Microsoft.Maui.ApplicationModel Xamarin.Forms.PlatformConfiguration → Microsoft.Maui.Controls.PlatformConfiguration

In XAML files, update the xmlns declarations:

<!-- Before (Xamarin.Forms) --> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> <!-- After (.NET MAUI) --> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

Step 3 — Replace App.xaml.cs startup with MauiProgram.cs

MAUI uses a generic host model similar to ASP.NET Core. Your startup logic, service registration, and app configuration all move into MauiProgram.cs:

public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }) .UseMauiCommunityToolkit(); // Register services builder.Services.AddSingleton<IMyService, MyService>(); builder.Services.AddTransient<MainViewModel>(); builder.Services.AddTransient<MainPage>(); return builder.Build(); } }

Step 4 — Move platform-specific code into the Platforms/ folders

Any code that previously lived in your MyApp.iOS or MyApp.Android projects now lives in Platforms/iOS/ and Platforms/Android/ within the single project. The build system automatically includes only the relevant platform code based on the current build target.

Step 5 — Migrate resources to the unified Resources/ folder

MAUI unifies asset management. Images no longer need to live in platform-specific drawable-hdpi / drawable-xhdpi folders on Android and @2x / @3x on iOS. Drop a single SVG or high-resolution PNG into Resources/Images/ and MAUI handles resizing automatically via MSBuild.

<ItemGroup> <MauiImage Include="Resources\Images\logo.svg" BaseSize="120,40" /> </ItemGroup>

Renderers → Handlers: the hardest part

If your app uses custom renderers, this section will take the most time. Xamarin.Forms used ViewRenderer<TView, TNativeView> to bridge controls to native views. MAUI replaces this with a lighter, interface-based Handler architecture.

The key difference: Handlers use a mapper pattern instead of overridable methods, which makes partial customisations much cleaner.

Old renderer pattern

[assembly: ExportRenderer(typeof(MyCustomEntry), typeof(MyCustomEntryRenderer))] namespace MyApp.Android { public class MyCustomEntryRenderer : EntryRenderer { protected override void OnElementChanged( ElementChangedEventArgs<Entry> e) { base.OnElementChanged(e); if (Control != null) Control.Background = Color.Transparent; } } }

New Handler pattern

// 1. Define the handler class public partial class MyCustomEntryHandler : EntryHandler { public static IPropertyMapper<IEntry, EntryHandler> MyMapper = new PropertyMapper<IEntry, EntryHandler>(Mapper); public MyCustomEntryHandler() : base(MyMapper) { } } // 2. Platform-specific implementation in Platforms/Android/ public partial class MyCustomEntryHandler { protected override AppCompatEditText CreatePlatformView() { var view = base.CreatePlatformView(); view.Background = null; return view; } } // 3. Register in MauiProgram.cs builder.ConfigureMauiHandlers(handlers => { handlers.AddHandler(typeof(MyCustomEntry), typeof(MyCustomEntryHandler)); });

Tip: For apps with many renderers, prioritise migrating the ones used on high-traffic screens first. A renderer that's only used on a settings page can wait — ship the migration in layers.

Plugin replacements

Many Xamarin plugins have been superseded by built-in MAUI Essentials APIs. Here are the API-level changes that catch most developers:

Permissions

// Xamarin.Essentials var status = await Permissions.RequestAsync<Permissions.Camera>(); // .NET MAUI Essentials — identical API, different namespace using Microsoft.Maui.ApplicationModel; var status = await Permissions.RequestAsync<Permissions.Camera>();

Connectivity

// Xamarin.Essentials var connected = Connectivity.NetworkAccess == NetworkAccess.Internet; // .NET MAUI — inject IConnectivity instead (testable) public class MyViewModel { private readonly IConnectivity _connectivity; public MyViewModel(IConnectivity connectivity) => _connectivity = connectivity; public bool IsOnline => _connectivity.NetworkAccess == NetworkAccess.Internet; }

SecureStorage

// Same API in both — just update the namespace await SecureStorage.SetAsync("oauth_token", token); var token = await SecureStorage.GetAsync("oauth_token");

Platform-specific code: the Device class is gone

In Xamarin.Forms, platform checks were done via Device.RuntimePlatform. In MAUI, use DeviceInfo.Current.Platform or — better — conditional compilation with #if ANDROID / #if IOS.

// Xamarin.Forms — don't use this in MAUI if (Device.RuntimePlatform == Device.iOS) { ... } // .NET MAUI — runtime check if (DeviceInfo.Current.Platform == DevicePlatform.iOS) { ... } // .NET MAUI — compile-time check (preferred) #if IOS // iOS-only code #elif ANDROID // Android-only code #endif

The Device.BeginInvokeOnMainThread call is also gone. Replace it with:

// Xamarin.Forms Device.BeginInvokeOnMainThread(() => { ... }); // .NET MAUI MainThread.BeginInvokeOnMainThread(() => { ... }); // Or — async version in MAUI 7+ await MainThread.InvokeOnMainThreadAsync(() => { ... });

Testing and CI/CD

MAUI apps can be unit tested with standard xUnit / NUnit — the key is to isolate your ViewModels and services from MAUI's UI layer. Don't try to spin up a MAUI app in your CI environment; test the logic, not the rendering.

Unit testing ViewModels

public class MainViewModelTests { [Fact] public async Task LoadItems_ShouldPopulateItems_WhenServiceSucceeds() { var mockService = new Mock<IItemService>(); mockService.Setup(s => s.GetItemsAsync()) .ReturnsAsync(new List<Item> { new() { Name = "Test" } }); var vm = new MainViewModel(mockService.Object); await vm.LoadItemsAsync(); Assert.Single(vm.Items); } }

GitHub Actions: build and sign for Android

name: MAUI Build on: push: branches: [main] jobs: android: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: dotnet-version: '8.x' - name: Install MAUI workload run: dotnet workload install maui-android - name: Restore run: dotnet restore MyApp/MyApp.csproj - name: Build AAB run: | dotnet publish MyApp/MyApp.csproj \ -f net8.0-android \ -c Release \ /p:AndroidPackageFormat=aab \ /p:AndroidSigningKeyStore=keystore.jks \ /p:AndroidSigningKeyAlias=${{ secrets.KEY_ALIAS }} \ /p:AndroidSigningKeyPass=${{ secrets.KEY_PASS }} \ /p:AndroidSigningStorePass=${{ secrets.STORE_PASS }}

AAB signing gotcha: If you see a multiple certificate chains error, your keystore has more than one certificate entry. Use keytool -list -v -keystore keystore.jks to inspect it and explicitly pass the correct alias via AndroidSigningKeyAlias.

Common pitfalls (and how to avoid them)

1. XAML Hot Reload stops working mid-migration

Hot Reload requires the app to be running in debug mode with a valid MAUI project structure. If you've partially migrated XAML and have mixed namespace declarations, Hot Reload silently fails. Keep your XAML namespace declarations consistent — even one file with old Xamarin xmlns will break reload for the whole project.

2. Shell navigation breaks

If you were using Xamarin.Forms Shell, the route registration API is mostly the same, but Shell's implicit routes have changed. Any page you want to navigate to must be explicitly registered in MauiProgram.cs if it's not already in the ShellContent hierarchy.

3. Fonts not loading on iOS

MAUI requires fonts to be declared in both MauiProgram.cs (via ConfigureFonts) and as a MauiFont item in the .csproj. Missing either declaration causes silent fallback to the system font with no build error.

<MauiFont Include="Resources\Fonts\*.ttf" />

4. CollectionView performance regression

MAUI's CollectionView uses a different virtualization strategy than Xamarin.Forms. If you have lists with complex item templates, set ItemSizingStrategy="MeasureAllItems" only when necessary — it forces measurement of every item and destroys scroll performance on long lists.

5. Android back button handling

Xamarin had OnBackPressed() in MainActivity. In .NET MAUI 8+, handle back navigation via BackButtonBehavior in XAML or override OnNavigatedFrom in your page. The Activity-level override still works but is discouraged.

Realistic timelines by app size

Based on migrations we've run, here's an honest breakdown. "Size" refers to the number of distinct screens, not lines of code.

App size

Screens

Custom renderers

Realistic timeline

Small

5–15

0–2

3–7 days

Medium

15–40

2–6

2–5 weeks

Large

40+

6+

2–4 months

Large + native SDK bindings

40+

6+

3–6 months

These timelines assume one experienced MAUI developer working full-time, a codebase that's reasonably well-structured, and no major architectural changes during migration. If you're also upgrading your architecture — adding proper DI, replacing a legacy nav stack, refactoring into MVVM — add 30–50% to the estimate.

Ship in phases. The best migrations we've run were phased: migrate the project structure and shared code first, get a working build, ship it. Then migrate custom renderers one by one. Then tackle CI/CD. Never try to do everything in one branch — it leads to months-long PRs that are impossible to review.

Need someone to do this for you? We've completed 10+ production Xamarin → MAUI migrations.

Book a free audit →

Final thoughts

Migrating from Xamarin.Forms to .NET MAUI is a significant investment, but it's not optional — Xamarin is dead, and every month you delay is another month of accumulating compatibility debt. The good news: MAUI is genuinely better. The single-project structure, handler architecture, and .NET 8 performance improvements are real improvements, not just a rebrand.

The migration path is well-documented and the tooling has matured considerably since MAUI's rocky .NET 6 launch. On .NET 8, it's stable, performant, and a pleasure to work in.

If you're facing a large codebase, complex native integrations, or simply don't have the internal bandwidth to handle the migration without risking your release schedule, we've done this before — get in touch.

If your migration involves third-party native SDKs that only ship as Swift frameworks or Android AARs — think wearable health devices, payments providers, or biometric auth libraries — the next challenge is binding them into .NET MAUI. We've published a full production guide covering Swift-to-Objective-C wrappers, XCFramework builds, Android AAR binding, and Metadata.xml transformations:

Binding Native iOS and Android SDKs for .NET MAUI: A Production Engineering Guide →

If you'd rather hand the migration off entirely, our Xamarin to .NET MAUI Migration Services page covers our methodology, the ten technical challenges we solve on every engagement, and how pricing is structured.

Keywords

.net mauixamarinmigration
Hafiz Muhammad Hassan

Author

Hafiz Muhammad Hassan

Founder/.NET MAUI/Data Scientist

SHARE
FacebookInstagramXLinkedIn
AitchSoft Logo

Accelerate
Your Business
Growth with Us

We turn your ideas into digital solutions that drive real growth.

By sending this form I confirm that I have read and accept the Privacy Policy.