Persist non-primitive types in Room using TypeConverters or Embedded types.
On this page
Type Converter
Type converters are pairs of functions, annotated with @TypeConverter, that translate a single database column type to a Kotlin property type and back.
Typical uses:
- Map an
Instantto aLongfor a SQLiteintegercolumn. - Map a
Locationto aStringfor a SQLitetextcolumn. - Map a collection of
Stringto a singleString(e.g., CSV) for atextcolumn.
Type converters are strictly 1:1 (one property ↔ one column).
Setting Up a Type Converter
- Create any Kotlin class to hold converter functions.
- For each type pair, create two functions: A→B and B→A. If input is
null, returnnull. Ensure the round-trip preserves values. - Annotate both functions with
@TypeConverter. - Bring them into scope with
@TypeConverterson one of:
| @TypeConverters on… | Applies to… |
|---|---|
| RoomDatabase | everything in the database |
| Entity class | all properties in that entity |
| Entity property | that property only |
| DAO class | all DAO functions |
| DAO function | that function (all params) |
| DAO function parameter | that single parameter |
Example: an entity using converters for Instant, Location, and Set<String>:
val id: Long ,
val instant: Instant ,
val location: Location,
val tags: ) suspend fun :
suspend fun
}
}
: this
}InstantTypeConverter
SQLite does not have a native date/time type. The recommended approach is to store timestamps as the number of milliseconds since the Unix epoch (January 1, 1970, 00:00:00 UTC).
Instant has a toEpochMilliseconds() function that returns this value.
fun
fun timestamp?.let
}With @TypeConverters(InstantTypeConverter::class) in scope, Room will persist Instant as a SQLite integer.
LocationTypeConverter
Persist a Location (latitude/longitude) as a single String in a text column (e.g., “latitude;longitude”):
A Location object contains a latitude, longitude, and perhaps other values (e.g., altitude). If we only care about the latitude and longitude, we could save those in the database in a single text column, so long as we can determine a good format to use for that string. One possibility is to have the two values separated by a semicolon.
The LocationTypeConverter class has a pair of functions designed to convert between a Location and a String:
fun location?.let
fun val pieces if try
} null
}
} else null
}
}
}Trade-off: compact storage but poor queryability by location.
SetTypeConverter
When you don’t want a separate relation, encode collections. CSV is simple but limited; JSON is flexible:
fun
fun }Given these type conversion functions, we can use a Set of String values in Record:
val tags: …where the tags will be stored in a text column.
Embedded Types
Type converters map one property to one column.
When a single property should span multiple columns, use @Embedded on a simple Kotlin class and reference it in your entity.
Example: Locations
To query by latitude/longitude, embed them as separate columns:
val id: Long ,
val name: String,
val location: Location
) suspend fun :
suspend fun
}
}Schema:
not exists embedded (
id integer primary key autoincrement not null,
name text not null,
latitude real not null,
longitude real not null
)You may use @ColumnInfo inside embedded classes to rename columns.
Types within embedded classes must still be Room-supported (natively or via converters).
Simple vs. Prefixed
If you embed the same class multiple times, use a column prefix to avoid name collisions:
val officeLocation: LocationResulting columns:
not exists embedded (
id integer primary key autoincrement not null,
name text not null,
office_latitude real not null,
office_longitude real not null
)Remember to use the prefixed names in any @Query.
Task
Create a Money value object with amount and currency and persist it in Room in two ways:
- As a single column using a
TypeConverter(e.g., JSON or “amount;currency”). - As embedded columns using
@Embeddedwith a prefix.
Requirements
- Implement
OrderandOrderItementities that useMoney. - Provide minimal SQL methods (insert + select) to verify round-trips for both approaches.
- Ensure stable formatting and locales.
- Prefer
BigDecimalfor amount; if usingDouble, document precision trade-offs. - Write tests that insert and read back values, asserting equality for:
- Multiple currencies (e.g., USD, EUR),
- Edge amounts (0, negative, large, fractional),
- Embedded with and without prefixes (e.g., price vs. discount).
Show solution