Skip to content
Legacy Modernisation

Xamarin to .NET MAUI Migration: A Practical Guide with AI-Assisted Code Translation

11 min read Matt Hammond

Xamarin.Forms to .NET MAUI is the most direct mobile migration path available: same language, similar patterns, and Microsoft provides migration tooling. But “most direct” does not mean trivial. Custom Renderers to Handlers, project restructuring, and DI changes need careful handling.

  • The single-project structure replaces the old multi-project Xamarin.Forms solution layout.
  • Custom Renderers to Handlers is the most significant breaking change and the most time-consuming migration task.
  • XAML namespace changes are mechanical and handled well by AI tooling.
  • DependencyService is replaced by built-in .NET dependency injection.
  • AI-assisted tooling handles 55-70% of the mechanical migration work. The rest requires human review, particularly for visual QA on devices.

Your Xamarin.Forms app works. It just cannot stay where it is.

Xamarin reached end of life in May 2024. Your Xamarin.Forms app still runs, still passes tests, and still ships to the App Store, for now. But the framework underneath it has stopped moving. Apple’s SDK requirements advance annually. Your framework does not.

.NET MAUI is the intended successor. It shares C# and XAML with Xamarin.Forms. It runs on .NET 10. It targets the latest iOS and Android SDKs. For most Xamarin.Forms applications, it is the lowest-friction migration path available.

This guide walks through the migration step by step.

Before you start: is MAUI the right target?

MAUI is the right target for most Xamarin.Forms apps. But not all. Consider React Native instead if:

  • Your app is Xamarin.Native (Xamarin.iOS / Xamarin.Android without Xamarin.Forms)
  • You want a broader developer ecosystem and hiring market
  • The app needs a significant UI redesign alongside the migration
  • Your team has JavaScript/TypeScript expertise

For the comparison, see our MAUI vs React Native analysis. For PhoneGap/Cordova apps, MAUI is not an option. See our PhoneGap to React Native guide.

Phase 1: Project restructuring

From multi-project to single-project

Xamarin.Forms solutions typically contain:

MyApp.sln
├── MyApp/                    (shared .NET Standard library)
├── MyApp.iOS/                (iOS platform project)
├── MyApp.Android/            (Android platform project)
└── MyApp.UWP/                (optional UWP project)

MAUI uses a single project with platform-specific folders:

MyApp.sln
└── MyApp/
    ├── Platforms/
    │   ├── iOS/
    │   ├── Android/
    │   ├── MacCatalyst/
    │   └── Windows/
    ├── Resources/
    ├── MauiProgram.cs
    └── MyApp.csproj

How to migrate the project structure:

  1. Create a new MAUI project using dotnet new maui -n MyApp
  2. Copy shared code (ViewModels, Services, Models) into the new project
  3. Move platform-specific code into the appropriate Platforms/ subfolder
  4. Migrate resources (images, fonts) into the Resources/ folder using MAUI’s resource system

The .NET Upgrade Assistant can perform this restructuring automatically:

dotnet tool install -g upgrade-assistant
upgrade-assistant upgrade MyApp.sln --target-tfm net10.0

Review the output carefully. The Upgrade Assistant handles the project file conversion well but can miss custom build configurations and platform-specific MSBuild properties.

NuGet package updates

Xamarin-specific packages need replacing:

Xamarin PackageMAUI Equivalent
Xamarin.FormsMicrosoft.Maui.Controls (included in MAUI)
Xamarin.EssentialsMicrosoft.Maui.Essentials (included in MAUI)
Xamarin.CommunityToolkitCommunityToolkit.Maui
Xamarin.CommunityToolkit.MarkupCommunityToolkit.Maui.Markup

Claude Code handles these package replacements when analysing the .csproj files.

Phase 2: XAML migration

Namespace changes

Every XAML file needs its root namespace updated:

<!-- BEFORE -->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyApp.Views">

<!-- AFTER -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyApp.Views">

This is a mechanical find-and-replace across every XAML file. Claude Code handles it with high accuracy. For a 50-screen app, this takes seconds with AI tooling and hours manually.

Control API changes

Most Xamarin.Forms controls exist in MAUI with the same name. Some have API changes:

<!-- BEFORE: Xamarin.Forms Frame -->
<Frame HasShadow="True" CornerRadius="10" Padding="10">
    <Label Text="Content" />
</Frame>

<!-- AFTER: MAUI Border (Frame is available but Border is preferred) -->
<Border StrokeShape="RoundRectangle 10"
        Padding="10"
        Shadow="{Shadow Brush=Black, Offset='2,2', Opacity=0.3}">
    <Label Text="Content" />
</Border>
<!-- BEFORE: Xamarin.Forms SearchBar -->
<SearchBar Placeholder="Search"
           SearchButtonPressed="OnSearch" />

<!-- AFTER: MAUI SearchBar (same, but event handling may use Command) -->
<SearchBar Placeholder="Search"
           SearchCommand="{Binding SearchCommand}" />

Claude Code identifies these API differences and applies the correct MAUI equivalents. The CLAUDE.md file should specify whether to use the MAUI-preferred approach (Border over Frame, Commands over events) or maintain maximum compatibility.

Phase 3: Custom Renderers to Handlers

This is the most significant migration task. Xamarin.Forms Custom Renderers gave you full control over how a control rendered on each platform, but the API was complex and tightly coupled to the Xamarin rendering pipeline. MAUI Handlers are simpler and more maintainable, but the architecture is fundamentally different.

Understanding the difference

Custom Renderer pattern (Xamarin):

  • Inherits from a platform-specific renderer base class
  • Overrides OnElementChanged and OnElementPropertyChanged
  • Accesses the native control via Control property
  • Registered via assembly attribute

Handler pattern (MAUI):

  • Uses a property mapper to map cross-platform properties to platform-specific implementations
  • Platform-specific code lives in partial classes
  • Accesses the native control via PlatformView property
  • Registered in MauiProgram.cs

Migration example: a custom entry with rounded corners

// BEFORE: Xamarin Custom Renderer (iOS)
[assembly: ExportRenderer(typeof(RoundedEntry),
    typeof(RoundedEntryRenderer))]
namespace MyApp.iOS.Renderers
{
    public class RoundedEntryRenderer : EntryRenderer
    {
        protected override void OnElementChanged(
            ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);
            if (Control == null) return;

            Control.Layer.CornerRadius = 10;
            Control.Layer.BorderWidth = 1;
            Control.Layer.BorderColor = UIColor.SystemGray.CGColor;
            Control.ClipsToBounds = true;
        }
    }
}
// AFTER: MAUI Handler (cross-platform definition)
public partial class RoundedEntryHandler : EntryHandler
{
    public RoundedEntryHandler() : base(PropertyMapper) { }

    public static new IPropertyMapper<IEntry, RoundedEntryHandler>
        PropertyMapper =
            new PropertyMapper<IEntry, RoundedEntryHandler>(
                EntryHandler.Mapper)
    {
        [nameof(IView.Background)] = MapBackground
    };
}

// iOS partial class (in Platforms/iOS/)
public partial class RoundedEntryHandler
{
    private static void MapBackground(
        RoundedEntryHandler handler, IEntry entry)
    {
        handler.PlatformView.Layer.CornerRadius = 10;
        handler.PlatformView.Layer.BorderWidth = 1;
        handler.PlatformView.Layer.BorderColor =
            UIColor.SystemGray.CGColor;
        handler.PlatformView.ClipsToBounds = true;
    }
}

// Registration in MauiProgram.cs
builder.ConfigureMauiHandlers(handlers =>
{
    handlers.AddHandler<RoundedEntry, RoundedEntryHandler>();
});

Claude Code handles this structural conversion well because the mapping is well-defined. The key areas where human review is needed:

  • Renderers that access Element properties beyond standard control properties
  • Renderers with complex lifecycle management (creating/disposing native views)
  • Renderers that use platform-specific gestures or animations

How many renderers do you have?

The number of Custom Renderers is the single best predictor of Xamarin to MAUI migration complexity. Count them before estimating:

Custom RenderersMigration complexityTypical timeline impact
0-5Low1-2 weeks for renderer migration
6-15Moderate2-4 weeks for renderer migration
16-30High4-6 weeks for renderer migration
30+Very high6+ weeks; consider whether MAUI is still the right target

Phase 4: Dependency injection migration

Xamarin.Forms used DependencyService for platform-specific service resolution. MAUI uses the built-in .NET dependency injection container.

// BEFORE: Xamarin DependencyService registration (attribute-based)
[assembly: Dependency(typeof(LocationService))]
namespace MyApp.iOS.Services
{
    public class LocationService : ILocationService
    {
        public async Task<Location> GetCurrentLocation()
        {
            // iOS-specific implementation
        }
    }
}

// Usage in shared code
var location = DependencyService.Get<ILocationService>();
// AFTER: MAUI built-in DI
// Registration in MauiProgram.cs
#if IOS
builder.Services.AddSingleton<ILocationService, 
    Platforms.iOS.LocationService>();
#elif ANDROID
builder.Services.AddSingleton<ILocationService, 
    Platforms.Android.LocationService>();
#endif

// Usage via constructor injection
public class MapViewModel(ILocationService locationService)
{
    public async Task UpdateLocation()
    {
        var location = await locationService.GetCurrentLocation();
    }
}

Claude Code migrates DependencyService registrations to MauiProgram.cs registrations systematically. The human decision: whether to use platform-specific compile directives (#if IOS) or MAUI’s conditional service registration. For most apps, compile directives are clearer.

Phase 5: Navigation migration

Xamarin.Forms offered multiple navigation patterns: NavigationPage, TabbedPage, FlyoutPage, and manual PushAsync/PopAsync calls. MAUI consolidates these under Shell navigation.

// BEFORE: Xamarin NavigationPage
await Navigation.PushAsync(new OrderDetailPage(orderId));

// AFTER: MAUI Shell navigation
await Shell.Current.GoToAsync(
    $"orderDetail?orderId={orderId}");

Shell navigation is route-based, which is cleaner for deep linking and URL-based navigation. The migration involves:

  1. Defining routes in AppShell.xaml
  2. Registering route parameters with [QueryProperty] attributes
  3. Replacing PushAsync/PopAsync calls with GoToAsync

This is a systematic change that Claude Code handles well, though human review is important for navigation flows that depend on the stack order (e.g., clearing the navigation stack, modal presentations).

Phase 6: Testing and device verification

After all code changes are complete, testing happens in three layers:

Unit tests. If you had unit tests for ViewModels and services, they should pass with minimal changes. Test framework migration (if needed) follows the same patterns as backend .NET migration.

Integration tests. API clients, data access, and service interactions should be tested against real (or stubbed) backends.

Device testing. This cannot be automated by AI. Every screen must be tested on physical iOS and Android devices. Focus on:

  • Visual parity with the Xamarin version (padding, font rendering, control sizing)
  • Touch targets and gesture recognition
  • Orientation changes
  • Accessibility (VoiceOver on iOS, TalkBack on Android)
  • Performance on lower-end devices (MAUI’s Android startup time is slower than Xamarin in some configurations)

What we have seen in practice

[CLIENT EXAMPLE: Insurance company, Xamarin.Forms claims app, 52 screens, 18 Custom Renderers, Syncfusion data grid and chart components. Migrated to MAUI over 6 weeks. Custom Renderer migration was the critical path (3 weeks). XAML namespace changes and DependencyService migration were handled almost entirely by Claude Code (~65% AI-authored). The Syncfusion migration was smoother than expected: most API names matched. Key issue: two Custom Renderers that used MessagingCenter for cross-component communication needed manual redesign to use WeakReferenceMessenger.]

[CLIENT EXAMPLE: Healthcare provider, Xamarin.Forms patient check-in app, 28 screens, 8 Custom Renderers, camera integration for ID scanning. Migrated to MAUI over 4 weeks. The camera Custom Renderer was the most complex migration (used AVFoundation directly on iOS). AI-assisted tooling handled the standard renderers well. Total AI-authored code: ~60%. Key lesson: MAUI’s camera API has improved since Xamarin, so the custom renderer was simplified rather than directly translated.]

[CLIENT EXAMPLE: Logistics company, Xamarin.Forms driver app, 35 screens, 12 Custom Renderers, offline SQLite sync, Bluetooth LE for barcode scanners. Migrated to MAUI over 7 weeks. The Bluetooth LE custom renderer required platform-specific rewriting because the MAUI Bluetooth landscape had changed. SQLite migration was straightforward (sqlite-net-pcl works on both platforms). AI-assisted tooling was most effective on the XAML migration and ViewModel restructuring.]

Next steps

Before starting a Xamarin to MAUI migration, assess your application’s complexity using our approach from the mobile migration playbook. Count your Custom Renderers, catalogue your third-party components, and verify that MAUI equivalents exist.

For the decision between MAUI and React Native, see our honest comparison.

For IP considerations when using AI tools in migration, see Who Owns AI-Written Code?.

For the general modernisation decision framework, see Modernise, Rebuild, or Replace.

Ready to plan your migration? Book a free Mobile Migration Assessment consultation.

Frequently asked questions

Can I migrate Xamarin.Native apps to MAUI?
MAUI is the successor to Xamarin.Forms, not Xamarin.Native. If your app is Xamarin.iOS and Xamarin.Android with no shared UI layer, the migration effort to MAUI is closer to a rewrite than a migration. Consider React Native as an alternative target.
Do I need to migrate all Custom Renderers at once?
No. MAUI supports a compatibility mode that allows Xamarin.Forms-style renderers to run alongside native MAUI Handlers. You can migrate renderers incrementally. However, we recommend migrating them during the main migration rather than carrying compatibility shims into production.
What happens to my Syncfusion or Telerik controls?
Both vendors have MAUI component suites. Most controls have direct MAUI equivalents with similar (but not identical) APIs. Check the vendor's migration guide for your specific components before starting. Claude Code handles the API renaming when given the migration documentation as context.
Will my MAUI app look and behave identically to the Xamarin.Forms version?
MAUI uses a different rendering architecture (Handlers vs Renderers), so pixel-perfect visual parity is not guaranteed. Most UI differences are minor (padding, font rendering, default control styling). Plan for a visual QA pass on both iOS and Android after migration.
How long does a typical Xamarin.Forms to MAUI migration take?
A mid-complexity app (30-60 screens, 10-20 custom renderers, one component library) typically takes 4-8 weeks with AI-assisted development. Simpler apps (under 20 screens, few custom renderers) can be faster. Complex apps with many custom renderers and deep platform-specific code take longer.

Ready to transform your software?

Let's talk about your project. Contact us for a free consultation and see how we can deliver a business-critical solution at startup speed.