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
launchBillingFlowpurchase code. - Move your existing subscribers over so paying users keep their access.
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.
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.
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.
Add and Configure RevenueCat
Add the SDK to your app module's Gradle file (use the latest version):
// 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:
// 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()
)
}
}
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.
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:
// 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:
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.
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.
// 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() }
)
}
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.
appUserID), or call Purchases.sharedInstance.logIn(userId) before syncing,
so the synced purchase attaches to the right customer instead of an anonymous id.
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.
- In the Play Console, add testers under Setup > License testing, and publish your build to an internal or closed testing track.
- As a tester with an existing subscription, install the new RevenueCat build and confirm the one-time
syncPurchasesruns and theproentitlement becomes active. - As a new tester, complete a purchase through the new flow and confirm the entitlement unlocks and the transaction appears in RevenueCat.
- 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.
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) withgetOfferings+purchase. - Modeled access as a single
proentitlement instead of hand-rolled SKU logic. - Moved existing subscribers with a one-time
syncPurchasesplus 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
- Android In-App Purchases & Paywalls: the full RevenueCat Android integration.
- Boost your app revenue and Get products and prices: what to do now that billing is handled.
- RevenueCat: Migrating existing subscriptions and Google: Migrate to Billing Library 8.