DEV Community

Cover image for Migrating a Large Enterprise Xamarin Application to .NET MAUI: Practical Strategies That Actually Work
NuvyntraLabs
NuvyntraLabs

Posted on

Migrating a Large Enterprise Xamarin Application to .NET MAUI: Practical Strategies That Actually Work

Migrating a small Xamarin.Forms application to .NET MAUI is usually manageable.

Migrating a large enterprise application is a different story.

The difficulty is rarely the Xamarin.Forms namespace replacement or converting a .csproj file. The real challenge is that an enterprise application has accumulated years of assumptions around Xamarin, third-party libraries, native platform APIs, custom renderers, background processing, authentication, local databases, CI/CD, and production behavior.

Microsoft provides migration tooling and official migration paths, but the tooling should be treated as an accelerator—not as the migration strategy itself. The Upgrade Assistant can perform many project and namespace transformations, but additional manual work is expected.

The practical question therefore becomes:

How do you migrate a large Xamarin application while continuing to develop, test, and release the product?

This article focuses on that problem.


1. Don't Start With the .csproj

One of the most common mistakes is starting the migration by changing:

<TargetFramework>...</TargetFramework>
Enter fullscreen mode Exit fullscreen mode

and then fixing whatever breaks.

That approach works reasonably well for smaller applications.

For a large application, it creates a huge error list before you understand where the real risks are.

Instead, start by answering:

  • How many screens exist?
  • Which screens are business-critical?
  • Which NuGet packages are Xamarin-specific?
  • Which controls use custom renderers?
  • Which features depend on native Android/iOS code?
  • Are there background services?
  • How are push notifications implemented?
  • How does offline synchronization work?
  • Which parts depend on third-party SDKs?
  • How is the application signed and distributed?
  • Which production workflows cannot fail?

The first deliverable should be a dependency and feature map, not a converted project.


2. Divide the Application Into Migration Units

A 200-screen application should not be treated as one migration task.

Break it into units.

For example:

Application
│
├── Authentication
├── Customer
│   ├── Customer Search
│   ├── Customer Details
│   └── Customer History
│
├── Orders
│   ├── Order Creation
│   ├── Order Editing
│   └── Order Submission
│
├── Offline
│   ├── Local Database
│   ├── Queue
│   └── Synchronization
│
├── Notifications
├── Location
├── Documents
└── Reporting
Enter fullscreen mode Exit fullscreen mode

Each unit should have its own migration status.

A simple tracking model is enough:

Feature Xamarin MAUI Dependency Risk Testing
Login Working Migrated Low Automated
Customer Search Working Migrated Medium Regression
Order Creation Working In progress High Full workflow
Push Notifications Working In progress High Device testing
Offline Sync Working Not started Very High Scenario testing

This changes the migration from:

"The application is 60% migrated."

to:

"Authentication and customer workflows are migrated; offline synchronization and notifications remain."

That is much more useful to an engineering team.


3. Freeze Architecture, Not Development

A large enterprise application may need to continue receiving new features while migration is happening.

This creates a difficult situation.

The Xamarin application is still in production, but the team is building the future MAUI version.

Do not freeze all development for months.

Instead, establish a rule:

New feature
     │
     ├── Business logic → Shared modern layer
     │
     ├── Xamarin UI → Temporary implementation
     │
     └── MAUI UI → Preferred implementation
Enter fullscreen mode Exit fullscreen mode

The important part is that new business logic should not become more dependent on Xamarin.

For example, avoid introducing:

public class OrderService
{
    Xamarin.Forms.Application.Current...
}
Enter fullscreen mode Exit fullscreen mode

Instead:

public class OrderService
{
    private readonly IOrderRepository _repository;

    public OrderService(IOrderRepository repository)
    {
        _repository = repository;
    }
}
Enter fullscreen mode Exit fullscreen mode

The service can then be consumed by both generations of the application.

This is one of the most effective ways to prevent the migration from becoming a moving target.


4. Use a Compatibility Layer Instead of Rewriting Everything

Large Xamarin applications often contain hundreds of dependencies on platform-specific functionality.

Trying to replace everything immediately creates unnecessary work.

Introduce interfaces around unstable or platform-specific functionality.

For example:

public interface IDeviceService
{
    Task<string?> GetDeviceIdAsync();
    Task<bool> IsNetworkAvailableAsync();
}
Enter fullscreen mode Exit fullscreen mode

The implementation can change:

Xamarin
   ↓
XamarinDeviceService

MAUI
   ↓
MauiDeviceService
Enter fullscreen mode Exit fullscreen mode

The application code does not need to know which implementation is being used.

This approach is particularly useful for:

  • Device information
  • Secure storage
  • File access
  • Connectivity
  • Location
  • Notifications
  • Logging
  • Analytics
  • Authentication
  • Background processing

The goal is not to create abstractions everywhere.

Create them specifically where migration or platform differences are expected.


5. Treat Custom Renderers as a Separate Migration Problem

Custom renderers are frequently one of the biggest sources of migration effort.

A typical Xamarin application may contain:

CustomRenderer
├── Android
├── iOS
├── Shared Control
└── Platform-specific behavior
Enter fullscreen mode Exit fullscreen mode

In MAUI, handlers provide the preferred customization model.

However, you do not necessarily need to rewrite every renderer on day one. MAUI can reuse certain Xamarin.Forms renderer implementations through compatibility/shim support, although this approach has limitations and becomes more important to revisit as you move to newer MAUI versions.

A practical strategy is:

Existing Renderer
       │
       ├── Simple + stable
       │       ↓
       │   Temporary reuse
       │
       ├── Complex but required
       │       ↓
       │   Migrate later
       │
       └── Obsolete behavior
               ↓
           Delete it
Enter fullscreen mode Exit fullscreen mode

Do not automatically convert every renderer to a handler.

First ask:

Why does this renderer exist?

Sometimes the requirement disappeared years ago.


6. Be Aggressive About Removing Dead Xamarin Code

Migration is an excellent opportunity to identify code that no longer has a purpose.

Large Xamarin applications often contain:

  • Old platform workarounds
  • Deprecated controls
  • Duplicate services
  • Unused renderers
  • Legacy navigation code
  • Old dependency injection registrations
  • Abandoned feature flags
  • Xamarin-specific utilities
  • Compatibility packages added years ago

Don't migrate these blindly.

Use this decision:

Does the code still provide business value?
        │
        ├── Yes → Migrate
        │
        ├── Maybe → Validate usage
        │
        └── No → Delete
Enter fullscreen mode Exit fullscreen mode

Every line of legacy code that you remove is one less thing the MAUI application needs to maintain.


7. Migrate Dependencies Before UI

A common approach is:

Page 1
Page 2
Page 3
Page 4
...
Enter fullscreen mode Exit fullscreen mode

This looks productive, but it can be misleading.

Suppose 80 pages depend on:

OldControls
OldDatabase
OldNavigation
OldAuthentication
Enter fullscreen mode Exit fullscreen mode

Migrating the pages first means the same dependency problems appear repeatedly.

Instead, migrate foundational dependencies first:

API Client
     ↓
Authentication
     ↓
Storage
     ↓
Database
     ↓
Telemetry
     ↓
Navigation
     ↓
UI
Enter fullscreen mode Exit fullscreen mode

Once the foundation is stable, migrating individual screens becomes significantly more predictable.

Microsoft's migration guidance similarly recommends updating dependencies and ensuring the Xamarin.Forms application is stable before moving further into the migration.


8. Don't Assume Every NuGet Package Has a MAUI Replacement

This is where many enterprise migrations get stuck.

You may discover:

Xamarin Package
       ↓
No MAUI version
Enter fullscreen mode Exit fullscreen mode

Don't immediately search for a package with a similar name.

Classify the dependency:

Option 1 — Replace

There is a maintained MAUI/.NET equivalent.

Option 2 — Remove

The application no longer needs it.

Option 3 — Wrap

The package can remain behind an interface temporarily.

Option 4 — Fork

The package is important but abandoned and the organization controls the source.

Option 5 — Rewrite

The dependency is small enough to replace with internal code.

The worst strategy is:

"Let's keep searching until we find something that looks similar."

A package replacement can introduce completely different runtime behavior.


9. Separate UI Migration From Business Logic Migration

Suppose you have:

OrderPage
OrderViewModel
OrderService
OrderRepository
OrderDatabase
Enter fullscreen mode Exit fullscreen mode

Do not migrate these as one block.

A better approach is:

OrderService       → Modern .NET
OrderRepository    → Modern .NET
OrderDatabase      → Modern .NET
OrderViewModel     → Modernized
OrderPage          → MAUI
Enter fullscreen mode Exit fullscreen mode

This allows the business layer to stabilize before the UI is converted.

The result should look approximately like:

             ┌───────────────┐
             │   MAUI UI     │
             └───────┬───────┘
                     │
             ┌───────▼───────┐
             │  ViewModel    │
             └───────┬───────┘
                     │
             ┌───────▼───────┐
             │ Application   │
             │   Services    │
             └───────┬───────┘
                     │
             ┌───────▼───────┐
             │ Infrastructure│
             └───────────────┘
Enter fullscreen mode Exit fullscreen mode

This is more valuable than simply changing namespaces.


10. Build One Complete Vertical Slice

Don't try to migrate 30 screens simultaneously.

Pick one representative business workflow.

For example:

Login
  ↓
Customer Search
  ↓
Customer Details
  ↓
Create Order
  ↓
Save Locally
  ↓
Submit
  ↓
Receive Notification
Enter fullscreen mode Exit fullscreen mode

Migrate the complete workflow.

Why?

Because this exposes problems that a single migrated screen will not reveal:

  • Navigation
  • Authentication
  • API calls
  • Dependency injection
  • Local storage
  • Serialization
  • Native APIs
  • Error handling
  • Logging
  • Push notifications
  • Performance
  • App lifecycle

If the vertical slice works, you have a template for subsequent migrations.


11. Keep Xamarin and MAUI Builds Running in Parallel

For large applications, one of the safest approaches is to maintain:

             Shared Core
                 │
        ┌────────┴────────┐
        │                 │
   Xamarin App        MAUI App
        │                 │
     Production        Migration
Enter fullscreen mode Exit fullscreen mode

The shared layer contains business logic and infrastructure that can be reused.

The UI/platform layer gradually moves to MAUI.

This prevents the migration branch from becoming isolated for six months.

It also means developers can continue delivering fixes to the existing application.


12. Make the CI Pipeline a Migration Gate

A migration is incomplete if it only works on a developer's machine.

The CI pipeline should build:

Android
iOS
Enter fullscreen mode Exit fullscreen mode

and eventually all supported targets.

At minimum, validate:

Restore
   ↓
Build
   ↓
Unit Tests
   ↓
UI/Integration Tests
   ↓
Signing
   ↓
Package
   ↓
Artifact Validation
Enter fullscreen mode Exit fullscreen mode

Also validate the things developers frequently forget:

  • App identifier
  • Version number
  • Signing certificates
  • Provisioning profiles
  • Android keystore
  • Entitlements
  • Push notification configuration
  • Environment configuration
  • API endpoints
  • Release symbols
  • Crash reporting
  • Store packaging

A successful local build is not equivalent to a successful enterprise migration.


13. Test Native Features on Real Devices Early

One of the biggest mistakes is leaving native functionality until the end.

The application may compile perfectly while these features fail at runtime:

Push notifications
Background services
Location
Bluetooth
Camera
File picker
Deep links
Biometric authentication
Audio
Native SDKs
Enter fullscreen mode Exit fullscreen mode

Create a native-risk prototype early.

For example:

MAUI Prototype
   ├── Push notification
   ├── Background task
   ├── Location
   ├── Camera
   └── Third-party SDK
Enter fullscreen mode Exit fullscreen mode

Run it on actual Android and iOS devices.

This gives you answers before hundreds of screens depend on those capabilities.


14. Pay Special Attention to Android and iOS Lifecycle Differences

Xamarin applications often contain lifecycle assumptions that are no longer safe to carry forward unchanged.

Examples include:

Application startup
Activity lifecycle
Background execution
Notification handling
Permission flow
Process termination
App resume
App sleep
Enter fullscreen mode Exit fullscreen mode

Don't test only:

"Does the screen open?"

Test:

Open app
↓
Background app
↓
Kill app
↓
Receive notification
↓
Tap notification
↓
Resume app
↓
Network unavailable
↓
Network restored
Enter fullscreen mode Exit fullscreen mode

Enterprise bugs frequently appear in these transitions rather than in normal UI navigation.


15. Offline Synchronization Deserves Its Own Migration Plan

Offline applications are particularly difficult to migrate.

Do not treat offline storage as simply:

"Move SQLite code to MAUI."

You need to validate:

Create offline record
        ↓
Modify offline record
        ↓
Queue operation
        ↓
Application terminated
        ↓
Network restored
        ↓
Synchronize
        ↓
Server conflict
        ↓
Retry
Enter fullscreen mode Exit fullscreen mode

Compare Xamarin and MAUI behavior using the same test dataset.

For synchronization systems, correctness is much more important than simply getting the database to compile.


16. Use Feature Flags During the Transition

Feature flags can reduce migration risk.

For example:

if (FeatureFlags.UseMauiOrderFlow)
{
    return await _mauiOrderFlow.ExecuteAsync();
}

return await _legacyOrderFlow.ExecuteAsync();
Enter fullscreen mode Exit fullscreen mode

This allows a migration to be released gradually.

Possible rollout:

Internal users
      ↓
QA
      ↓
10% users
      ↓
25% users
      ↓
50% users
      ↓
100% users
Enter fullscreen mode Exit fullscreen mode

The exact rollout depends on the organization's release process, but the principle is important:

Don't make the first production release the first real-world test.


17. Compare Production Metrics, Not Just Screens

A MAUI application can look identical to the Xamarin version and still behave differently.

Compare:

Startup time
Memory usage
Crash rate
ANR rate
API failures
Login success
Screen load time
Database operations
Push delivery
Background task success
Synchronization failures
Enter fullscreen mode Exit fullscreen mode

For example:

Metric                  Xamarin       MAUI

Startup                 2.8 sec       3.1 sec
Crash-free sessions     99.4%         99.5%
Sync failures           0.8%          0.7%
Login failures          0.4%          0.5%
Enter fullscreen mode Exit fullscreen mode

The exact numbers aren't important here.

The important thing is having measurable evidence instead of relying on:

"It seems to work."


18. Create a Migration Definition of Done

A screen should not be considered migrated merely because it compiles.

For each feature, define:

[ ] UI migrated
[ ] Navigation verified
[ ] API verified
[ ] Authentication verified
[ ] Offline behavior verified
[ ] Error handling verified
[ ] Analytics verified
[ ] Logging verified
[ ] Android tested
[ ] iOS tested
[ ] Regression tests passed
[ ] Production metrics available
Enter fullscreen mode Exit fullscreen mode

For native features, add:

[ ] App backgrounded
[ ] App terminated
[ ] App restarted
[ ] Permission denied
[ ] Permission granted
[ ] Network unavailable
[ ] Network restored
Enter fullscreen mode Exit fullscreen mode

This turns "migration complete" into something measurable.


19. What I Would Avoid

There are several approaches that look attractive but create problems later.

Big-bang migration

Stop Xamarin development
        ↓
Migrate everything
        ↓
Test everything
        ↓
Release
Enter fullscreen mode Exit fullscreen mode

This creates a very large feedback loop.

Automatic conversion followed by mass bug fixing

Automation is useful, but it cannot understand business behavior.

Rewriting the entire application

Migration is not automatically a reason to rewrite business logic.

Migrating every renderer immediately

Some renderers can be temporarily reused or may no longer be required.

Replacing every NuGet package

A replacement package is not necessarily behaviorally equivalent.

Testing only the happy path

Enterprise applications fail in lifecycle, offline, permission, synchronization, and recovery scenarios.


20. A More Practical Migration Model

For a large enterprise application, I would structure the work like this:

             EXISTING XAMARIN
                    │
                    ▼
          ┌──────────────────┐
          │ Dependency Audit │
          └────────┬─────────┘
                   │
                   ▼
          ┌──────────────────┐
          │ Modern Core      │
          │ Services         │
          └────────┬─────────┘
                   │
          ┌────────┴────────┐
          ▼                 ▼
    Xamarin UI          MAUI UI
          │                 │
          │          Vertical Slice
          │                 │
          └────────┬────────┘
                   ▼
             Feature Flags
                   │
                   ▼
              Canary Users
                   │
                   ▼
            Production MAUI
                   │
                   ▼
          Remove Xamarin Code
Enter fullscreen mode Exit fullscreen mode

This is essentially a controlled strangler approach rather than a rewrite.

The old application continues to work while the new implementation progressively takes over.


Final Thoughts

The hardest part of Xamarin-to-MAUI migration isn't learning .NET MAUI.

Most experienced Xamarin developers can learn the MAUI differences relatively quickly.

The difficult part is managing the technical dependencies, production risk, and migration sequencing of a large application.

The most effective practical principles are:

  1. Map the application before changing it.
  2. Migrate dependencies before hundreds of screens.
  3. Keep business logic independent of Xamarin.
  4. Use compatibility layers where they reduce risk.
  5. Don't migrate obsolete code.
  6. Build one complete vertical slice first.
  7. Keep Xamarin and MAUI development running in parallel where necessary.
  8. Test native functionality on real devices early.
  9. Use feature flags and controlled rollout.
  10. Measure production behavior instead of judging migration by compilation.

.NET MAUI gives organizations a modern platform, but the migration itself should be treated as an engineering transformation rather than a project-file conversion.

The goal isn't:

"We converted Xamarin to MAUI."

The real goal is:

"We moved the production application to MAUI without losing business functionality, reliability, or the ability to keep delivering."

That distinction is what makes a large migration manageable.


By Niladri

Top comments (0)