Question

Room Persistence Library Auto-Increment Primary Key in Kotlin

kotlinandroidandroid-room

Question

I am creating a Room Persistence Library Entity class named Food, and I want foodId to behave as an auto-incrementing primary key.

@Entity
class Food(
    var foodName: String,
    var foodDesc: String,
    var protein: Double,
    var carbs: Double,
    var fat: Double
) {
    @PrimaryKey
    var foodId: Int = 0
    var calories: Double = 0.0
}

How can I make foodId an auto-increment field in Room?

Short Answer

By the end of this page, you will understand how Room handles primary keys, how to enable automatic ID generation with @PrimaryKey(autoGenerate = true), how inserts behave, and how to avoid common mistakes when designing Room entities in Kotlin.

Concept

Room is Android's ORM layer on top of SQLite. When you define an @Entity, Room maps that Kotlin class to a database table.

A primary key is a column whose value uniquely identifies each row. In many apps, you do not want to manually assign this value every time you insert data. Instead, you want the database to generate a new unique ID automatically.

In Room, this is done with:

@PrimaryKey(autoGenerate = true)

When autoGenerate = true is enabled:

  • Room treats the field as a generated primary key.
  • You usually pass 0 for an Int key when inserting a new object.
  • SQLite generates the actual ID value for the row.
  • Room can return the inserted row ID from the DAO.

This matters because auto-generated IDs are very common in real apps:

  • users
  • products
  • notes
  • meals
  • messages

Without auto-generation, you would need to manually manage unique IDs, which is error-prone and unnecessary in most local database use cases.

Mental Model

Think of a Room table like a notebook where each row needs a unique ticket number.

  • The row data is the actual note you write.
  • The primary key is the ticket number.
  • autoGenerate = true means the notebook assigns the next ticket number automatically.

So instead of saying, "This food must have ID 17," you say, "Please add this food," and Room gives it the next available ID.

Take Quiz

Syntax and Examples

The key syntax is:

@PrimaryKey(autoGenerate = true)
var foodId: Int = 0

A common Room entity looks like this:

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity
data class Food(
    val foodName: String,
    val foodDesc: String,
    val protein: Double,
    val carbs: Double,
    val fat: Double,
    val calories: Double = 0.0,
    @PrimaryKey(autoGenerate = true)
    val foodId: Int = 0
)

Why 0?

For an Int primary key with auto-generation, 0 is commonly used as the default value for new objects. Room understands that this row does not yet have a real ID from the database.

DAO example

import androidx.room.Dao
import androidx.room.Insert

@Dao
interface FoodDao {
    @Insert
    suspend fun insert(food: Food): Long
}

Inserting data

val food = Food(
    foodName = "Rice",
    foodDesc = "Boiled white rice",
    protein = 2.7,
    carbs = 28.0,
    fat = 0.3,
    calories = 130.0
)

val newId = foodDao.insert(food)

Here:

  • foodId is not manually set.
  • Room inserts the row.
  • SQLite generates the ID.
  • The DAO returns that generated row ID as Long.

Direct answer

To make your field auto-increment in Room, write:

@PrimaryKey(autoGenerate = true)
var foodId: Int = 0

Step by Step Execution

Consider this entity and insert call:

@Entity
data class Food(
    val foodName: String,
    val foodDesc: String,
    val protein: Double,
    val carbs: Double,
    val fat: Double,
    @PrimaryKey(autoGenerate = true)
    val foodId: Int = 0
)
val food = Food(
    foodName = "Egg",
    foodDesc = "Boiled egg",
    protein = 6.0,
    carbs = 0.6,
    fat = 5.0
)

val id = foodDao.insert(food)

What happens step by step:

  1. A Food object is created.
  2. foodId is left at its default value of 0.
  3. foodDao.insert(food) sends the object to Room.
  4. Room sees that foodId is the primary key and has autoGenerate = true.
  5. Because the ID is unset for a new row, SQLite generates a unique numeric ID.
  6. The row is stored in the database.
  7. The DAO returns the generated row ID, for example 1.

After more inserts, generated IDs might look like:

  • first row -> 1
  • second row -> 2
  • third row -> 3

The important idea is that your app does not have to calculate or track those IDs manually.

Real World Use Cases

Auto-generated primary keys are useful in many real applications:

  • Food tracking apps: each meal or food item gets a unique local ID.
  • Notes apps: every note needs a stable identifier.
  • Offline-first apps: items created locally need unique IDs before syncing.
  • Inventory apps: each product record needs a distinct key.
  • Task managers: every task should be uniquely identifiable for updates and deletes.

For example, if you want to edit or delete a food item later, the generated foodId gives you a reliable way to find that exact row.

Take Quiz

Real Codebase Usage

In real projects, developers often combine auto-generated IDs with a few common patterns.

1. Insert and get the generated ID

@Insert
suspend fun insert(food: Food): Long

This is useful when you need to immediately navigate to a detail screen or save related data.

2. Use the ID for updates and deletes

@Update
suspend fun update(food: Food)

@Delete
suspend fun delete(food: Food)

The primary key helps Room know which row to update.

3. Keep entity creation simple

Developers usually create new entities without manually assigning IDs:

val food = Food("Apple", "Fresh apple", 0.3, 14.0, 0.2)

4. Validation before insert

Apps often validate values before saving:

if (food.foodName.isBlank()) {
    throw IllegalArgumentException("Food name cannot be blank")
}

5. Separate database IDs from server IDs

In larger apps, a Room entity may contain:

  • a local auto-generated primary key
  • a remote API ID from the backend

Example:

data class Food(
    @PrimaryKey(autoGenerate = true)
    val foodId: Int = 0,
    val serverId: String?,
    val foodName: String
)

This is common in sync-enabled apps.

Common Mistakes

1. Forgetting autoGenerate = true

Broken example:

@PrimaryKey
var foodId: Int = 0

Problem:

  • Room treats foodId as a normal primary key.
  • If you keep inserting rows with 0, you can get conflicts.

Fix:

@PrimaryKey(autoGenerate = true)
var foodId: Int = 0

2. Manually setting IDs for new rows unnecessarily

Broken example:

val food = Food("Milk", "Low fat milk", 3.4, 5.0, 1.0, foodId = 99)

Problem:

  • You may create collisions with existing rows.
  • It defeats the purpose of auto-generation.

Fix:

  • Let Room assign the ID unless you have a specific reason not to.

3. Expecting the object to automatically update its property after insert

Depending on your code structure, the returned ID is often the safest value to use immediately:

val insertedId = foodDao.insert(food)

Do not assume your original object instance will always be refreshed automatically.

4. Using the wrong type carelessly

Room commonly uses Int or Long for generated IDs. If your table may grow large, Long can be safer.

Example:

@PrimaryKey(autoGenerate = true)
val foodId: Long = 0L

5. Putting mutable database state everywhere

Beginners often make every field var. In many Room entities, val is cleaner unless mutation is needed.

data class Food(
    val foodName: String,
    @PrimaryKey(autoGenerate = true)
    val foodId: Int = 0
)

Comparisons

ConceptWhat it doesWhen to use
@PrimaryKeyMarks a field as the unique row identifierUse when you will provide the ID yourself
@PrimaryKey(autoGenerate = true)Marks a field as the primary key and lets Room/SQLite generate itUse for most local entities
Manual ID assignmentYou set the value before insertUse only when IDs come from another source
Auto-generated local IDDatabase creates the valueBest for offline/local records

Int vs Long for Room primary keys

TypeExampleNotes
Intval foodId: Int = 0Fine for many apps
Longval foodId: Long = 0LCommon and safer for larger datasets

Class vs data class

OptionNotes
Regular classWorks, but often more verbose
data classCommon in Room because it is concise and readable
Take Quiz

Cheat Sheet

@PrimaryKey(autoGenerate = true)
val id: Int = 0

Rules

  • Add autoGenerate = true to the primary key annotation.
  • Use 0 for Int or 0L for Long as the default value.
  • Do not manually assign IDs for normal inserts.
  • Use the DAO @Insert return value to get the generated row ID.

Example entity

@Entity
data class Food(
    val foodName: String,
    val foodDesc: String,
    val protein: Double,
    val carbs: Double,
    val fat: Double,
    val calories: Double = 0.0,
    @PrimaryKey(autoGenerate = true)
    val foodId: Int = 0
)

Example DAO

@Dao
interface FoodDao {
    @Insert
    suspend fun insert(food: Food): Long
}

Common gotchas

  • Missing autoGenerate = true
  • Manually setting IDs for new rows
  • Expecting duplicate IDs to work
  • Forgetting that Room uses the primary key for updates and deletes

FAQ

How do I make a Room primary key auto-increment?

Use @PrimaryKey(autoGenerate = true) on the ID field.

Should I use Int or Long for a Room primary key?

Both work, but Long is often preferred for safety in larger databases.

What default value should I use for an auto-generated ID in Room?

Use 0 for Int or 0L for Long.

Do I need to set the ID before inserting a row?

No. For auto-generated keys, let Room and SQLite create it.

Can I still update a row if the ID is auto-generated?

Yes. Once the row exists, its generated primary key identifies it for updates and deletes.

Does Room automatically return the generated ID after insert?

It can, if your @Insert DAO method returns Long or List<Long>.

Is auto-increment the same as a unique key?

Not exactly. A primary key must be unique. Auto-generation is just a way to create that unique value automatically.

Take Quiz

Mini Project

Description

Build a simple food storage feature for an Android app using Room. Each food item should be saved with a name and nutrition values, while the database automatically assigns a unique ID. This demonstrates how auto-generated primary keys work in a practical setup.

Goal

Create a Room entity and DAO that can insert food items and return the generated ID without manually setting the primary key.

Requirements

  1. Create a Food Room entity with an auto-generated primary key.
  2. Add fields for name, description, protein, carbs, fat, and calories.
  3. Create a DAO with an insert method that returns the generated ID.
  4. Insert at least one Food object without manually setting its ID.
Take Quiz

Keep learning

Related questions

View more