MaboaSoft Engineering Team

The Xamarin to .NET MAUI Migration Readiness Checklist (with Runnable Commands)

Most failed Xamarin to .NET MAUI migrations fail because nobody inventoried the risk first. This is a pre-migration readiness audit you can run against your own repository — runnable commands to count custom renderers, effects, DependencyService usage, deprecated Xamarin.Forms.Device calls, and NuGet packages with no .NET equivalent — plus the prerequisite and bootstrap gates that decide whether the app launches at all.

The Xamarin to .NET MAUI Migration Readiness Checklist (with Runnable Commands)

Most Xamarin to .NET MAUI migrations that go badly go badly for the same reason: nobody inventoried the risk before starting. The migration then discovers the expensive parts mid-flight, and the budget and timeline discover them too. This checklist flips that around. Before you migrate, run a readiness audit against your own repository that answers five questions: how many custom renderers you have, how many effects, how much DependencyService usage, how many calls to deprecated APIs like the Xamarin.Forms.Device class, and how many NuGet dependencies have no .NET-compatible version. Each is manual work the .NET Upgrade Assistant does not fully automate. Below are runnable commands to count each one, the two gates that decide whether the app even launches, and a scoring rubric to turn the results into an honest estimate. All facts here are from Microsoft’s official migration docs.

The failure this checklist prevents

The most expensive migration outcome is the silent runtime failure: the project compiles, the IDE reports no errors, the app deploys to a device — and then crashes on launch or misbehaves with no stack trace. Everything a compiler can check has passed while the app is still broken.

That failure almost always traces back to something a readiness audit would have flagged: an unported custom renderer, a DependencyService that was never re-registered, a deprecated API silently returning the wrong thing, or a platform entry point that was never bootstrapped. You cannot fix in QA what you never scoped. The point of this checklist is to move those discoveries to before the migration, where they cost an hour of grep instead of a week of debugging.

Gate 1 — Get to Xamarin.Forms 5 first

Do not migrate from an old Xamarin.Forms version. Microsoft’s guidance is explicit: before upgrading, update the app to Xamarin.Forms 5, update its dependencies to their latest versions, and confirm it still runs correctly. This minimizes the API gap between Xamarin.Forms and .NET MAUI and surfaces dependency problems while you are still on a working app.

Concrete prerequisites from the docs:

  • The .NET Upgrade Assistant requires Xamarin.Forms 4.8 or higher; Microsoft recommends Xamarin.Forms 5.0 and .NET Standard 2.0 or higher for best results.
  • Visual Studio 2022 17.6.0 or later is recommended to run the Upgrade Assistant.
  • .NET MAUI’s minimum platform versions are Android API 21 and iOS 11.0 — confirm your app does not require anything lower.

Find your current Xamarin.Forms version before anything else:

# From the repo root — every Xamarin.Forms package reference and its version
grep -rhoE 'Xamarin\.Forms"? Version="[^"]+"' --include=*.csproj . 2>/dev/null
grep -rhoE 'Xamarin\.Forms" version="[^"]+"' --include=packages.config . 2>/dev/null

If that reports anything below 5.0, your first project is a Xamarin.Forms upgrade, not a MAUI migration. Sequence it first.

Inventory 1 — Custom renderers (the biggest cost driver)

Custom renderers do not port automatically: .NET MAUI replaces the renderer architecture with handlers, and every renderer must be reimplemented. This is usually the single largest block of manual effort, so count it precisely. Custom renderers are registered with an [assembly: ExportRenderer(...)] attribute and derive from a platform renderer base class:

# Count renderer registrations (one per custom renderer)
grep -rn 'ExportRenderer' --include=*.cs . | wc -l

# List the files so you can scope each one
grep -rln 'ExportRenderer\|: .*Renderer<\|CustomRenderer' --include=*.cs .

Record the number. A handful is a small job; dozens is a multi-month engineering line item and the main thing your estimate hinges on. Microsoft’s remediation guidance is in Use custom renderers in .NET MAUI.

Inventory 2 — Effects

Effects are a lighter-weight customization mechanism than renderers, but they also need updating for .NET MAUI. They register with [assembly: ExportEffect(...)] and ResolutionGroupName:

grep -rn 'ExportEffect\|ResolutionGroupName' --include=*.cs . | wc -l
grep -rln 'ExportEffect' --include=*.cs .

Each effect is a smaller unit of work than a renderer, but the count still feeds the estimate. See Use effects in .NET MAUI.

Inventory 3 — DependencyService usage

Xamarin.Forms’ DependencyService is superseded by standard .NET dependency injection in .NET MAUI: platform services are registered on the MauiApp builder (for example with AddTransient) instead. Every DependencyService call site is a place that needs rewiring, and a missed registration is a classic cause of a runtime crash:

# Registrations and resolutions
grep -rn 'DependencyService\.\|\[assembly: Dependency' --include=*.cs . | wc -l
grep -rln 'DependencyService' --include=*.cs .

Plan to replace these with constructor injection registered in MauiProgram. The count tells you how much of the app’s service wiring you are touching.

Inventory 4 — Deprecated and removed APIs

Some Xamarin.Forms APIs were removed or replaced. The highest-frequency one is the Xamarin.Forms.Device class, which .NET MAUI deprecates and splits across several types (DeviceInfo, MainThread, Dispatcher, AppInfo, and others). Other common ones: Color.FromHex is obsolete, RelativeLayout exists only as a compatibility control (Microsoft recommends Grid), and the default XAML namespace changed.

# Xamarin.Forms.Device usage (BeginInvokeOnMainThread, RuntimePlatform, StartTimer, OnPlatform, Idiom, ...)
grep -rnE '\bDevice\.(BeginInvokeOnMainThread|RuntimePlatform|OS|StartTimer|OnPlatform|Idiom|Invalidate|OpenUri)' --include=*.cs . | wc -l

# Obsolete Color.FromHex
grep -rn 'Color\.FromHex' --include=*.cs . | wc -l

# RelativeLayout (compatibility-only in MAUI; prefer Grid)
grep -rn 'RelativeLayout' --include=*.xaml --include=*.cs . | wc -l

# Old XAML namespace that must be replaced with the MAUI one
grep -rn 'xamarin.com/schemas/2014/forms' --include=*.xaml . | wc -l

The XAML namespace change is find-and-replace: http://xamarin.com/schemas/2014/forms becomes http://schemas.microsoft.com/dotnet/2021/maui. The Device and layout changes need per-call-site judgement. Microsoft’s full API-change tables are in Manually upgrade a Xamarin.Forms app to a multi-project .NET MAUI app.

Also note a behavioural change that no grep will catch: in .NET MAUI, Page.OnAppearing is not called when an app is backgrounded and returns to the foreground on any platform. If your app relied on that (it worked on Android in Xamarin.Forms), you must move the logic to Window lifecycle events. Add a manual review item for foreground/resume logic.

Inventory 5 — NuGet dependency compatibility

Xamarin.Forms NuGet packages are generally not compatible with modern .NET unless recompiled with .NET target framework monikers (TFMs). This is the wildcard that overruns budgets, so audit it up front. Microsoft’s compatibility rule:

  • Compatible targets include net8.0-android, net8.0-ios, net8.0-maccatalyst, monoandroid, and dependency-free .NET Standard 2.0.
  • Incompatible targets include monotouch, xamarinios, xamarinios10, xamarinmac, xamarinmac20, and xamarinwatchos.

List every package you depend on, then check each one’s Frameworks tab on nuget.org:

# Every PackageReference across the solution
grep -rhoE '<PackageReference Include="[^"]+" Version="[^"]+"' --include=*.csproj . | sort -u

# Legacy packages.config style, if present
grep -rhoE '<package id="[^"]+" version="[^"]+"' --include=packages.config . | sort -u

For each package, record one of three outcomes: has a .NET version (cheap), has a successor you must swap to (medium), or is abandoned with no equivalent (expensive — you reimplement or replace it). The count of the third category is the riskiest number in your whole estimate.

What the Upgrade Assistant will not touch

The .NET Upgrade Assistant automates the mechanical conversion — SDK-style projects, retargeting, <UseMaui>true</UseMaui>, some NuGet swaps (it removes Xamarin.Forms/Essentials and replaces the Xamarin Community Toolkit and SkiaSharp packages), and namespace updates. Install and run it via:

dotnet tool install -g upgrade-assistant
upgrade-assistant upgrade

But it explicitly does not support upgrading UWP projects, iOS extension projects, or binding projects, and it does not resolve renderers, effects, or incompatible dependencies. If your solution has any of those project types, add them as separate manual line items now — they are a common source of “the tool finished but the app doesn’t build.”

The bootstrap gate — where “app won’t launch” comes from

.NET MAUI apps start from a single cross-platform entry point: a static MauiProgram.CreateMauiApp() that returns a MauiApp built with MauiApp.CreateBuilder(). Each platform head must be updated to hand off to it:

  • Android: MainApplication inherits MauiApplication; MainActivity inherits MauiAppCompatActivity.
  • iOS: AppDelegate inherits MauiUIApplicationDelegate.
  • Windows (WinUI 3): App inherits MauiWinUIApplication.

If these are not wired up, the app compiles and then fails at runtime — the silent runtime failure again. Microsoft’s own troubleshooting guidance for “App won’t launch” is precisely: update each platform project’s entry point class and the app entry point. Treat the bootstrap as a required, separately-verified step, not something to assume worked because the build passed.

Validation — how to know the migration actually worked

A migration is not done when it compiles. Because the dangerous failures live at runtime, validation must exercise the app on real devices:

  1. Launch on each platform — Android, iOS, and Windows if targeted. A clean build proves nothing here.
  2. Walk every screen that had a custom renderer or effect. These are the highest-risk surfaces.
  3. Exercise every DependencyService-backed feature to confirm services resolve.
  4. Test background-to-foreground resume on every platform, given the OnAppearing change.
  5. Check the known-difference surfaces Microsoft documents: CollectionView scrolling inside the wrong container, BoxView defaulting to 0x0 instead of 40x40, popups on iOS, and layout padding/spacing defaults.
  6. Clean between attempts — delete all bin/obj folders and the Android Resource.designer.cs when changing .NET versions, as Microsoft recommends, to avoid chasing stale-artifact ghosts.

Automated tests help, but the decisive validation for a Xamarin migration is human QA on device. Budget for it.

Score your readiness

Tally the inventory into a simple risk profile:

SignalLow riskHigh risk
Xamarin.Forms version5.0, app runsbelow 5.0
Custom renderers0–5dozens
Effectsfewmany
DependencyService call sitesfewpervasive
Deprecated Device/API usagerarethroughout
Incompatible NuGet packagesnoneseveral abandoned
Non-supported project types (UWP/extensions/bindings)nonepresent
Test coverage / original teamgood / presentnone / gone

An app that sits on the left is a matter of weeks. An app that sits on the right is a multi-month engineering project, and its plan needs a funded discovery phase, not an optimistic fixed price. If you want the numbers behind that, read the Xamarin to .NET MAUI migration cost and effort guide; if you are still deciding whether to migrate at all, read your realistic options if you are still on Xamarin in 2026.

If the original team is gone and nobody can explain the app’s behaviour, run a legacy repository discovery before you migrate — a faithful migration of a misunderstood app faithfully reproduces its bugs.

Get a scoped migration plan

Run the commands above and you will have the raw numbers a real estimate is built from. If you would rather hand that to a senior team and get back a scoped plan and a defensible number, book a 20-minute call or look at how we work. Xamarin modernization is core to what we do.

FAQ

What should I check before migrating a Xamarin.Forms app to .NET MAUI? Before migrating, confirm the app is on Xamarin.Forms 5 and still runs, then inventory the five things that carry the most risk: custom renderers, effects, DependencyService usage, deprecated Xamarin.Forms.Device (and other removed) APIs, and NuGet dependencies with no .NET-compatible version. Each of these needs manual work that the .NET Upgrade Assistant does not fully automate, so counting them up front is what makes an estimate defensible.

Does the .NET Upgrade Assistant handle everything in a Xamarin migration? No. The .NET Upgrade Assistant converts projects to SDK-style, retargets them, sets UseMaui, swaps some NuGet packages, and updates namespaces — but Microsoft states additional effort is required afterward. It also does not support upgrading UWP projects, iOS extension projects, or binding projects, and it does not rewrite custom renderers, effects, or incompatible dependencies. Treat it as automation for the mechanical parts, not a full migration.

Why does a migrated Xamarin app compile but crash on launch? This is usually a bootstrap problem, not a code problem. .NET MAUI apps start from a single MauiProgram.CreateMauiApp() entry point, and each platform head must be updated — MainApplication to MauiApplication, MainActivity to MauiAppCompatActivity, and AppDelegate to MauiUIApplicationDelegate. If the entry points and service registration are not wired correctly, the compiler sees nothing wrong and the app fails at runtime. Confirm the bootstrap before assuming your business logic is broken.

How do I know if a NuGet package is compatible with .NET MAUI? Check the package’s Frameworks tab on nuget.org. A package is compatible if it lists a .NET target such as net8.0-android, net8.0-ios, or a dependency-free .NET Standard 2.0 target. Frameworks like monotouch, xamarinios, xamarinios10, xamarinmac, and xamarinwatchos are not compatible. If no compatible version exists, you either recompile it yourself, find a preview build, or replace it — and that replacement work belongs in the estimate.


Sources (accessed 14 July 2026):