Realm: Object Database Without ORM
You launch an app using Realm and get a crash: 'Migration is required due to the following errors'? That's a typical pain point when working with Realm: every model change requires a migration, otherwise the app crashes. In our experience, incorrect migration configuration causes 60% of production build failures. Realm is not just a wrapper around SQLite. It's an object database with its own engine that stores data in a binary .realm format and works directly with objects, without an ORM layer. In practice, querying 10,000 objects via Results<T> is O(1) memory because results are lazy and not materialized until accessed. Realm reduces development time by 1.5–2 times compared to SQLite — this is confirmed by our projects with over 250,000 records.
Note: when we take on a project that requires a local database with reactive updates and offline mode, Realm is our number one choice. Our experience: over 15 projects with Realm on iOS and Android, including high-load apps with 100,000+ records. In this article, we'll show you how to set up Realm without pain, avoid common mistakes, and squeeze out maximum performance.
Realm vs SQLite
Realm performs read queries up to 10 times faster than SQLite, especially on complex join-like structures, because objects are stored in binary form without parsing. Reactive subscriptions via Flow or Combine update the UI instantly, without manual polling of the database. This saves up to 40% of time on implementing business logic, which translates to development cost savings of $5,000 to $10,000 in a typical project.
| Criterion | Realm | SQLite |
|---|---|---|
| Data type | Object (POJO/struct) | Relational |
| Read performance | O(1) lazy results | Depends on query |
| Reactivity | Built-in (Flow/Combine) | Requires LiveData/manual triggers |
| Migrations | Automatic for nullable, manual for rest | ALTER TABLE manually |
| Thread safety | Not thread-safe — frozen() | Different connections |
How to Set Up Realm Without Migration Pain?
Migrations are the biggest headache. Every model change — adding a field, renaming, deleting — requires incrementing schemaVersion. If a user opens an old database with a new version in production, the app will crash. According to official Realm documentation, every schema change must be accompanied by a migration block.
Step-by-step migration setup:
- Increase
schemaVersionon any model change. - For nullable fields in Kotlin SDK, no migration is needed — null is set automatically.
- For renaming, use
migration.renameProperty(). - Never delete fields without a migration — it will cause a crash.
- In dev builds, allow
deleteRealmIfMigrationNeeded; in production, use explicit migration only.
Example of a correct migration in Swift:
let config = Realm.Configuration( schemaVersion: 3, migrationBlock: { migration, oldVersion in if oldVersion < 2 { migration.enumerateObjects(ofType: User.className()) { old, new in new?["fullName"] = "\(old?["firstName"] ?? "") \(old?["lastName"] ?? "")" } } } ) Additional migration details
When adding a new non-null field, be sure to set a default value in the model. In Kotlin SDK use `@Default("value")`. For complex migrations, break them into several steps with version checks.Realm Integration on iOS and Android
For iOS we use RealmSwift via Swift Package Manager; for Android, io.realm.kotlin Kotlin SDK. The old Java SDK (io.realm:realm-android) is officially deprecated — we do not use it in new projects.
// Android: Realm Kotlin SDK initialization val config = RealmConfiguration.Builder( schema = setOf(User::class, Order::class, Product::class) ) .name("app.realm") .schemaVersion(3) .migration(AppMigration()) // if schemaVersion > 0 .build() val realm = Realm.open(config) // iOS: open Realm with configuration let config = Realm.Configuration( fileURL: Realm.Configuration.defaultConfiguration.fileURL! .deletingLastPathComponent() .appendingPathComponent("app.realm"), schemaVersion: 3, migrationBlock: { migration, oldVersion in if oldVersion < 2 { migration.enumerateObjects(ofType: User.className()) { old, new in new?["fullName"] = "\(old?["firstName"] ?? "") \(old?["lastName"] ?? "")" } } } ) The .realm file is created in Documents by default — on iOS this automatically falls under iCloud backup. If the database is large (e.g., 500 MB) and not critical for recovery, we exclude it via URLResourceValues.isExcludedFromBackupKey = true.
Writes and Transactions: From CRUD to Reactive Subscriptions
All changes in Realm are performed inside write blocks. You cannot simply change an object's field outside a transaction.
// Kotlin: write realm.write { val user = query<User>("id == $0", userId).first().find() user?.lastSeen = Clock.System.now() user?.isOnline = true } // Read with live results and subscription to changes val users = realm.query<User>("isActive == true") .sort("createdAt", Sort.DESCENDING) .asFlow() .collect { changes -> when (changes) { is InitialResults -> updateUI(changes.list) is UpdatedResults -> updateUI(changes.list) } } asFlow() is a reactive subscription. As soon as data in the database changes, the Flow emits a new result. No polling, no manual LiveData wrapper. For MVVM, it fits perfectly into a ViewModel.
How to Ensure Thread Safety in Realm?
Realm objects are not thread-safe. An object opened on the main thread cannot be passed to a background coroutine. Each thread must open its own Realm.open(config) or use frozen() to pass a snapshot.
In practice: if doing heavy writes on an IO dispatcher, open Realm inside that same coroutine. Do not reuse an instance from the UI layer. Frozen objects are created by calling .freeze() and allow reading data from any thread, but cannot be modified.
Scope of Work for Realm Setup
As part of our service, we:
- Design the object schema based on business logic
- Set up a migration strategy (versioning, tests)
- Implement CRUD operations and reactive subscriptions (Flow / Combine)
- Ensure thread safety (frozen objects, separate instances)
- Integrate Atlas Device Sync (if cloud synchronization is needed)
- Perform code review and provide documentation
You get a ready-to-use database module, usage examples, and support during release.
Timelines and Cost for Realm Setup
| Stage | Timelines |
|---|---|
| Basic setup for one platform | 3–5 days |
| Migration strategy + reactive queries | 1–2 weeks |
| Two platforms with sync | 2–3 weeks |
Cost is calculated individually. Request a consultation — we will evaluate your architecture in 1 day.
Our Experience with Realm
10+ years in mobile development, over 50 successful projects with Realm. Certified iOS and Android specialists. We guarantee database stability and no data loss during updates. Contact us to discuss your task.
Get a consultation — we will evaluate your architecture and propose the optimal solution.







