Does Jetpack Room Support Desktop JVM? Room 3.0 Desktop Target Explained
I was building a Kotlin Multiplatform project and needed to share database code between Android and Desktop JVM targets. The question that kept coming up: Does Jetpack Room support Desktop JVM?
The Problem: Cross-Platform Database Needs
When I started my Kotlin Multiplatform project, I wanted to share as much code as possible between platforms. Database logic seemed like the perfect candidate for code sharing. But Room was historically Android-only, and I kept seeing conflicting information about desktop support.
The alternatives looked less appealing:
- SQLDelight requires learning a different query syntax
- Exposed is JVM-only (no iOS support)
- Writing platform-specific database code defeats the purpose of KMP
I needed a definitive answer about Room’s desktop capabilities.
The Answer: Yes, Room Supports Desktop JVM
After digging through documentation and community discussions, I found that Room supports Desktop JVM as a Kotlin Multiplatform target. This support existed before Room 3.0.
The Room 3.0 alpha01 announcement caused confusion because it highlighted JS and WasmJS targets:
Room 3.0 alpha01 adds:├─ JS target (JavaScript)├─ WasmJS target (WebAssembly)└─ Desktop JVM was already supportedWhen developers asked “what about DesktopJVM?” in the Reddit discussion, the community confirmed: “It already support” - meaning Desktop JVM support was available before Room 3.0.
How Room KMP Targets Work
Room now supports five targets with a single database definition:
| Target | Status | Use Case |
|---|---|---|
| Android JVM | Stable | Android apps |
| Desktop JVM | Stable | Desktop applications |
| iOS (via Kotlin/Native) | Stable | iOS apps |
| JS | Alpha (3.0+) | Web browsers |
| WasmJS | Alpha (3.0+) | WebAssembly |
This means you can write your database layer once in commonMain and use it everywhere.
Implementation: Shared Database Definition
I’ll show you how to set up Room for cross-platform use. The key is placing your database code in commonMain and platform-specific initialization in source sets.
Step 1: Define Entities in commonMain
@Entitydata class User( @PrimaryKey val id: Long, val name: String, val email: String)Step 2: Define DAOs in commonMain
@Daointerface UserDao { @Query("SELECT * FROM User") suspend fun getAll(): List<User>
@Insert suspend fun insert(user: User)
@Delete suspend fun delete(user: User)}Step 3: Define Database in commonMain
@Database(entities = [User::class], version = 1)abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao}This single database definition works across Android, Desktop, iOS, JS, and WasmJS.
Platform-Specific Initialization
The database builder needs platform-specific paths. Here’s how I handle initialization for each platform.
Desktop JVM Initialization
import java.io.File
fun createDatabase(): AppDatabase { val dbFile = File(System.getProperty("user.home"), ".myapp/database.db")
// Ensure parent directory exists dbFile.parentFile?.mkdirs()
return Room.databaseBuilder<AppDatabase>( AppDatabase::class.java, dbFile.absolutePath ).build()}The desktop version uses System.getProperty("user.home") to get the user’s home directory, which is the standard location for application data on desktop platforms.
Android Initialization
import android.content.Context
fun createDatabase(context: Context): AppDatabase { return Room.databaseBuilder( context, AppDatabase::class.java, "app-database" ).build()}Android uses Context to access the app’s database directory, which is handled automatically by the framework.
iOS Initialization
import platform.Foundation.NSDocumentDirectoryimport platform.Foundation.NSFileManagerimport platform.Foundation.NSUserDomainMask
fun createDatabase(): AppDatabase { val documentDirectory = NSFileManager.defaultManager.URLForDirectory( NSDocumentDirectory, NSUserDomainMask, null, false, null )!!
return Room.databaseBuilder<AppDatabase>( AppDatabase::class.java, documentDirectory.path + "/app-database.db" ).build()}iOS requires accessing the documents directory through NSFileManager, which provides the appropriate sandboxed storage location.
Why This Matters
I’ve identified several key benefits of using Room for cross-platform database needs:
Code Reuse Across Platforms
Traditional approach:├─ Android: Room implementation (500 lines)├─ Desktop: Exposed or raw SQLite (500 lines)├─ iOS: Core Data or SQLite.swift (500 lines)└─ Total: 1500 lines of duplicated logic
Room KMP approach:├─ commonMain: Shared database logic (500 lines)├─ Platform initialization: 30 lines each└─ Total: 590 lines with 60% reductionConsistency in ORM Patterns
The same Room patterns I use on Android work everywhere:
- Entity annotations for data classes
- DAO interfaces for data access
- Migration strategies
- Type converters
No need to learn different ORM paradigms for each platform.
Compile-Time Verification
Room’s compile-time checks work on desktop too. I get caught errors before runtime:
@Daointerface UserDao { @Query("SELECT * FROM NonExistentTable") // Error at compile time suspend fun getInvalid(): List<User>}The compiler validates:
- SQL syntax
- Table and column names
- Type mappings
- Query return types
Common Mistakes to Avoid
When implementing Room for desktop, I made several mistakes that you should avoid.
Mistake 1: Using Android-Specific APIs in commonMain
// WRONG: Context is Android-onlyfun createDatabase(context: Context): AppDatabase { // This won't compile for desktop/iOS}Solution: Keep platform-specific types in platform source sets:
// commonMainexpect fun createDatabase(): AppDatabase
// androidMainactual fun createDatabase(): AppDatabase { val context = getAndroidContext() // Platform-specific return Room.databaseBuilder(context, ...)}
// jvmMain (desktop)actual fun createDatabase(): AppDatabase { val path = System.getProperty("user.home") + "/.app/db" return Room.databaseBuilder(AppDatabase::class.java, path)}Mistake 2: Incorrect Desktop Database Paths
// WRONG: Relative path might resolve to unexpected locationval db = Room.databaseBuilder( AppDatabase::class.java, "database.db" // Where is this?).build()Solution: Always use absolute paths with proper directory creation:
val dbFile = File(System.getProperty("user.home"), ".myapp/database.db")dbFile.parentFile?.mkdirs() // Create directory if needed
val db = Room.databaseBuilder( AppDatabase::class.java, dbFile.absolutePath).build()Mistake 3: Assuming All Features Work Identically
Some Room features behave differently on desktop:
Android vs Desktop Differences:├─ FTS (Full-Text Search): Works differently on SQLite versions├─ Migrations: Need to handle SQLite version differences├─ File permissions: Desktop has more flexibility└─ Background threads: Threading models differProject Structure
Here’s how I organize a Kotlin Multiplatform project with Room:
my-project/├─ shared/│ ├─ commonMain/kotlin/│ │ ├─ data/│ │ │ ├─ User.kt (Entity)│ │ │ ├─ UserDao.kt (DAO)│ │ │ └─ AppDatabase.kt (Database)│ │ └─ DatabaseFactory.kt (expect)│ ├─ androidMain/kotlin/│ │ └─ DatabaseFactory.kt (actual with Context)│ ├─ jvmMain/kotlin/│ │ └─ DatabaseFactory.kt (actual for desktop)│ └─ iosMain/kotlin/│ └─ DatabaseFactory.kt (actual for iOS)├─ androidApp/ (Android application)├─ desktopApp/ (Desktop JVM application)└─ iosApp/ (iOS application)Gradle Configuration
Setting up Room in a Kotlin Multiplatform project requires specific Gradle configuration:
kotlin { androidTarget() jvm("desktop") // Desktop JVM target iosX64() iosArm64()
sourceSets { val commonMain by getting { dependencies { implementation("androidx.room:room-runtime:2.6.1") implementation("androidx.room:room-ktx:2.6.1") } } val androidMain by getting { dependencies { implementation("androidx.room:room-runtime:2.6.1") } } val desktopMain by getting { dependencies { implementation("androidx.room:room-runtime:2.6.1") // SQLite JDBC for desktop implementation("org.xerial:sqlite-jdbc:3.42.0.0") } } }}Migration Strategy
If you’re migrating from Android-only Room to cross-platform Room:
Step 1: Extract to commonMain
Move your entities, DAOs, and database class to commonMain:
// androidApp/src/main/kotlin/data/User.kt@Entitydata class User(...)
// Move to:// shared/commonMain/kotlin/data/User.ktStep 2: Create Platform-Specific Factories
Use the expect/actual pattern for database creation:
expect fun createDatabaseBuilder(): RoomDatabase.Builder<AppDatabase>actual fun createDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> { return Room.databaseBuilder( context, AppDatabase::class.java, "app-database" )}actual fun createDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> { val path = File(System.getProperty("user.home"), ".app/db").absolutePath return Room.databaseBuilder( AppDatabase::class.java, path )}Step 3: Update Gradle Configuration
Add Kotlin Multiplatform plugin and configure targets.
Performance Considerations
I noticed performance differences between platforms:
| Platform | Database Location | Typical Performance | Notes |
|---|---|---|---|
| Android | Internal storage | Slower (encrypted FS) | Battery considerations |
| Desktop | User home directory | Fast | SSD improves significantly |
| iOS | App sandbox | Medium | iCloud backup enabled by default |
For desktop applications, I recommend:
- Use SSD storage for database files
- Enable WAL mode for better concurrent access
- Implement connection pooling for multi-threaded access
val db = Room.databaseBuilder<AppDatabase>(...) .setJournalMode(JournalMode.WRITE_AHEAD_LOGGING) .build()Testing Cross-Platform Database Code
Testing Room database code across platforms requires platform-specific test setups:
class UserDaoTest { @Test fun testInsertAndRetrieve() = runTest { val db = createTestDatabase() val dao = db.userDao()
dao.insert(user)
val retrieved = dao.getAll() assertEquals(1, retrieved.size) assertEquals("Test", retrieved[0].name) }}Each platform provides its own test database factory:
actual fun createTestDatabase(): AppDatabase { return Room.inMemoryDatabaseBuilder( context, AppDatabase::class.java ).build()}actual fun createTestDatabase(): AppDatabase { return Room.inMemoryDatabaseBuilder( AppDatabase::class.java ).build()}Summary
Jetpack Room supports Desktop JVM through Kotlin Multiplatform, allowing you to share database code between Android, desktop, iOS, and web applications. The key points:
- Desktop JVM support exists - Available before Room 3.0
- Room 3.0 adds JS and WasmJS - Expanding to web targets
- Single database definition - Works across all platforms
- Platform-specific initialization - Handle file paths per platform
- Avoid Android-specific APIs - Keep commonMain platform-agnostic
The days of writing separate database layers for each platform are over. With Room’s Kotlin Multiplatform support, I can now share my data layer between Android and desktop applications with minimal platform-specific code.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Jetpack Room 3.0 (alpha01) Release Notes
- 👨💻 r/androiddev Discussion on Room 3.0
- 👨💻 Kotlin Multiplatform Documentation
- 👨💻 Room Database Guide
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments