The migration is done. The project builds, the tests pass, the app is in the stores. And it is slower than the Xamarin.Forms version it replaced.
This is common enough that the dotnet/maui tracker has a recurring genre of issue for it — #26567, #28008, #18531, #24224, #30721 — filed by different teams, describing the same shape of problem. Worth noting: these are user-filed reports, not Microsoft statements. Microsoft has published no claim that migrated apps generally regress, and we are not making one either.
What we are saying is that this specific failure has a small number of specific causes, and the published advice is not helping people find them.
Search for a fix and you get optimisation listicles: use compiled bindings, enable AOT, virtualize your lists, don’t nest layouts. All correct. All useless when the question is not “how do I make a MAUI app fast” but “what did my migration leave behind that the Xamarin version didn’t have.”
Those are different questions. The second one has an answer.
Why this is urgent now, not just annoying
Starting in .NET 11 Preview 6, the optional Microsoft.Maui.Controls.Compatibility NuGet package is no longer built or shipped.
Before that, UseMauiCompatibility() had already been marked obsolete, carrying this warning verbatim:
The compatibility functionality is deprecated. This will be removed in the future, please make sure to migrate to handlers. You might already not be using any of the compatibility functionality anymore, in that case please remove this call, and the reference to the Compatibility NuGet package, as it will introduce unnecessary overhead that negatively impacts performance.
Read that last clause again. Microsoft named the performance cost in the deprecation notice.
Here is the connection nobody is making: the population of apps that migrated cheaply by leaning on the compatibility layer is the same population that is now slow and blocked on .NET 11. One decision, two symptoms, and most teams are treating them as unrelated tickets in different quarters.
If that describes your app, the performance investigation and the .NET 11 upgrade are the same piece of work. Do them once. We covered the runtime side of Preview 6 in Mono Is Gone: What .NET 11 Does to Your Migrated Xamarin App.
The diagnostic
Ordered cheapest-first. Stop when you find your cause; do not run the whole list speculatively.
0. Rule out the free explanation
Measure in Release configuration on physical hardware before anything else. Debug builds on MAUI can be dramatically slower for reasons that have nothing to do with your code, and half of all reported regressions evaporate here.
If it is fast in Release, you are done and it cost you twenty minutes. If it is still slow — and community-filed issues include regressions that reproduce in Release — continue.
1. Find out what you are still shimming
Search the solution for ExportRenderer, ViewRenderer, AddHandler registrations of renderer types, and any reference to Microsoft.Maui.Controls.Compatibility.
.NET MAUI ships shimmed renderer base classes so that Xamarin.Forms renderers deriving from FrameRenderer, ListViewRenderer, NavigationRenderer on iOS, ShellRenderer on iOS and Android, TabbedRenderer on iOS, TableViewRenderer, and VisualElementRenderer keep working. They work. That is precisely the problem — nothing fails, so nobody revisits them.
The structural cost, in Microsoft’s words:
In Xamarin.Forms, the
ViewRendererclass creates a parent element. For example, on Android, aViewGroupis created which is used for auxiliary positioning tasks. In .NET MAUI, theViewHandler<TVirtualView,TPlatformView>class doesn’t create a parent element, which helps to reduce the size of the visual hierarchy and improve your app’s performance.
An extra ViewGroup per renderer is not free, and it compounds — inside a list item template it is paid once per visible row, on every recycle.
Microsoft publishes no benchmark numbers for handlers versus renderers, so do not expect a multiplier from us or anyone else. It is a structural argument. Convert your two or three most-used renderers to handlers, measure, and let your own app settle it.
2. Check whether your bindings are compiled
This one does have published numbers, and they are large. Microsoft states that a compiled binding using property-change notification resolves approximately 8 times quicker than a classic binding, and a OneTime compiled binding approximately 20 times quicker. Setting BindingContext is roughly 5 to 7 times quicker depending on binding mode.
Xamarin.Forms bindings were reflection-based by default. If your migration was a port rather than a rewrite, yours probably still are.
Fix: set x:DataType on the element and let the XAML compiler resolve the expressions at build time. .NET MAUI 9 turned on build warnings for uncompiled bindings by default, so if you are on 9 or later your build log is already telling you.
The trap to avoid: setting x:DataType="x:Object" to silence a warning. Microsoft documents this explicitly as a mistake made when migrating from Xamarin.Forms — it silently disables compiled bindings for that element and every descendant, reverting the whole subtree to reflection, and turns build-time errors into silent runtime failures. If you inherited a migration from someone else, grep for it.
3. Check the layout defaults that changed underneath you
Xamarin.Forms shipped arbitrary non-zero defaults. .NET MAUI set them to zero:
| Property | Xamarin.Forms | .NET MAUI |
|---|---|---|
Grid.ColumnSpacing | 6 | 0 |
Grid.RowSpacing | 6 | 0 |
StackLayout.Spacing | 6 | 0 |
BoxView default size | 40 × 40 | 0 × 0 |
This is usually a visual complaint rather than a performance one — but it drives a pattern worth catching, because the common fix is to paste explicit spacing into every layout, which grows the visual tree rather than setting the values once in a style resource.
Behavioural changes in the same category, all officially documented:
StackLayoutsemantics. MAUI stack layouts stack children in one direction until they run out of children, even if that takes them past the available space. They do not subdivide space, and*AndExpandhas no effect inHorizontalStackLayout/VerticalStackLayout. Xamarin.Forms changed behaviour based on circumstances; MAUI does not.ScrollViewinside aVerticalStackLayoutexpands to its full content height and does not scroll, because the stack layout can expand infinitely.Gridcolumns withAutowidth no longer implicitly word-wrap or truncate aLabel. The column expands past the screen instead.Frameis replaced byBorder.Frameremains to ease migration, and MAUI measures its padding correctly across platforms where Xamarin.Forms had discrepancies — which means correct rendering can still look wrong relative to the app users remember.
4. Separate “not scrolling” from “scrolling badly”
These get filed as the same bug and are not.
Not scrolling is almost always a container problem. A CollectionView takes as much space as the container allows; a Grid constrains children to its own size, a StackLayout lets them extend beyond its bounds. Fix the container.
Scrolling badly is virtualization being defeated, or reflection-based bindings in the item template, or a shimmed renderer inside the template — steps 1 and 2 above, paid per row.
5. Look for leaks, especially on iOS
The dotnet/maui repo wiki documents circular references created by C# events and delegates as a real leak source, with a worked example in which a style holds a collection that back-references a Grid, and the note that this “could cause entire Pages to live forever.” The wiki is explicit that this class of problem occurs on iOS and Mac Catalyst — where C# objects coexist with reference counting — and not on Android or Windows. It also notes the issue is not limited to events: a property holding a Func<int> does the same thing.
The migration-specific version: DisconnectHandler was not automatically invoked by .NET MAUI in .NET 8 and earlier — you had to call it yourself from a suitable point in the app lifecycle. It became automatic by default in .NET 9. If your handlers were written during a migration that started on .NET 7 or 8 and nobody revisited them, that cleanup may still be missing.
One more that produces bug reports rather than slowness, but belongs on any post-migration checklist: Page.OnAppearing no longer fires when the app is backgrounded and foregrounded. Use Window lifecycle events. Plenty of migrated apps have a “data doesn’t refresh” bug with exactly this cause.
6. Only now, reach for the optimisation list
Trimming, AOT, image sizing, layout flattening. These are real and they work — but applied before steps 1 through 5 they are guesswork, and they make the codebase harder to reason about while the actual cause is still in place.
What this costs, and why teams don’t do it
None of the above is difficult. It is a few days of senior engineering time for a mid-sized app, most of it reading rather than writing.
It is hard to fund because it produces no feature. The output is “the app is as fast as the one we replaced,” which is a difficult line item to defend in a roadmap review, and it is why this work reliably loses to the next release.
The argument that works: the compatibility layer stopped shipping in .NET 11. This is no longer a performance nice-to-have competing with features — it is an upgrade blocker with a November 2026 date on it. The performance improvement is what you get for free while clearing it.
If you are also weighing whether MAUI remains the right platform at all, that is a separate and legitimate question — we worked through it in Flutter vs .NET MAUI in 2026. Decide the framework question on its own merits, not on the basis of a migration that was never finished.
FAQ
Do custom renderers still work in .NET MAUI?
Some do. .NET MAUI ships shimmed renderer base classes that let certain Xamarin.Forms custom renderers keep working — those deriving from FrameRenderer, ListViewRenderer, NavigationRenderer on iOS, ShellRenderer on iOS and Android, TabbedRenderer on iOS, TableViewRenderer, and VisualElementRenderer. Working is not the same as being the right long-term answer, and the separate opt-in Microsoft.Maui.Controls.Compatibility package is no longer built or shipped in .NET 11.
Are handlers actually faster than renderers?
Microsoft states that handlers offer performance improvements over custom renderers, and gives a specific architectural reason: the Xamarin.Forms ViewRenderer class creates a parent element — on Android, a ViewGroup used for auxiliary positioning — while the .NET MAUI ViewHandler does not, which reduces the size of the visual hierarchy. Microsoft publishes no benchmark numbers for this comparison, so treat it as a structural argument rather than a measured multiplier, and measure your own app.
Why is my CollectionView slow or not scrolling after migration?
Two different problems are commonly confused here. Not scrolling is usually a container issue: a CollectionView takes as much space as its container allows, and a Grid constrains children to its own size while a StackLayout lets children extend beyond its bounds. Slow scrolling is more often virtualization being defeated, or reflection-based bindings in the item template. Check which symptom you actually have before optimising.
Is Microsoft.Maui.Controls.Compatibility going away?
It has gone. Starting in .NET 11 Preview 6 the optional Microsoft.Maui.Controls.Compatibility NuGet package is no longer built or shipped, and the UseMauiCompatibility method was marked obsolete before that. Apps that only reference Microsoft.Maui.Controls are unaffected.
Could the slowdown just be Debug mode?
Sometimes, and it is always worth ruling out first because it costs nothing. But it is not a universal explanation. Community-filed issues on the dotnet/maui tracker include regressions that reproduce in Release configuration, so a Release-build measurement is the minimum evidence needed before investigating further.
Related reading
- Mono Is Gone: What .NET 11 Does to Your Migrated Xamarin App — the same Preview 6, the other half of the problem.
- Upgrading Flutter Without Surprises: A Diagnostic for What Silently Breaks — the same method applied to a different framework.
- Hardening a Flutter MVP for Production: The Last 20% That Hides the Cost — why the unglamorous remainder is where budgets actually go.
Stuck on a migration that technically finished?
We take on Xamarin and .NET MAUI modernization as a contained workstream, so your team stays on the product roadmap. If you want an outside read on where your performance went, book a 20-minute call.
Sources
- Reuse Xamarin.Forms custom renderers in .NET MAUI — Microsoft Learn
- Migrate a custom renderer to a .NET MAUI handler — Microsoft Learn
- Compiled bindings — Microsoft Learn
- Layout behavior changes from Xamarin.Forms — Microsoft Learn
- UseMauiCompatibility API reference — Microsoft Learn
- What’s new in .NET MAUI for .NET 11 — Microsoft Learn
- Memory leaks — dotnet/maui wiki
Facts verified against the sources above on 31 July 2026. Linked GitHub issues are user-filed reports and are cited as evidence that the problem class exists, not as Microsoft positions.