---
title: "HealthKit to Health Connect: the API equivalents, call by call"
canonical: "https://aifitnessapi.com/health-connect-api/healthkit-vs-health-connect-api-equivalents"
cluster: "Health Connect API"
primary_query: "HealthKit to Health Connect API equivalents"
last_reviewed: "2026-10-04"
description: "Map HealthKit calls to Health Connect: HKSampleQuery to readRecords, HKStatisticsQuery to aggregate, anchors to getChanges, and what has no equivalent."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"HealthKit to Health Connect: the API equivalents, call by call\", AIFitnessAPI, https://aifitnessapi.com/health-connect-api/healthkit-vs-health-connect-api-equivalents"
---

# HealthKit to Health Connect: the API equivalents, call by call

> Most HealthKit calls have a direct Health Connect counterpart: HKSampleQuery maps to readRecords, HKStatisticsQuery to aggregate, HKStatisticsCollectionQuery to aggregateGroupByDuration or aggregateGroupByPeriod, HKAnchoredObjectQuery to getChangesToken and getChanges, requestAuthorization to PermissionController's request contract, and save and delete to insertRecords and deleteRecords. HKObserverQuery with background delivery has no equivalent: Google says apps cannot be notified of new data, so Android apps poll, in the background through WorkManager with READ_HEALTH_DATA_IN_BACKGROUND. Paging, token expiry and permission visibility also differ.

- Canonical: https://aifitnessapi.com/health-connect-api/healthkit-vs-health-connect-api-equivalents
- Last reviewed: 2026-10-04
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "HealthKit to Health Connect: the API equivalents, call by call", AIFitnessAPI, https://aifitnessapi.com/health-connect-api/healthkit-vs-health-connect-api-equivalents

---

Porting a HealthKit integration to Android starts with a translation table: which Health Connect call does the job of each HealthKit class or method. Most rows have a close match. A few do not, and those are where ported apps break. This page maps the calls one by one, with each side described from its own platform's documentation, and links to the detailed page for both halves. For the product-level comparison, see [HealthKit vs Health Connect](/fitness-apis/apple-healthkit-vs-google-health-connect); for the project plan, see [adding Android to a HealthKit app](/migrate/add-android-to-healthkit-app). For the data types rather than the calls, [the identifier translator](/tools/identifier-translator) looks up the Health Connect record for a HealthKit identifier, and the reverse.

## The mapping

| Job | HealthKit | Health Connect (Jetpack) |
| --- | --- | --- |
| Read matching samples once | [HKSampleQuery](/healthkit-queries/hksamplequery) | [readRecords(ReadRecordsRequest)](/health-connect-api/readrecords-pagination) |
| One total or average over a window | [HKStatisticsQuery](/healthkit-queries/hkstatisticsquery) | [aggregate(AggregateRequest)](/health-connect-api/aggregate-request) |
| One value per hour, day or month | [HKStatisticsCollectionQuery](/healthkit-queries/hkstatisticscollectionquery) | [aggregateGroupByDuration / aggregateGroupByPeriod](/health-connect-api/aggregategroupbyduration-vs-period) |
| Only what changed, including deletions | [HKAnchoredObjectQuery](/healthkit-queries/hkanchoredobjectquery) | [getChangesToken + getChanges](/health-connect-api/getchanges-incremental-sync) |
| Be told when data changes, even in the background | [HKObserverQuery + enableBackgroundDelivery](/healthkit-queries/hkobserverquery-background-delivery) | No callback; a periodic WorkManager job with [READ_HEALTH_DATA_IN_BACKGROUND](/health-connect-api/read-health-data-in-background) |
| Ask the user for access | `requestAuthorization(toShare:read:completion:)` | [PermissionController.createRequestPermissionResultContract()](/health-connect-api/permissioncontroller-request-permissions) |
| Save | `HKHealthStore.save(_:withCompletion:)` | [insertRecords](/health-connect-api/insertrecords-clientrecordid-upsert) |
| Delete | `HKHealthStore.delete(_:withCompletion:)`, `deleteObjects(of:predicate:withCompletion:)` | [deleteRecords](/health-connect-api/deleterecords) |
| Filter by date and source | [HealthKit query predicates](/healthkit-queries/healthkit-query-predicates) | `TimeRangeFilter` and `dataOriginFilter` on each request |
| Workout GPS route | [HKWorkoutRouteQuery](/healthkit-queries/hkworkoutroutequery) | [ExerciseSessionRecord.exerciseRouteResult](/health-connect-api/exercise-route-consent) |
| async/await | [HealthKit async query descriptors](/healthkit-queries/healthkit-async-query-descriptors) | Every read and write on `HealthConnectClient` is a Kotlin `suspend` function |

## Reading samples

Apple describes `HKSampleQuery` as "A general query that returns a snapshot of all the matching samples currently saved in the HealthKit store." Google's `readRecords` "Retrieves a collection of `Record`s." Both are one-shot reads.

The difference is paging. HealthKit lets you pass `HKObjectQueryNoLimit`, "A value indicating that the query returns all the matching samples in the HealthKit store." Health Connect has no such value: `ReadRecordsRequest` has a `pageSize` that defaults to 1000, and Google says that if more records match "you need to iterate over all pages of the response to retrieve all records by using `pageToken`." A port that reads once and stops loses everything after the first page.

## Totals and buckets

Apple's `HKStatisticsQuery` "performs statistical calculations over a set of matching quantity samples"; Google's `aggregate` "Reads `AggregateMetric`s according to requested read criteria". Google's guide gives the reason to use it over adding up raw records: "The aggregation API also contains logic to handle duplicate records, and lessens the chances of rate limiting."

For charts, Apple's `HKStatisticsCollectionQuery` "performs multiple statistics queries over a series of fixed-length time intervals", aligned to an `anchorDate`. Health Connect splits this into two methods, one bucketing by `Duration` and one by `Period`, each returning a list sorted "by time in ascending order". Google notes that a row "is returned only if there are `Record` to aggregate within start and end time of the row", so empty buckets are missing rather than zero. Apple says its collection query can also be long-running and receive updates "when the HealthKit store's content changes"; Google's grouped aggregates return a list once and stop.

## Incremental sync

Apple: `HKAnchoredObjectQuery` "returns an anchor value that corresponds to the last sample or deleted object received by that query", and later queries use it "to restrict their results to only newer saved or deleted objects." Health Connect's equivalent is a changes token: `getChangesToken` returns one, and `getChanges` returns the changes since it plus `nextChangesToken`.

Two differences matter. Google: "Changes-tokens are only valid for 30 days after they're generated", and an expired one sets `changesTokenExpired`; the Apple pages fetched for this article describe no expiry for `HKQueryAnchor`. And Google says a `DeletionChange` "only contains the `id` of the deleted record, and not the record type, due to privacy", so you must have stored Health Connect IDs to act on deletions. Recovery from expiry is on [changes token expired](/fix/health-connect-changes-token-expired).

## Background delivery has no counterpart

HealthKit can wake your app. Apple's observer query is "A long-running query that monitors the HealthKit store and updates your app when the HealthKit store saves or deletes a matching sample", and with `enableBackgroundDelivery(for:frequency:withCompletion:)` "HealthKit wakes your app whenever a process saves or deletes samples of the specified type", at most once per period of the frequency you choose.

Health Connect has no such mechanism. Google's sync guide: "As your app can't get notified of new data, check for new data at two points": when the app comes to the foreground, and periodically while it stays there. To poll while the app is closed, Google documents a periodic WorkManager worker that requires the extra `READ_HEALTH_DATA_IN_BACKGROUND` permission and a feature check. Ports that relied on HealthKit waking them need a polling schedule and a changes token instead. The general design is in [background sync](/architecture/background-sync).

## Permissions behave differently

Apple's `requestAuthorization(toShare:read:completion:)` "Requests permission to save and read the specified data types", and Apple says "Each data type has two separate permissions, one to read it and one to share it." Health Connect works the same way per type, with `HealthPermission.getReadPermission(...)` and `getWritePermission(...)`.

What you can learn afterwards differs. Apple: "your app cannot determine whether or not a user has granted permission to read data. If you are not given permission, it simply appears as if there is no data of the requested type in the HealthKit store." Google's `getGrantedPermissions()` "Returns a set of all health permissions granted by the user to the calling app", read permissions included. A port can, and Google's guides say should, check grants before every operation instead of inferring them from empty results.

Health Connect also has permissions with no HealthKit equivalent in this table: [READ_HEALTH_DATA_HISTORY](/health-connect-api/read-health-data-history) for data older than 30 days before the first grant, the background read permission, and the per-session route consent.

## Saving and deleting

Both platforms write batches as all-or-nothing. Apple: "When saving multiple objects, if any object cannot be saved, none of them are saved." Google: "Insertion of multiple `records` is executed in a transaction - if one fails, none is inserted."

Re-saving differs. Apple: "Saving an object with the same unique identifier as an object already in the HealthKit store fails with an `errorInvalidArgument` error." Health Connect lets you upsert through `Metadata.clientRecordId`: on a repeat insert, "whichever `Record` with the higher `androidx.health.connect.client.records.metadata.Metadata.clientRecordVersion` takes precedence." Health Connect also has `updateRecords`, which "Updates one or more `Record` of given UIDs to newly specified values."

For deletion, Apple: "Your app can delete only those objects that it has previously saved to the HealthKit store." Google's time-range `deleteRecords` deletes records "automatically filtered to `Record` belonging to the calling application", and its ID-based overload runs "in single transaction - if one fails, none is deleted." Apple's batch delete is likewise all-or-nothing.

## Routes

Apple: `HKWorkoutRouteQuery` is "A query to access the location data stored in a workout route", returning locations in batches because a route "can include a large number of `CLLocation` objects." Health Connect has no route query: the route comes back on the session as an `ExerciseRouteResult`, which is `Data`, `ConsentRequired` or `NoData`. Another app's route can require a one-time user consent through `ExerciseRouteRequestContract`, and Google says your app "can't read exercise route data created by other apps when it runs in the background."

## Pitfalls

- **Assuming one read returns everything.** Loop on `pageToken`; there is no `HKObjectQueryNoLimit`.
- **Waiting for a callback that never comes.** No Health Connect API pushes changes to your app. Poll on foreground and on a schedule.
- **Treating empty results as "no permission".** On Android you can and should ask `getGrantedPermissions()`.
- **Expecting zero-filled buckets.** Grouped aggregates omit empty rows; fill gaps yourself, as covered in [missing data and gaps](/architecture/missing-data-and-gaps).
- **Ignoring the 30-day history window.** A HealthKit-style full backfill on Android needs `READ_HEALTH_DATA_HISTORY`, and the feature must be available.
- **Long-lived anchors.** A changes token unused for 30 days expires; plan the re-sync path from the start.

## FAQ

### What is the Health Connect equivalent of HKObserverQuery?

There is none. Apple's observer query monitors the HealthKit store and, with background delivery, HealthKit wakes the app when matching samples are saved or deleted. Google's sync guide says Health Connect apps cannot be notified of new data and should check at foreground and periodically; for checks while the app is closed, Google documents a periodic WorkManager worker that needs READ_HEALTH_DATA_IN_BACKGROUND.

[Permalink](https://aifitnessapi.com/health-connect-api/healthkit-vs-health-connect-api-equivalents#faq-1)

### Which Health Connect method replaces HKStatisticsCollectionQuery?

Two methods: aggregateGroupByDuration for fixed-length buckets such as hours, and aggregateGroupByPeriod for calendar buckets such as days or months. Both return a list of rows sorted by time in ascending order. Unlike HealthKit's collection query they do not keep running for updates, and Google says a row is returned only if there are records to aggregate in it, so empty buckets are missing.

[Permalink](https://aifitnessapi.com/health-connect-api/healthkit-vs-health-connect-api-equivalents#faq-2)

### Can a Health Connect app tell whether read permission was granted, unlike HealthKit?

Yes. Apple says a HealthKit app cannot determine whether the user granted read permission, and that denied reads simply look like no data. Health Connect's PermissionController.getGrantedPermissions returns every health permission granted to the calling app, read permissions included, and Google's guides say to check it before each operation.

[Permalink](https://aifitnessapi.com/health-connect-api/healthkit-vs-health-connect-api-equivalents#faq-3)

### Is there a Health Connect equivalent of HKObjectQueryNoLimit?

No. ReadRecordsRequest has a pageSize that defaults to 1000, and Google says that when more records match you must iterate over all pages of the response using the pageToken. A port from HealthKit that reads once with no limit and stops will silently miss everything after the first page.

[Permalink](https://aifitnessapi.com/health-connect-api/healthkit-vs-health-connect-api-equivalents#faq-4)
