---
title: "insertRecords and clientRecordId: how Health Connect upserts"
canonical: "https://aifitnessapi.com/health-connect-api/insertrecords-clientrecordid-upsert"
cluster: "Health Connect API"
primary_query: "clientRecordId"
last_reviewed: "2026-10-04"
description: "How Health Connect upserts with insertRecords: clientRecordId identifies your record, a higher clientRecordVersion wins, and when to use updateRecords."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"insertRecords and clientRecordId: how Health Connect upserts\", AIFitnessAPI, https://aifitnessapi.com/health-connect-api/insertrecords-clientrecordid-upsert"
---

# insertRecords and clientRecordId: how Health Connect upserts

> In Health Connect, insertRecords works as an upsert when your records carry a clientRecordId in their Metadata. Google's 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 insert with that identifier either replaces the stored record or is ignored depending on clientRecordVersion: the higher version wins, and Google's write guide says a version that is not higher leaves the stored value unchanged. Versions never increase on their own, so your app must supply a larger Long for every change. updateRecords is the alternative when you hold the Health Connect id instead.

- Canonical: https://aifitnessapi.com/health-connect-api/insertrecords-clientrecordid-upsert
- Last reviewed: 2026-10-04
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "insertRecords and clientRecordId: how Health Connect upserts", AIFitnessAPI, https://aifitnessapi.com/health-connect-api/insertrecords-clientrecordid-upsert

---

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](https://developer.android.com/reference/kotlin/androidx/health/connect/client/HealthConnectClient), the [`Metadata` reference](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/metadata/Metadata), the [write data guide](https://developer.android.com/health-and-fitness/health-connect/write-data), the [synchronize data guide](https://developer.android.com/health-and-fitness/health-connect/sync-data) and the [metadata guide](https://developer.android.com/health-and-fitness/health-connect/metadata), 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

```kotlin
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 `clientRecordId` per 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 [`DeletionChange` entries](/health-connect-api/getchanges-incremental-sync) and to [delete by record id](/health-connect-api/deleterecords).
- **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 `id` or `dataOrigin` yourself.** 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](/fix/health-connect-rate-limit).

## FAQ

### 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.

[Permalink](https://aifitnessapi.com/health-connect-api/insertrecords-clientrecordid-upsert#faq-1)

### 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.

[Permalink](https://aifitnessapi.com/health-connect-api/insertrecords-clientrecordid-upsert#faq-2)

### 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.

[Permalink](https://aifitnessapi.com/health-connect-api/insertrecords-clientrecordid-upsert#faq-3)
