insertRecords and clientRecordId: how Health Connect upserts
Last verified October 4, 2026 · 6 min read
Covered here:Health Connect
If your app has its own datastore, you will write the same logical record to Health Connect more than once: after an edit, after a retry, after a reinstall. clientRecordId is what stops those writes from becoming duplicates. Sources: the HealthConnectClient reference, the Metadata reference, the write data guide, the synchronize data guide and the metadata guide, all fetched October 4, 2026.
insertRecords#
The declaration is suspend fun insertRecords(records: List<Record>): InsertRecordsResponse. Google: it "Inserts one or more Record and returns newly assigned Metadata.id generated. Insertion of multiple records is executed in a transaction - if one fails, none is inserted." The return value is "List of unique identifiers in the order of inserted records", read from InsertRecordsResponse.recordIdsList. It lists RemoteException, SecurityException and IOException.
The upsert behaviour is described in the same reference entry: "Metadata.clientRecordId can be used to deduplicate data with a client provided unique identifier. When a subsequent insertRecords is called with the same Metadata.clientRecordId, whichever Record with the higher Metadata.clientRecordVersion takes precedence."
The Metadata fields that matter#
| Property | Who sets it | Google's description |
|---|---|---|
id | Health Connect | "Unique identifier of this data, assigned by Health Connect at insertion time. When Record is created before insertion, this takes a sentinel value, any assigned value will be ignored." |
lastModifiedTime | Health Connect | "Automatically populated to when data was last modified (or originally created)." |
clientRecordId | Your app | "There is guaranteed a single entry for any type of data with same client provided identifier for a given client. Any new insertions with the same client provided identifier will either replace or be ignored depending on associated clientRecordVersion." |
clientRecordVersion | Your app | "This determines conflict resolution outcome when there are multiple insertions of the same clientRecordId. Data with the highest clientRecordVersion takes precedence. clientRecordVersion starts with 0." |
Since Jetpack 1.1.0-alpha12, the metadata guide says "Metadata can no longer be directly instantiated, so use one of the factory methods". Each recording method has a factory that takes a client id: activelyRecorded(device, clientRecordId, clientRecordVersion = 0), autoRecorded(device, clientRecordId, clientRecordVersion = 0), manualEntry(clientRecordId, clientRecordVersion = 0, device = null) and unknownRecordingMethod(clientRecordId, clientRecordVersion = 0, device = null). The reference labels each "Added in 1.1.0". Which recording method to choose is a separate decision; the write guide notes that "As of version 1.1.0-rc01, RecordingMethod and DeviceType are mandatory requirements when writing data."
An upsert from your datastore#
suspend fun pushWeights(client: HealthConnectClient, rows: List<LocalWeight>): List<String> {
val records = rows.map { row ->
WeightRecord(
weight = Mass.kilograms(row.kg),
time = row.measuredAt,
zoneOffset = ZoneId.systemDefault().rules.getOffset(row.measuredAt),
metadata = Metadata.manualEntry(
clientRecordId = row.uuid, // your stable key
clientRecordVersion = row.version // raise it on every edit
)
)
}
val ids = mutableListOf<String>()
for (chunk in records.chunked(1000)) { // Google: at most 1000 records per write request
ids += client.insertRecords(chunk).recordIdsList
}
return ids // keep beside row.uuid; DeletionChange only gives you this id
}
LocalWeight is your type. The write guide asks you to "Chunk requests to at most 1000 records per write request", and each call is its own transaction, so if a later chunk fails, earlier chunks stay written. The sync guide explains why the returned ids matter: "If your app also reads data from Health Connect, store the Health Connect id for records after you upsert them. You need this id to process deletions when you pull data changes." Google does not document what recordIdsList contains for a record whose insert was ignored because its version was not higher.
Version rules, and where Google's pages disagree#
The write guide is the most explicit: "If the version from the inserted data is higher than the version from the existing data, the upsert happens. Otherwise, the process ignores the change and the value remains the same." It continues: "Upserts don't automatically increment version whenever there are changes, preventing any unexpected instances of overwriting data. With that, you have to manually supply it with a higher value."
The sync guide describes the same operation differently: existing data "gets overwritten as long as the clientRecordId values exist in the Health Connect datastore, and the clientRecordVersion is higher than the existing value. Otherwise, the upserted data is written as new data." Read literally, that would make a repeated insert with an unchanged version create a second record, which contradicts the reference's "guaranteed a single entry". We read the reference and the write guide as the rule, and the sync guide's "Otherwise" as covering a clientRecordId Health Connect has not stored yet. That reading is ours.
Two consequences. A repeated insert with the same version is ignored under the write guide's rule, so retries are safe but edits need a new version. And if your data has no version of its own, the sync guide suggests "you can use the Long value of the current timestamp as an alternative."
updateRecords#
suspend fun updateRecords(records: List<Record>): Unit "Updates one or more Record of given UIDs to newly specified values. Update of multiple records is executed in a transaction - if one fails, none is inserted." The reference adds that an update "with invalid identifiers will result in IPC failure." Google's update example reads records by time range and builds new ones with metadata = record.metadata, carrying the existing id across. To build metadata for a known id yourself, the ...WithId factories ("Creates Metadata to update a record with an existing UUID") exist, with the instruction "Use this only when there's no client ID or version associated with the record."
If you use client ids, Google's write guide points away from updateRecords: "If you are using the optional Client Record ID and Client Record Version values, we recommend using insertRecords instead of updateRecords."
Pitfalls#
- Editing without raising the version. The insert succeeds and the stored record stays as it was. Nothing throws.
- A new
clientRecordIdper write. The id has to be stable. Google's guide suggests your datastore's existing unique key: "You can use your existing unique key for this." - Not keeping the returned ids. You will need them to apply
DeletionChangeentries and to delete by record id. - Writing back what you read. The write guide: "if your app has read data from Health Connect, that data shouldn't be written back into Health Connect."
- Setting
idordataOriginyourself. Both are assigned by Health Connect, and the reference says values you assign before insertion are ignored. - One bad record fails the call. Because the insert is transactional, a single out-of-range value rejects the whole chunk. Retrying from the failed chunk rather than from zero is covered in Health Connect rate limit exceeded.
Frequently asked questions
- What happens when I insert a Health Connect record with a clientRecordId that already exists?
- It becomes an update or is ignored, never a duplicate. Google's Metadata reference says there is guaranteed a single entry for any type of data with the same client-provided identifier for a given app, and that a new insertion with that identifier either replaces the stored record or is ignored depending on clientRecordVersion. The write guide says the replacement happens only if the new version is higher than the stored one.
- Does Health Connect increment clientRecordVersion automatically?
- No. Google's write guide says upserts don't automatically increment the version whenever there are changes, so that data is not overwritten unexpectedly, and that you have to manually supply a higher value. The reference says clientRecordVersion starts at 0. If your data is not versioned, Google's sync guide suggests using the current timestamp as a Long.
- When should I use updateRecords instead of insertRecords in Health Connect?
- When you are changing records you identify by their Health Connect id rather than your own client id. Google's write guide shows updateRecords applied to records read by time range, keeping each record's metadata, and recommends insertRecords instead whenever you use the optional client record id and version. updateRecords is transactional, and the reference says invalid identifiers cause an IPC failure.
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 4, 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 health connect api · by AIFitnessAPI