Skip to content
AF
Health Connect reference

ExerciseSessionRecord: Health Connect exercise data

Read from Google’s documentation on 2026-10-03 · Google’s reference page last updated 2026-08-26

ExerciseSessionRecord is Health Connect's exercise record — an Interval record in Google's Activity category. Its read permissions are android.permission.health.READ_EXERCISE and android.permission.health.READ_EXERCISE_ROUTE; its write permissions are android.permission.health.WRITE_EXERCISE and android.permission.health.WRITE_EXERCISE_ROUTE. It supports 1 aggregate metric (EXERCISE_DURATION_TOTAL). Mandatory fields per Google's table: exerciseType, endTime, laps, metadata, segments and startTime.

What Google says ExerciseSessionRecord is

Captures any exercise a user does. This can be common fitness exercise like running or different sports.

Each record needs a start time and end time. Records don't need to be back-to-back or directly after each other, there can be gaps in between.

Example code demonstrate how to read exercise session:

Google’s example is reproduced under Read ExerciseSessionRecord in Kotlin.

Quoted from Google’s ExerciseSessionRecord Jetpack reference, last updated 2026-08-26, read 2026-10-03.

Class
androidx.health.connect.client.records.ExerciseSessionRecord
Google's category
Activity
Record type
Interval
Unit class
none named in Google's table
Mandatory fields
exerciseTypeendTimelapsmetadatasegmentsstartTime
Added in (Jetpack)
1.1.0

Permissions

The exact strings Google’s data-types table declares for ExerciseSessionRecord. Google’s page: “Before requesting any permissions, your app must declare them in the manifest.”

AccessPermission stringFramework reference
Readandroid.permission.health.READ_EXERCISEAPI 34 · U Ext 7
Readandroid.permission.health.READ_EXERCISE_ROUTEnot defined in the reference — see below
Writeandroid.permission.health.WRITE_EXERCISEAPI 34 · U Ext 7
Writeandroid.permission.health.WRITE_EXERCISE_ROUTEAPI 34 · U Ext 7

Google’s pages disagree on a string here.

The data-types table prints android.permission.health.READ_EXERCISE_ROUTE, but neither the framework HealthPermissions reference nor a Jetpack HealthPermission constant defines that string. The references define android.permission.health.READ_EXERCISE_ROUTES (framework READ_EXERCISE_ROUTES; Jetpack PERMISSION_READ_EXERCISE_ROUTES) instead. Copy the string the permission references define.

AndroidManifest.xml
<uses-permission android:name="android.permission.health.READ_EXERCISE" />
<uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTE" />
<uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
<uses-permission android:name="android.permission.health.WRITE_EXERCISE_ROUTE" />
Kotlin — the same strings from the Jetpack helper
import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.records.ExerciseSessionRecord

val permissions = setOf(
    HealthPermission.getReadPermission(ExerciseSessionRecord::class),
    HealthPermission.getWritePermission(ExerciseSessionRecord::class),
)

The 13 fields of ExerciseSessionRecord

Types and descriptions as Google’s reference prints them. The range column quotes Google’s own range annotation or sentence; a dash means Google states none, which is not the same as no validation.

FieldTypeAllowed rangeGoogle’s description
endTimemandatoryInstant—End time of the record.
endZoneOffsetZoneOffset?—User experienced zone offset at endTime, or null if unknown. Providing these will help history aggregations results stay consistent should user travel. Queries with user experienced time filters will assume system current zone offset if the information is absent.
exerciseRouteResultExerciseRouteResult—ExerciseRouteResult of the session. Location data points of ExerciseRoute should be within the parent session, and should be before the end time of the session.
exerciseTypemandatoryInt—Type of exercise (e.g. walking, swimming). Required field.
lapsmandatory= emptyList()List<ExerciseLap>—ExerciseLaps of the session. Optional field. Time in laps should be within the parent session, and should not overlap with each other.
metadatamandatoryMetadata—Set of common metadata associated with the written record.
notes= nullString?—Additional notes for the session. Optional field.
plannedExerciseSessionId= nullString?—Google gives no description.
rateOfPerceivedExertion= nullFloat?@FloatRange(from = 0.0, to = 10.0)Rate of perceived exertion (RPE) for the session. Must be between 0 and 10. See ExerciseSegment.rateOfPerceivedExertion
segmentsmandatory= emptyList()List<ExerciseSegment>—ExerciseSegments of the session. Optional field. Time in segments should be within the parent session, and should not overlap with each other.
startTimemandatoryInstant—Start time of the record.
startZoneOffsetZoneOffset?—User experienced zone offset at startTime, or null if unknown. Providing these will help history aggregations results stay consistent should user travel. Queries with user experienced time filters will assume system current zone offset if the information is absent.
title= nullString?—Title of the session. Optional field.

Aggregate metrics

The AggregateMetric constants Google defines on ExerciseSessionRecord. The value type is the T in Google’s AggregateMetric<T> signature. Every aggregate metric across all records.

MetricValue typeGoogle’s description
ExerciseSessionRecord.EXERCISE_DURATION_TOTALDurationMetric identifier to retrieve the total exercise time from androidx.health.connect.client.aggregate.AggregationResult.

Google’s read-data guide: “For cumulative types like StepsRecord, use aggregate() instead of readRecords() to avoid double counting from multiple sources and improve accuracy.” and “The aggregation API also contains logic to handle duplicate records, and lessens the chances of rate limiting.”

Constants

The 61 const val values Google’s reference lists on ExerciseSessionRecord, with the integer each maps to.

ConstantValueGoogle’s description
ExerciseSessionRecord.EXERCISE_TYPE_BADMINTON2—
ExerciseSessionRecord.EXERCISE_TYPE_BASEBALL4—
ExerciseSessionRecord.EXERCISE_TYPE_BASKETBALL5—
ExerciseSessionRecord.EXERCISE_TYPE_BIKING8—
ExerciseSessionRecord.EXERCISE_TYPE_BIKING_STATIONARY9—
ExerciseSessionRecord.EXERCISE_TYPE_BOOT_CAMP10—
ExerciseSessionRecord.EXERCISE_TYPE_BOXING11—
ExerciseSessionRecord.EXERCISE_TYPE_CALISTHENICS13—
ExerciseSessionRecord.EXERCISE_TYPE_CRICKET14—
ExerciseSessionRecord.EXERCISE_TYPE_DANCING16—
ExerciseSessionRecord.EXERCISE_TYPE_ELLIPTICAL25—
ExerciseSessionRecord.EXERCISE_TYPE_EXERCISE_CLASS26—
ExerciseSessionRecord.EXERCISE_TYPE_FENCING27—
ExerciseSessionRecord.EXERCISE_TYPE_FOOTBALL_AMERICAN28—
ExerciseSessionRecord.EXERCISE_TYPE_FOOTBALL_AUSTRALIAN29—
ExerciseSessionRecord.EXERCISE_TYPE_FRISBEE_DISC31—
ExerciseSessionRecord.EXERCISE_TYPE_GOLF32—
ExerciseSessionRecord.EXERCISE_TYPE_GUIDED_BREATHING33—
ExerciseSessionRecord.EXERCISE_TYPE_GYMNASTICS34—
ExerciseSessionRecord.EXERCISE_TYPE_HANDBALL35—
ExerciseSessionRecord.EXERCISE_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING36—
ExerciseSessionRecord.EXERCISE_TYPE_HIKING37—
ExerciseSessionRecord.EXERCISE_TYPE_ICE_HOCKEY38—
ExerciseSessionRecord.EXERCISE_TYPE_ICE_SKATING39—
ExerciseSessionRecord.EXERCISE_TYPE_MARTIAL_ARTS44—
ExerciseSessionRecord.EXERCISE_TYPE_OTHER_WORKOUT0Can be used to represent any generic workout that does not fall into a specific category. Any unknown new value definition will also fall automatically into EXERCISE_TYPE_OTHER_WORKOUT. Next Id: 84.
ExerciseSessionRecord.EXERCISE_TYPE_PADDLING46—
ExerciseSessionRecord.EXERCISE_TYPE_PARAGLIDING47—
ExerciseSessionRecord.EXERCISE_TYPE_PILATES48—
ExerciseSessionRecord.EXERCISE_TYPE_RACQUETBALL50—
ExerciseSessionRecord.EXERCISE_TYPE_ROCK_CLIMBING51—
ExerciseSessionRecord.EXERCISE_TYPE_ROLLER_HOCKEY52—
ExerciseSessionRecord.EXERCISE_TYPE_ROWING53—
ExerciseSessionRecord.EXERCISE_TYPE_ROWING_MACHINE54—
ExerciseSessionRecord.EXERCISE_TYPE_RUGBY55—
ExerciseSessionRecord.EXERCISE_TYPE_RUNNING56—
ExerciseSessionRecord.EXERCISE_TYPE_RUNNING_TREADMILL57—
ExerciseSessionRecord.EXERCISE_TYPE_SAILING58—
ExerciseSessionRecord.EXERCISE_TYPE_SCUBA_DIVING59—
ExerciseSessionRecord.EXERCISE_TYPE_SKATING60—
ExerciseSessionRecord.EXERCISE_TYPE_SKIING61—
ExerciseSessionRecord.EXERCISE_TYPE_SNOWBOARDING62—
ExerciseSessionRecord.EXERCISE_TYPE_SNOWSHOEING63—
ExerciseSessionRecord.EXERCISE_TYPE_SOCCER64—
ExerciseSessionRecord.EXERCISE_TYPE_SOFTBALL65—
ExerciseSessionRecord.EXERCISE_TYPE_SQUASH66—
ExerciseSessionRecord.EXERCISE_TYPE_STAIR_CLIMBING68—
ExerciseSessionRecord.EXERCISE_TYPE_STAIR_CLIMBING_MACHINE69—
ExerciseSessionRecord.EXERCISE_TYPE_STRENGTH_TRAINING70—
ExerciseSessionRecord.EXERCISE_TYPE_STRETCHING71—
ExerciseSessionRecord.EXERCISE_TYPE_SURFING72—
ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_OPEN_WATER73—
ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_POOL74—
ExerciseSessionRecord.EXERCISE_TYPE_TABLE_TENNIS75—
ExerciseSessionRecord.EXERCISE_TYPE_TENNIS76—
ExerciseSessionRecord.EXERCISE_TYPE_VOLLEYBALL78—
ExerciseSessionRecord.EXERCISE_TYPE_WALKING79—
ExerciseSessionRecord.EXERCISE_TYPE_WATER_POLO80—
ExerciseSessionRecord.EXERCISE_TYPE_WEIGHTLIFTING81—
ExerciseSessionRecord.EXERCISE_TYPE_WHEELCHAIR82—
ExerciseSessionRecord.EXERCISE_TYPE_YOGA83—

Read ExerciseSessionRecord in Kotlin

The shape of Google’s own read example, with ExerciseSessionRecord and its names filled in. Get the client with HealthConnectClient.getOrCreate(context) after checking getSdkStatus.

readRecords
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.request.ReadRecordsRequest
import androidx.health.connect.client.time.TimeRangeFilter
import java.time.Instant

suspend fun readExerciseSession(
    client: HealthConnectClient,
    startTime: Instant,
    endTime: Instant,
): List<ExerciseSessionRecord> {
    val response = client.readRecords(
        ReadRecordsRequest(
            ExerciseSessionRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
        )
    )
    // One page of up to pageSize records (default 1000); follow
    // response.pageToken for the rest.
    return response.records
}
aggregate
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.request.AggregateRequest
import androidx.health.connect.client.time.TimeRangeFilter
import java.time.Instant
import java.time.Duration

suspend fun aggregateExerciseSession(
    client: HealthConnectClient,
    startTime: Instant,
    endTime: Instant,
): Duration? {
    val response = client.aggregate(
        AggregateRequest(
            metrics = setOf(ExerciseSessionRecord.EXERCISE_DURATION_TOTAL),
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
        )
    )
    // get() is declared as returning a nullable value.
    return response[ExerciseSessionRecord.EXERCISE_DURATION_TOTAL]
}
Google's example from the ExerciseSessionRecord reference
import androidx.health.connect.client.readRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.request.ReadRecordsRequest
import androidx.health.connect.client.time.TimeRangeFilter

val response =
    healthConnectClient.readRecords(
        ReadRecordsRequest<ExerciseSessionRecord>(
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
    )
for (exerciseRecord in response.records) {
    // Process each exercise record
    // Optionally pull in with other data sources of the same time range.
    val heartRateRecords =
        healthConnectClient
            .readRecords(
                ReadRecordsRequest<HeartRateRecord>(
                    timeRangeFilter =
                        TimeRangeFilter.between(
                            exerciseRecord.startTime,
                            exerciseRecord.endTime,
                        )
                )
            )
            .records
}

Reproduced from Google’s reference page , whose footer states its content and code samples are subject to the licenses described in Google’s Content License.

Setting up the client, the permission flow and Play Console declaration: integrating Health Connect step by step.

HealthKit counterpart

ExerciseSessionRecord sits in the verified cross-platform matrix’s Workouts row, which groups HKWorkout (HKWorkoutBuilder) on Apple with ExerciseSessionRecord, PlannedExerciseSessionRecord on Android. The row groups the metric; it does not pair each Android record with one Apple type.

Watch out: Activity-type taxonomies differ between platforms — map them explicitly rather than by name.

Questions

What permission does ExerciseSessionRecord need?
Google's Health Connect data-types table (read 2026-10-03) declares android.permission.health.READ_EXERCISE and android.permission.health.READ_EXERCISE_ROUTE for reading ExerciseSessionRecord and android.permission.health.WRITE_EXERCISE and android.permission.health.WRITE_EXERCISE_ROUTE for writing it, and says apps must declare permissions in the manifest before requesting them. READ_EXERCISE and WRITE_EXERCISE are also declared for CyclingPedalingCadenceRecord. Note that READ_EXERCISE_ROUTE is not defined by the HealthPermissions reference, which defines READ_EXERCISE_ROUTES instead.
Which aggregate metrics does ExerciseSessionRecord support?
Google's Jetpack reference defines 1 on ExerciseSessionRecord: EXERCISE_DURATION_TOTAL (AggregateMetric<Duration>). Pass it in the metrics set of an AggregateRequest and read it back from the AggregationResult.
What values does ExerciseSessionRecord.rateOfPerceivedExertion accept?
Google's reference types rateOfPerceivedExertion as Float?, annotated @FloatRange(from = 0.0, to = 10.0). Its description reads: "Rate of perceived exertion (RPE) for the session. Must be between 0 and 10. See ExerciseSegment.rateOfPerceivedExertion"
Is ExerciseSessionRecord an interval, instantaneous or series record?
Google's data-types table lists ExerciseSessionRecord as Interval, and defines that column as indicating "whether the data is recorded at an instant in time or over an interval". Its mandatory fields are exerciseType, endTime, laps, metadata, segments and startTime.

Other Activity records

All Activity records →

Sources checked

Google’s descriptions are quoted for identification; the page layout, the capsule and the cross-platform pairing are AIFitnessAPI’s. Generated from Google’s pages by a script, not typed by hand — the same data is behind every page in the Health Connect reference.

← All Health Connect records · Every permission string