Skip to content
AF
Troubleshooting

Health Connect SecurityException: The Permission Was Not Granted

Last verified October 3, 2026 · 5 min read

Sources checked (9) · October 3, 2026
Health Connect throws SecurityException when a call needs a permission your app does not currently hold. Google's exception table describes it as issues "when the requests require permissions that aren't granted". The usual causes are a permission that was declared but never granted, one the user has since revoked, or one missing from the manifest. The fix is to call getGrantedPermissions before every read or write, request anything missing with PermissionController.createRequestPermissionResultContract, and treat the exception as "not allowed to look", never as "no data".

Covered here:Health Connect

Your Android app calls readRecords, insertRecords, aggregate or getChanges, and instead of data you get java.lang.SecurityException. Health Connect is not telling you the data is missing. It is telling you the call needed a permission your app does not hold at that moment. Google documents exactly one meaning for it, and almost every fix comes down to checking the granted set before you call.

What Google documents#

Google's read-data and write-data guides carry the same exception table. The SecurityException row reads, verbatim:

There are issues encountered when the requests require permissions that aren't granted.

The recommended best practice in the same row: "make sure that you've declared use of Health Connect data types for your published app. Also, you must declare Health Connect permissions in the manifest file and in your activity." (Both guides fetched October 3, 2026: read data, write data.)

Android's Jetpack HealthConnectClient reference lists SecurityException under Throws for readRecords, readRecord, insertRecords, aggregate, deleteRecords, getChanges and getChangesToken, each time with the same gloss: "For requests with unpermitted access." The one place it names a specific permission is the medical-records API: "Caller must hold PERMISSION_WRITE_MEDICAL_DATA in order to call this API, otherwise a SecurityException will be thrown." Health Connect error codes sets SecurityException beside the other exception types the HealthConnectClient reference documents, with how many methods each appears on.

Where the HealthConnectException in your stack trace comes from#

On Android 14 and later Health Connect is part of the system, and the platform API reports failures as android.health.connect.HealthConnectException with an error code. Android's platform reference describes ERROR_SECURITY as "The caller doesn't have the correct permissions for this call. This error may be considered similar to SecurityException." The Jetpack library's own source (ExceptionConverter.kt, read October 3, 2026) converts that code into SecurityException(this) in its platform code path, which requires API 34. So on those devices the SecurityException you catch carries the platform exception as its cause. That is internal library code, not a documented contract, so read the cause for logging and never branch on its message text.

Most likely causes (ranked)#

The ranking is our judgement. Each cause is tied to what Google documents.

  1. Declared, never granted, or since revoked. The manifest entry makes a permission requestable. It does not grant it. Google's get-started guide says "users can grant or revoke permissions at any time", so a read that worked yesterday can throw today. Google's read guide adds that if a user deletes your app, "all permissions, including the history permission, are revoked", so a reinstall starts from zero.
  2. Not declared in the manifest. Google's get-started guide says the permissions in your request set must be "declared in your Android manifest first". Health Connect uses standard uses-permission tags, one per type and direction. A typo or a missing write entry fails here.
  3. A changes token that spans a revoked type. Google's sync guide recommends "getting separate tokens per data type instead of getting them in bulk to avoid having an Exception in case one of the permissions is revoked." One token over five types fails as a whole when the user revokes any one of them.
  4. A background read without the background permission. Android's HealthPermission reference says "An attempt to read data in background without this permission may result in an error" for PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND. It does not name the exception type, so confirm with your own logs whether this is what you are seeing.
  5. A published app without its Play Console declaration. Google's table row tells you to declare your Health Connect data types for your published app. If a type works in a local build and fails only for Play users, see the production section of Health Connect returns no data.

The fix: check the granted set, then call#

Google's get-started guide is explicit that "your app needs to check for permissions every time before using them and handle scenarios where permission is lost." Ask permissionController.getGrantedPermissions() first, and keep a catch for the gap between your check and the call.

val stepsRead = HealthPermission.getReadPermission(StepsRecord::class)

suspend fun readStepsOrNull(
    client: HealthConnectClient,
    start: Instant,
    end: Instant
): List<StepsRecord>? {
    val granted = client.permissionController.getGrantedPermissions()
    if (stepsRead !in granted) return null // not granted: ask, don't call
    return try {
        client.readRecords(
            ReadRecordsRequest(
                StepsRecord::class,
                timeRangeFilter = TimeRangeFilter.between(start, end)
            )
        ).records
    } catch (e: SecurityException) {
        null // revoked between the check and the call
    }
}

A null here means "we may not look", which is different from an empty list. Keep the two apart in anything that draws charts or streaks.

When the permission is missing, request it with the Health Connect contract, which Google's get-started guide says displays the Health Connect permissions screen. Android's PermissionController reference documents createRequestPermissionResultContract(), and the callback returns the set the user actually granted:

val requestPermissions = registerForActivityResult(
    PermissionController.createRequestPermissionResultContract()
) { granted ->
    if (stepsRead in granted) {
        // safe to read
    } else {
        // denied: degrade, don't re-prompt in a loop
    }
}

requestPermissions.launch(setOf(stepsRead))

And the manifest side, in the format Google's get-started guide shows:

<uses-permission android:name="android.permission.health.READ_STEPS"/>
<uses-permission android:name="android.permission.health.WRITE_STEPS"/>

Symptom to cause#

What you observeLikely causeWhat to do
Throws on first use after installNever grantedCheck, then request through the contract
Worked, now throws on every callUser revoked itRe-check every session; offer reconnect
getChanges throws after a settings changeOne token covers a revoked typeOne changes token per record type
Throws only in a background workerBackground permission missingRequest PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND
No exception, just an empty listNot this errorRead Health Connect returns no data

Where to go next#

The full setup, including the manifest and permission flow this page assumes, is in the Google Health Connect integration guide. Every android.permission.health.* string by record type is in the Health Connect records reference. For an availability failure rather than a permission one, see SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED.

Frequently asked questions

Is a Health Connect SecurityException the same as an empty result?
No. An empty list means the call was allowed and found nothing. A SecurityException means the call was not allowed at all. Google's exception table ties it to requests that need permissions that aren't granted. Keep the two apart in your code: an empty list can be shown as zero, but a SecurityException should lead to a permission check and a reconnect prompt, never a zero.
Why does getChanges throw SecurityException after the user changes permissions?
Most likely because one changes token covers several record types and the user revoked one of them. Google's sync guide recommends getting separate tokens per data type instead of getting them in bulk, to avoid an Exception if one of the permissions is revoked. With one token per type, losing one permission stops only that type's sync.
Is declaring a Health Connect permission in the manifest enough?
No. The manifest entry is what makes a permission requestable, and Google's get-started guide says every permission in your request set must be declared there first. Granting happens at runtime through the Health Connect permissions screen, which you open with PermissionController.createRequestPermissionResultContract. The user can also revoke a grant at any time, so check getGrantedPermissions before each use.
What is the HealthConnectException in my SecurityException stack trace?
On Android 14 and later the platform reports the failure as android.health.connect.HealthConnectException with the code ERROR_SECURITY, which Android describes as the caller lacking the correct permissions. The Jetpack library's source wraps that in a SecurityException, so the platform exception shows up as the cause. That wrapping is internal library code, so log the cause but do not branch on its message.

All 124 questions in Troubleshooting →

Keep reading

Elsewhere on the site

Pages that share this one’s concepts and sources, from other sections.

Next steps

Was this page useful?

Independent comparison, last reviewed October 3, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.

← All troubleshooting · by AIFitnessAPI