Copyright © 2026 Unity Technologies
Description
Follows a previous fix, which addressed one instance of this leak class: QueryProductDetailsParamsProductList leaked one AndroidJavaObject per product while fetching. The same class remains on the purchase path, so a title that purchases repeatedly in a session accumulates JNI global references from a different direction and can reach the same reference-table overflow. A reporter would reasonably expect that fix to have covered it.
⚠️ Found by inspection while reviewing that change - not reproduced. No customer has reported these specific sites. Filed as a Bug because it is a defect with the same user-visible consequence, not because a repro exists.
Accuracy note, so this isn't overstated in release notes: these are not permanent leaks. AndroidJavaObject has a finalizer, so GC releases the refs eventually. They are deferred releases, which is what builds reference-table pressure - and the reason BillingClient.cs otherwise disposes of every wrapper immediately instead of relying on finalizatio
Initial setup
Present in 5.4.3. Any Unity version; Android/Google Play only. No special project setup - the affected code runs on every launchBillingFlow.
Reproduction steps
No behavioural repro. The mechanism is static, in Runtime/Stores/Android/GooglePlay/AAR/Models/BillingClient.cs:
1. MakeBillingFlowParams (line 188) - 5 wrappers dropped per launchBillingFlow. Every Call mints a fresh wrapper, and the reassignment idiom drops each intermediate undisposed:
var billingFlowParams = GetBillingFlowParamClass().CallStatic<AndroidJavaObject>("newBuilder"); billingFlowParams = SetObfuscatedAccountIdIfNeeded(billingFlowParams); // previous dropped billingFlowParams = SetObfuscatedProfileIdIfNeeded(billingFlowParams); // dropped billingFlowParams = billingFlowParams.Call<AndroidJavaObject>("setProductDetailsParamsList", ...); // dropped billingFlowParams = billingFlowParams.Call<AndroidJavaObject>("build"); // dropped return billingFlowParams; // caller doesn't dispose
2. The convention used everywhere else in the file is builder.Call("setX", v).Dispose();
3. LaunchBillingFlow (lines 55-75) - productDetailsParamsList (var, never disposed, two lines above the previous fix); MakeBillingFlowParams(...)'s return (inline argument, never disposed); BuildSubscriptionUpdateParams(...)'s return (never disposed); line 58 subscriptionOfferDetails?.Enumerate().ToList() materialises one wrapper per offer, uses only .FirstOrDefault(), disposes none.
4. GooglePurchaseService.cs:106 — the returned billingResult is never disposed.
Expected behaviour
Every AndroidJavaObject is disposed at the end of its use, per the convention already followed throughout BillingClient.cs, so JNI global reference count returns to baseline after each purchase.
Actual behavior
Reference count grows by roughly 8-10 per purchase (5 from MakeBillingFlowParams, plus the LaunchBillingFlow and GooglePurchaseService sites, plus one per subscription offer), released only when GC finalises. Under repeated purchasing this is the same accumulation that produced JNI ERROR (app bug): global reference table overflow in the fetch-path instance.
Issues you vote on will appear here