microg/GmsCore · error · IllegalStateException

AcknowledgePurchaseResult purchase item count != 1

Error message

AcknowledgePurchaseResult purchase item count != 1

What it means

parseFrom throws IllegalStateException when the response's purchaseItemData list does not contain exactly one item. The library's contract is that one acknowledge/purchase response corresponds to exactly one purchase item; any other count (0 or >1) is treated as an unexpected server shape and is not recoverable.

Source

Thrown at vending-app/src/main/java/org/microg/vending/billing/core/AcknowledgePurchaseResult.kt:32

            response: AcknowledgePurchaseResponse?
        ): AcknowledgePurchaseResult {
            if (response == null) {
                throw NullPointerException("response is null")
            }
            if (response.failedResponse != null) {
                return AcknowledgePurchaseResult(
                    null,
                    mapOf(
                        "RESPONSE_CODE" to (response.failedResponse?.statusCode ?: 0),
                        "DEBUG_MESSAGE" to (response.failedResponse?.msg ?: "")
                    )
                )
            }
            if (response.purchaseItem == null) {
                throw NullPointerException("AcknowledgePurchaseResponse PurchaseItem is null")
            }
            if (response.purchaseItem?.purchaseItemData?.size != 1)
                throw IllegalStateException("AcknowledgePurchaseResult purchase item count != 1")
            return AcknowledgePurchaseResult(parsePurchaseItem(response.purchaseItem!!).getOrNull(0))
        }
    }
}

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Verify the purchaseToken being acknowledged exists and is in 'purchased' state (query purchases first)
  2. Check that the app's package name and signing key match what the Play Store knows
  3. Log purchaseItemData.size to see whether you got 0 or multiple items and handle each case
  4. Catch IllegalStateException and map it to a billing error code instead of crashing

Example fix

// before
iapCore.acknowledgePurchase(AcknowledgePurchaseParams(purchaseToken = token))
// after
val purchases = billingClient.queryPurchases("inapp")
if (purchases.any { it.purchaseToken == token && it.purchaseState == PURCHASED }) {
    iapCore.acknowledgePurchase(AcknowledgePurchaseParams(purchaseToken = token))
}
Defensive patterns

Strategy: validation

Validate before calling

val count = ack.purchaseItem?.purchaseItemData?.size
if (count != 1) throw IllegalStateException("Expected 1 purchase item, got $count")

Type guard

fun AcknowledgePurchaseResponse.hasSingleItem(): Boolean =
    this.purchaseItem?.purchaseItemData?.size == 1

Try / catch

try {
    AcknowledgePurchaseResult.parseFrom(ack)
} catch (e: IllegalStateException) {
    // log purchaseItemData.size and surface billing error
}

Prevention

When it happens

Trigger: AcknowledgePurchaseResponse.purchaseItem.purchaseItemData.size != 1 — e.g. an empty purchaseItemData list (token not found) or multiple entries from an unexpected server response.

Common situations: Acknowledging an already-consumed or unknown purchase token; backend changes returning multiple purchase items; mismatched app package/signature causing the server to return no items.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/2fbd18d7b83e82ef. Report an issue: GitHub.