Migrating a Mobile App from Deprecated APIs Without Breaking Production
Deprecated APIs are a fact of life in mobile development. Apple and Google continuously evolve their platforms, and the APIs your app was built on two or three years ago may already be marked for removal. The challenge is not whether to migrate. It is how to migrate without introducing regressions into an app that real users depend on every day.
A botched API migration can be worse than the deprecation itself. We have seen apps crash on launch after a hasty migration, lose data during a storage API transition, and silently break features that QA did not cover. Here is how to approach deprecated API migration methodically, with real examples from both Android and iOS.
Finding What Is Deprecated in Your Codebase
Before you can plan a migration, you need a complete inventory of deprecated API usage. Both platforms provide tooling for this, but you need to know where to look.
On Android, the Kotlin compiler and Android Lint surface deprecation warnings during builds. Run ./gradlew lint and filter the output for deprecation-related issues. Android Studio also highlights deprecated methods with a strikethrough in the editor, but relying on visual inspection misses usages in files you do not open. The lint report gives you a comprehensive list across the entire project.
On iOS, Xcode generates deprecation warnings during compilation. Build the project with -Wdeprecated-declarations (enabled by default) and review the full warning list in the Issue Navigator. For Swift, the compiler is thorough about flagging @available(*, deprecated) annotations. For Objective-C code, pay attention to API_DEPRECATED macros in system headers.
Third-party SDK deprecations are harder to catch because they do not always produce compiler warnings. Check the changelogs and migration guides for every SDK in your dependency list. Major SDKs like Firebase, Facebook, and Stripe publish migration guides when they deprecate significant APIs.
Prioritizing: Deadline vs. Impact
Not all deprecations are equal. Some have hard deadlines (App Store rejection after a certain date), while others are soft warnings that may persist for years. Prioritize based on two axes: urgency and blast radius.
- Hard deadline, high impact: Apple's requirement to adopt the UIScene lifecycle by a specific Xcode version, or Google's annual target SDK level requirements for Play Store submissions. These block your ability to ship updates entirely. Handle these first.
- No deadline, high impact: A deprecated networking API used in every screen of the app. No immediate deadline, but the migration touches a lot of code. Plan this as a focused project.
- Hard deadline, low impact: A deprecated API used in one minor feature. Quick to fix, and the deadline forces your hand. Slot it into a sprint.
- No deadline, low impact: A deprecated convenience method with a direct replacement. Fix it when you are already working in that file.
The Parallel Implementation Pattern
The safest way to migrate a deprecated API is to run the old and new implementations side by side before cutting over. This is especially important for APIs that affect data persistence, networking, or authentication, where a subtle behavioral difference can cause data loss or lock users out.
The pattern works like this: create a new implementation using the replacement API. Wire it up behind a feature flag or a runtime check. In your initial rollout, run both implementations and compare their outputs. Log any discrepancies. Once you have confidence that the new implementation behaves identically in production conditions, remove the old code path.
This approach adds temporary complexity, but it dramatically reduces risk. You catch behavioral differences before users do, and you have a clear rollback path at every stage.
Feature Flags for Gradual Migration
Feature flags are essential for safe API migrations. They let you enable the new code path for a percentage of users, for internal testers only, or based on OS version. If something goes wrong, you flip the flag remotely without shipping a new binary.
On Android, Firebase Remote Config is the most common tool for this. On iOS, you can use the same Firebase setup or Apple's CloudKit-based configuration. For simpler cases, a server-side JSON endpoint that your app fetches at launch works fine.
Structure your flag so that the default state is "use old implementation." This means that if your flag service is unreachable, users get the proven code path. Only enable the new path explicitly.
Real Example: AsyncTask to Kotlin Coroutines
Android's AsyncTask was deprecated in API level 30. It had been the standard way to run background work for over a decade, and many inherited codebases still use it extensively. The recommended replacement is Kotlin Coroutines with viewModelScope or lifecycleScope.
The migration is not a simple find-and-replace. AsyncTask ties background work to a specific lifecycle that does not map cleanly to coroutine scopes. Common pitfalls include:
AsyncTask.onPostExecute()runs on the main thread automatically. With coroutines, you need to explicitly switch toDispatchers.Mainfor UI updates, or useviewModelScope.launchwhich defaults to the main dispatcher.AsyncTaskserializes execution on a single background thread by default (since Android 3.0). Coroutines withDispatchers.IOuse a thread pool, which can surface concurrency bugs that the serializedAsyncTaskexecutor was masking.- Error handling changes fundamentally.
AsyncTaskswallows exceptions indoInBackgroundunless you explicitly catch them. Coroutines propagate exceptions through the coroutine scope, which can crash the app if you do not set up aCoroutineExceptionHandler.
Migrate one AsyncTask at a time. Start with the simplest case, verify it in production, then tackle the more complex ones.
Real Example: UIWebView to WKWebView
Apple deprecated UIWebView in iOS 12 and eventually began rejecting App Store submissions that contained UIWebView references. This migration affected thousands of apps, including many that used UIWebView through third-party SDKs rather than directly.
The migration to WKWebView is not a drop-in replacement. Key behavioral differences include:
WKWebViewruns in a separate process, which means cookies and local storage are not shared with the native app'sHTTPCookieStorage. Apps that relied on cookie sharing between native network calls and web views needed a customWKHTTPCookieStoresynchronization layer.- JavaScript execution is asynchronous in
WKWebView. Code that calledstringByEvaluatingJavaScript(from:)and used the return value synchronously had to be refactored to use the completion-handler-basedevaluateJavaScript(_:completionHandler:). - Local file loading behaves differently.
WKWebViewrequires files to be loaded withloadFileURL(_:allowingReadAccessTo:)and has stricter sandbox restrictions on which directories can be accessed.
The biggest challenge was third-party SDKs. Many ad networks and analytics SDKs still referenced UIWebView internally. Teams had to wait for SDK updates or find alternative SDKs before they could fully remove UIWebView from their binary.
Testing Strategies for API Migrations
Automated tests are your safety net during migration. Before changing any code, write tests that verify the current behavior of the deprecated API. These tests become your acceptance criteria for the replacement implementation. If the new code passes the same tests, you have high confidence that the migration is correct.
For UI-related migrations, snapshot tests (using tools like Paparazzi on Android or the native Xcode snapshot testing) catch visual regressions that unit tests miss. For data-layer migrations, write tests that verify round-trip data integrity: write data with the old API, read it with the new one, and confirm nothing is lost or corrupted.
Rollback Plans
Every migration should have a documented rollback plan. The simplest version is: keep the old code behind a feature flag and switch back if the new implementation causes problems. For more complex migrations that involve data format changes or server-side coordination, document the exact steps to revert and verify that the rollback path actually works before you begin the migration.
The goal is never to need the rollback plan. But having one means you can move forward with confidence, because the cost of being wrong is low.
Deprecated API migrations require careful planning and deep platform knowledge. DEVSFLOW Maintenance handles these migrations for production apps so your team can focus on building new features. Learn about our maintenance services and how we keep apps current.