What's Changing and Why Now

Google requires every Android app to keep its billing code current. By August 31, 2026, all new apps and all updates to existing apps must use Google Play Billing Library v8 or later (v9, released May 2026, also qualifies). You can request a one-time extension to November 1, 2026 in the Play Console. This is a publishing gate: apps already installed keep working, but you cannot ship any update until you are on v8+.

The catch is that this happens every year. v8 is the floor in 2026, v9 in 2027, and so on. Each bump removes APIs and forces code changes. Google's own Play Billing codelab is still stuck on v5 and carries a "deprecated" banner, which tells you how fast this moves.

This codelab shows the durable fix: migrate to RevenueCat, which manages the Billing Library for you so you never chase a version again. You will:

  • See exactly what v8 and v9 removed (so the page helps even if you patch BillingClient by hand).
  • Add RevenueCat and replace your launchBillingFlow purchase code.
  • Move your existing subscribers over so paying users keep their access.
Do this first: connect RevenueCat to Google Play. This codelab assumes you already have a RevenueCat project connected to the Google Play Store, with your service account credentials configured and your products imported. If you have not set that up yet, complete the RevenueCat Google Play integration codelab first, then come back here.
Who this is for: an Android app that integrates the raw Google Play Billing Library (BillingClient) directly today. Kotlin examples throughout.

Two Migration Paths

You have two ways to clear the deadline. Both are valid; this codelab recommends the second.

Path What you do Next year
Patch BillingClient to v8 yourself Rewrite the removed APIs (Step 3) in your own billing code Do it again for v9, v10, ...
Migrate to RevenueCat Replace your billing code once; RevenueCat owns the Billing Library Update the RevenueCat SDK; no BillingClient rewrite

If you only ever sold a single product and never plan to add paywalls, experiments, or cross-platform support, patching by hand is fine. For most apps, the recurring cost of the first path is the reason to take the second.

You do not have to do it all at once. RevenueCat supports a phased migration (observer mode) where it runs alongside your existing billing code first. We cover that in Step 9.

What v8 and v9 Broke

For reference, here is what the raw Billing Library removed in v8 (released June 30, 2025). If you patch by hand, this is your worklist. If you migrate to RevenueCat, you delete this code instead.

Removed in v8 Replacement
querySkuDetailsAsync(), SkuDetails queryProductDetailsAsync(), ProductDetails, QueryProductDetailsParams
queryPurchaseHistoryAsync(), PurchaseHistoryRecord queryPurchasesAsync() (active subscriptions and non-consumed one-time purchases only; voided history needs the server Voided Purchases API)
enablePendingPurchases() (no arguments) enablePendingPurchases(PendingPurchasesParams)
ProrationMode ReplacementMode via setSubscriptionReplacementMode()
setSkuDetails() setProductDetailsParamsList()

v9 (May 19, 2026) is smaller but still touches code: it changed the blocked-Play-Store error from ERROR to BILLING_UNAVAILABLE, made an external-payments field nullable, and raised the target SDK to 35. (The onProductDetailsResponse listener signature change and the new billing sub-response codes landed back in v8.) The minimum SDK rose to API 23 (Android 6.0) as of v8.1.

The pattern never ends. Note how much of this is plumbing that has nothing to do with your app. That is the work RevenueCat absorbs.

How RevenueCat Ends the Treadmill

The RevenueCat Android SDK bundles and manages the Google Play Billing Library for you. The current SDK ships with Billing Library 8.3.0 as a transitive dependency, so you do not add com.android.billingclient:billing yourself, and you do not call BillingClient at all.

What that means for the yearly deadline:

  • Today: adopt the RevenueCat SDK once (the rest of this codelab).
  • Next year, when v9 or v10 becomes mandatory: you bump the RevenueCat SDK version. No BillingClient rewrite, no removed-API hunt.

You also get the things you would otherwise build yourself: a single customerInfo.entitlements source of truth, server-side receipt validation, webhooks, and access to Paywalls, Experiments, and the Customer Center later.

One owner of the purchase. Because RevenueCat owns the Billing Library, it also acknowledges purchases with Google for you. You must not also acknowledge the same purchase in your own code (Google auto-refunds purchases not acknowledged within three days). Step 9 covers how to avoid running two integrations at once.

Add and Configure RevenueCat

Add the SDK to your app module's Gradle file (use the latest version):

kotlin
// app/build.gradle.kts
dependencies {
    implementation("com.revenuecat.purchases:purchases:10.10.0")
    // Optional: paywalls + Customer Center (requires minSdk 24)
    // implementation("com.revenuecat.purchases:purchases-ui:10.10.0")
}
// Remove your old: implementation("com.android.billingclient:billing:...")

The SDK requires minSdk 23 (Android 6.0). Configure it once in your Application class:

kotlin
// MainApplication.kt
class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        Purchases.logLevel = LogLevel.DEBUG
        Purchases.configure(
            PurchasesConfiguration.Builder(this, "goog_YOUR_PUBLIC_SDK_KEY")
                // .purchasesAreCompletedBy(PurchasesAreCompletedBy.REVENUECAT) // default: full migration
                .build()
        )
    }
}
Use the public key. The Google Play public SDK key starts with goog_ and is found in the RevenueCat dashboard under Project Settings > API keys. Never embed a secret key in the app. The default PurchasesAreCompletedBy.REVENUECAT means RevenueCat completes (acknowledges) purchases; switch to MY_APP only for the phased path in Step 9.

Map Your Billing Concepts

RevenueCat replaces hardcoded SKUs and your own "is premium" logic with a small, durable model. Configure these in the dashboard:

Raw Billing Library RevenueCat
A SKU / ProductDetails (e.g. premium_monthly) Product (your store product, imported into RevenueCat)
(you hardcoded SKU lists) Package (a role like monthly/annual) inside an Offering (what you present)
your own isPremium boolean Entitlement (e.g. pro), checked via customerInfo

In the dashboard: import your existing Google Play products, create an entitlement (for example pro), and Attach each product to it.

This is half of your migration. Attaching a product to an entitlement grants that entitlement to any customer who has previously purchased that product. So once RevenueCat knows about an existing subscriber's purchase (Step 8), their pro access is restored automatically.

Replace the Purchase Flow

Delete your queryProductDetailsAsync / launchBillingFlow / acknowledgement code and replace it with two calls. First, fetch what to sell:

kotlin
// Coroutines: fetch the current offering and its packages
val offerings = Purchases.sharedInstance.awaitOfferings()
val packages = offerings.current?.availablePackages.orEmpty()
// Show `packages` in your UI (each pkg.product carries the localized price)

Then make a purchase from a package the user selected:

kotlin
try {
    val result = Purchases.sharedInstance.awaitPurchase(
        PurchaseParams.Builder(activity, selectedPackage).build()
    )
    val customerInfo = result.customerInfo
    if (customerInfo.entitlements["pro"]?.isActive == true) {
        // Unlock pro. RevenueCat already acknowledged the purchase with Google.
    }
} catch (e: PurchasesTransactionException) {
    if (!e.userCancelled) {
        // Show a real error; userCancelled just means the user backed out.
    }
}

Callback-style equivalents (getOfferingsWith, purchaseWith) exist if you are not using coroutines.

No more acknowledgement code. With the default configuration, RevenueCat finishes the transaction with Google for you. Delete your old acknowledgePurchase / consumeAsync calls so you do not double-acknowledge.

New to this side of the SDK? The Android codelab and Get products and prices guide go deeper.

Migrate Existing Subscribers

This is the part that matters most: your current paying users must not lose access. There are two pieces, and you want both.

1. New purchases: track them automatically

With your Google Play account already connected (see the Google Play integration codelab), set up Google Play server notifications and turn on Track new purchases from server-to-server notifications. RevenueCat then records all new purchases, even from app versions that do not yet include the SDK.

2. Existing purchases: sync them once

For users who already subscribed through your old BillingClient code, call syncPurchases() once on the first launch of the RevenueCat-enabled build. It sends their existing Google Play purchases to RevenueCat with no OS sign-in prompt. Combined with the product-to-entitlement attach from Step 6, their pro access is restored automatically.

kotlin
// Run ONCE per subscriber, the first time they open the RevenueCat build.
if (!prefs.getBoolean("rc_migrated", false)) {
    Purchases.sharedInstance.syncPurchasesWith(
        onError = { /* retry later */ },
        onSuccess = { _ -> prefs.edit().putBoolean("rc_migrated", true).apply() }
    )
}
Sync once, not on every launch. Calling syncPurchases for every user on every launch adds latency and can unintentionally alias customers together. Gate it behind a one-time flag (or call it only when your old system says "subscribed" but RevenueCat does not). Use restorePurchases() (which can trigger an OS sign-in) only behind a user-tapped "Restore" button.
Use a stable App User ID. If your app has its own accounts, configure RevenueCat with that user id (appUserID), or call Purchases.sharedInstance.logIn(userId) before syncing, so the synced purchase attaches to the right customer instead of an anonymous id.
Historical data has limits. On the current SDK, syncPurchases only syncs active subscriptions and non-consumed one-time purchases. To backfill expired and past purchases (for accurate charts and lifetime history), run a Google Historical Import in the dashboard. It ingests history back to July 2023, with a known gap for purchase tokens expired more than 90 days ago.

Test with License Testers

Before you ship, verify the migration on a real device without being charged.

  1. In the Play Console, add testers under Setup > License testing, and publish your build to an internal or closed testing track.
  2. As a tester with an existing subscription, install the new RevenueCat build and confirm the one-time syncPurchases runs and the pro entitlement becomes active.
  3. As a new tester, complete a purchase through the new flow and confirm the entitlement unlocks and the transaction appears in RevenueCat.
  4. Check the customer in the RevenueCat dashboard to confirm the purchase and entitlement are recorded.

Prefer a phased rollout? Use observer mode

If you are not ready to remove your billing code in one release, configure RevenueCat with PurchasesAreCompletedBy.MY_APP. RevenueCat then records purchases and entitlements while your existing code still completes (acknowledges) them. Switch to the default PurchasesAreCompletedBy.REVENUECAT once you remove your old purchase code.

Never let both sides acknowledge. Run either your code (observer mode) or RevenueCat (full migration) as the one that completes a purchase, never both. Double acknowledgement and double integrations cause subtle, hard-to-debug bugs.

Recap and the Payoff

You migrated an Android app off the raw Billing Library and onto RevenueCat:

  • Replaced BillingClient (and all the v8 removed-API churn) with getOfferings + purchase.
  • Modeled access as a single pro entitlement instead of hand-rolled SKU logic.
  • Moved existing subscribers with a one-time syncPurchases plus the product-to-entitlement attach, and backfilled history with a Historical Import.

The payoff: you cleared the August 31, 2026 deadline, and when v9, v10, and beyond become mandatory you simply bump the RevenueCat SDK. No more yearly Billing Library migrations.

Keep going