GRDB.swift in 2026: vs Core Data, SwiftData, and Realm
Contents
Persistence-layer decisions rarely fail because the toolkit is bad, they fail because teams pick one whose concurrency model fights their app's architecture. GRDB.swift is a SQLite toolkit built around WAL-based concurrency and explicit SQL, positioned between Apple's higher-level frameworks and raw SQLite.
For a senior iOS engineer weighing it against Core Data, SwiftData, and Realm, the real question isn't feature checklists, it's which model survives a Swift 6 strict-concurrency migration intact. Here's what we found running that migration on real codebases.
GRDB.swift at a glance: Is it right for your app?
GRDB.swift is the pragmatic pick for teams running SQLite under Swift 6 strict concurrency checking, ahead of where SwiftData currently sits. Version 7 ships full Swift Concurrency support, confirmed against Swift 6.1 and Xcode 16.3 in the GRDB.swift GitHub changelog and readme.
In our work migrating production Core Data codebases to GRDB and benchmarking query interface requests against NSFetchRequest on matched datasets, we found measurable performance gains. GRDB's write-ahead logging model, layered over a single SQLite database file, resolves concurrency conflicts that Core Data's context juggling only masks.
This piece compares GRDB, Core Data, SwiftData, and Realm on concurrency guarantees, raw SQL access, Codable record types, and long-term maintenance risk, including the acquisition overhang on Realm since MongoDB bought it.
What is GRDB.swift used for?
GRDB.swift is a SQLite toolkit for Swift, not an object-relational mapper in the Core Data or Realm sense. It wraps SQLite directly and exposes both raw SQL and a typed query interface, so you define Codable record types that map straight to table rows without a generated model file or a class hierarchy.
Teams reach for GRDB.swift when they need SQLite's concurrency and query power without SwiftData's current feature gaps or Realm's exposure to MongoDB's roadmap.
Common use cases: offline-first apps syncing large local caches, apps needing full-text search or custom reporting SQL, and Core Data codebases migrating toward tighter control over threading than NSManagedObjectContext gives you.
This decision often ties into broader tradeoffs in building apps with Swift, since persistence strategy is just one part of the platform's overall development experience.
GRDB's ValueObservation replaces NSFetchedResultsController for reactive UI reload, publishing row changes over Combine or async sequences whenever the underlying database changes. Record types copy plain SQLite rows into Codable structs at the boundary, so persistence logic and view logic never share mutable state.
Worth checking the readme file on the GRDB.swift GitHub repository directly, ideally in another tab while you read the changelog. The commit history and blame make clear that raw SQL support was core to the design from the first release, not bolted on later, and that the maintainer, groue, has kept it that way through version 7.
Core design principles: Records, raw SQL, single source of truth
GRDB.swift's core bet is that the database is the model, not an abstraction layered on top of one.
You define Codable record types as plain structs that conform to FetchableRecord and PersistableRecord, then read and write them with a query interface request that compiles to SQL at build time. GRDB skips the generated model file, the .xcdatamodeld boilerplate, and the hidden object graph that Core Data maintains.
That single-source-of-truth stance is the real philosophical break from Core Data and Realm. Core Data maintains an in-memory object graph that can drift from what's actually on disk; Realm does something similar with live objects backed by its own file format.
GRDB skips that layer. Every QueryInterfaceRequest you write, and every raw SQL string you drop in when the interface can't express what you need, executes directly against the SQLite file.
GRDB.swift's GitHub releases page documents version 7.0 as the point where the library reached full Swift 6 strict concurrency conformance, which matters here because there's no ORM-managed context to reason about, just the database and Sendable value types moving through it.
That design forces a discipline Core Data hides from you: you decide up front what a record is, not what a managed object becomes at runtime.
Does GRDB.swift support Swift concurrency and Async/Await?
GRDB.swift supports Swift Concurrency natively. Version 7 enforces Swift 6 strict concurrency checking across its entire public API, according to the GRDB.swift release notes on GitHub. DatabaseQueue and DatabasePool both expose async/await entry points alongside their older completion-block and Combine interfaces, and every public type is audited for Sendable conformance.
The concurrency model rests on how GRDB wraps SQLite's write-ahead logging (WAL) mode. GRDB offers two concurrency strategies: DatabaseQueue serializes every access, read and write, onto one connection, which mirrors how a single NSManagedObjectContext behaves and suits small or medium databases.
DatabasePool opens a pool of reader connections against a WAL database file and lets writes proceed on a dedicated writer while readers keep serving consistent snapshots. That's the throughput win: concurrent reads no longer queue behind a writer, something Core Data's context-per-queue model can't do without manual plumbing.
In migration work moving a Core Data codebase to GRDB under Swift 6 strict checking, most of the effort wasn't rewriting queries. It was untangling NSManagedObjectContext references that were never Sendable and had been passed across queues informally for years, a pattern a quick blame on the older data layer file usually confirms in minutes.
GRDB's ValueObservation replaces NSFetchedResultsController for reactive reads: it triggers an automatic reload of query results on write and refreshes subscribers through an async sequence, no delegate or Combine bridge required. Records are copy-on-write value types, so passing them across actor boundaries needs no locking.
Realm's async/await support arrived later and remains partial per its own README, and SwiftData's @ModelActor model still has open Sendable-enforcement threads on the Swift Forums as of Xcode 16.x.
GRDB's connection-pool approach, backed by signed releases on the GitHub repo, sidesteps that class of bug by construction.
GRDB.swift vs Core Data vs SwiftData vs Realm: Comparison table
Choosing a persistence layer comes down to trade-offs between control, safety, and setup effort.
| Feature | GRDB.swift | Core Data | SwiftData | Realm |
|---|---|---|---|---|
| Underlying storage | SQLite | SQLite (opaque) | SQLite (opaque) | Custom object store |
| Swift Concurrency | Full async/await, actors | Partial, context-bound | Full, model actor | Partial |
| Type safety | Strong, compile-time checked | Moderate | Strong | Moderate |
| Raw SQL access | Yes, first-class | No | No | No |
| Migrations | Explicit, versioned actions | Managed, opaque | Managed, opaque | Managed |
| Installation | SPM, CocoaPods podspec | Built-in | Built-in | SPM, CocoaPods podspec |
| Learning curve | Moderate | Steep | Gentle | Gentle |
| Best for | SQL-fluent teams needing control | Legacy Apple apps | New Apple-only apps | Cross-platform teams |
GRDB.swift sits between Core Data's opacity and raw SQLite, giving developers master-level query control without sacrificing Swift-native safety.
Installing GRDB.swift: SPM, CocoaPods, and version compatibility
GRDB.swift installs cleanest through Swift Package Manager: add https://github.com/groue/GRDB.swift as a dependency in your Package.swift file, or via Xcode's Add Package dialog, and pin to the 7.x major version.
CocoaPods still works, pod 'GRDB.swift' in your Podfile, but the maintainer's own GRDB.swift README treats SPM as the primary path going forward, and CocoaPods support gets less attention in release notes.
Version compatibility is the part teams get wrong. GRDB.swift 7 requires Swift 6.1 and Xcode 16.3 or later, per the GRDB.swift GitHub releases changelog, because it adopts strict concurrency checking end to end rather than bridging with @preconcurrency.
If your project is still on Swift 5 mode, stay on GRDB 6.x until you've done the Swift 6 migration, don't try to force the two upgrades in one pull request.
After adding the package, do a clean build (not just a Derived Data reload) before touching any raw SQL calls, since stale module caches are the usual cause of "cannot find DatabaseQueue" errors on first setup.
Associations and relationships: belongsTo and hasMany
GRDB.swift models belongsTo and hasMany through explicit associations on Codable record types, not through a graph the framework manages behind your back. You declare the relationship once, then GRDB expands it into a single SQL join at fetch time.
struct Author: Codable, FetchableRecord, PersistableRecord {
static let books = hasMany(Book.self)
var id: Int64
var name: String
}
struct Book: Codable, FetchableRecord, PersistableRecord {
static let author = belongsTo(Author.self)
var id: Int64
var authorId: Int64
var title: String
}
let request = Author.including(all: Author.books)
let authorsWithBooks = try Author.fetchAll(db, request)
That request is a query interface request: composable, type-checked at compile time, and inspectable as raw SQL when you need to check the generated EXPLAIN QUERY PLAN.
Core Data hides that same join behind NSFetchRequest relationship faulting. Fetching an author's books looks similarly declarative:
let request = Author.fetchRequest()
request.relationshipKeyPathsForPrefetching = ["books"]
let authors = try context.fetch(request)
Nothing here reveals what SQL actually runs. To see it, you enable com.apple.CoreData.SQLDebug 1 as a launch argument and read the console output after the fact, rather than inspecting the query before it executes.
That difference matters once join performance regresses in production. GRDB lets you print or log the compiled SQL for any request during development, so a slow query shows up as a diagnosable string, not a debugging session.
SwiftData's @Relationship macro is more ergonomic to write, but as of Swift 6.1 it still inherits Core Data's underlying store, with the same opacity around generated joins.
Query interface and fetching requests
GRDB's query interface request builds type-safe SQL directly against your Codable record types, compiling to real SQLite statements you can inspect before they run against your database. Compare that to NSFetchRequest, where the predicate is a string Core Data translates at runtime, and a typo only surfaces as a crash log you blame on bad data instead of a compiler error.
On a Core Data to GRDB migration we ran under Swift 6 strict concurrency, replacing NSFetchRequest fetches with QueryInterfaceRequest chains removed a layer of context-juggling boilerplate entirely.
The GRDB readme documents the type-safe query interface and raw SQL escape hatches in the same file, so you drop to hand-written SQL for a complex aggregate, then copy the generated statement into a database client to debug it, without leaving the GRDB API surface.
That dual access has held steady since GRDB 7.0, alongside full Swift 6 concurrency support. Realm and SwiftData expose no equivalent raw SQL path, an option worth keeping when a query interface abstraction can't express what a hand-tuned SQLite plan can.
ValueObservation: Reactive updates with Combine and Async/Await
ValueObservation turns a query interface request into a live stream: GRDB reruns the request whenever the underlying tables change and pushes only the results that actually differ, using SQLite's write-ahead logging to detect writes without polling the database file on a timer.
You choose the consumption model per call site. .publisher(in:) gives you a Combine publisher for UIKit view controllers or existing Combine pipelines. .values gives you an AsyncSequence you can await in a Swift 6 actor-isolated view model without crossing an isolation boundary.
Our rule of thumb from recent migrations: keep Combine where a codebase already leans on it for UI binding, and use .values for anything new, since AsyncSequence composes cleaner with structured concurrency and avoids the Sendable friction Combine's sink closures tend to introduce under strict checking.
Either path reruns off the main thread by default and dispatches the diffed result back, so a list view refreshes without a manual reload call.
Core Data's NSFetchedResultsController and SwiftData's @Query solve the same reactive problem, but neither exposes a raw async stream as directly as ValueObservation does; that's a real ergonomics gap worth weighing during evaluation, and one the GRDB README documents in detail alongside its concurrency guide.
Full-text search, encryption, and SwiftUI integration
GRDB.swift bundles full-text search, at-rest encryption, and a SwiftUI-native property wrapper, three things Core Data handles awkwardly and SwiftData does not expose at all as of iOS 18. Choosing the right persistence layer is only part of the equation; getting the implementation right often benefits from expert Swift developers who have navigated these trade-offs on production apps.
FTS5 support ships as a compile-time trait: define a virtual table with FTS5Pattern, and GRDB.swift compiles raw MATCH queries into the same query interface request builder used everywhere else, so ranked search sits next to ordinary joins in one file instead of a bolted-on NSPredicate workaround.
Encryption goes through SQLCipher, the AES-256 extension for SQLite. GRDB.swift links it as a build variant (GRDB.swift/SQLCipher via Swift Package Manager), and SQLCipher itself is deployed on hundreds of millions of devices across thousands of commercial and open source products, according to Zetetic's SQLCipher project page. Realm offers encryption too, but under a database engine now owned by MongoDB, which changes the calculus on long-term license risk.
GRDBQuery, a companion library from GRDB's author Groué on GitHub, gives SwiftUI a @Query property wrapper backed by ValueObservation instead of Core Data's @FetchRequest. Views reload automatically on write-ahead logging commits, with no manual observation wiring and no context save-then-refresh dance.
Check the project readme before adopting SQLCipher in an existing app: migrating an unencrypted database file to an encrypted one is a one-time, blocking operation worth scheduling deliberately, not discovering in a crash report.
Is GRDB.swift still maintained? Migration risk in 2026
GRDB.swift is actively maintained heading into 2026, with the v7 line tracking Swift 6.1 and Xcode 16.3 within weeks of each Apple release. The real risk isn't code quality, it's that one maintainer, Gwendal Roué (GitHub handle groue), owns the bulk of the commit history.
Check the repository yourself: the commits tab, the blame view on CHANGELOG.md, and the raw README all show a project that ships, not one that's gone quiet.
According to GRDB.swift's GitHub releases, the project has cut a new tagged release for every major Swift and Xcode cycle since Swift Concurrency landed, a cadence few single-maintainer Swift libraries match.
If this single-maintainer risk gives your team pause, partnering with an experienced software development team can help you evaluate and de-risk your dependency choices.
That bus-factor concern is real but manageable, because GRDB's surface area is small and well-tested, unlike SwiftData, which still lacks a public migration story for complex schema changes, or Realm, which carries its own uncertainty after the MongoDB acquisition folded it into a broader roadmap.
Worth separating two meanings of "migration" here. Codebase migration risk (moving off Core Data) is low: GRDB's DatabaseMigrator handles schema-level database migrations explicitly, with each step versioned and tested independently, so a failed migration doesn't corrupt production data on reload. If the maintainer disappeared tomorrow, you'd still have a forkable, single-file-readable SQLite wrapper, not a black box.
FAQ: GRDB.swift questions answered
GRDB.swift vs Core Data
GRDB.swift vs SwiftData
GRDB.swift vs Realm
How to install GRDB.swift with Swift package manager
Does GRDB.swift support Swift 6 strict concurrency
GRDB.swift FTS5 full text search example
db.create(virtualTable:), queried through a Codable record. A typical setup indexes title and body columns and matches with FTS5Pattern, giving ranked full-text search without a separate index or library.
Is GRDB.swift still maintained in 2026
GRDB.swift SQLCipher encryption setup
Need help choosing or migrating your iOS persistence layer?
Choosing between GRDB.swift, Core Data, SwiftData, and Realm is a one-way door once a schema and raw SQL access patterns are in production.
Our engineers have migrated Core Data codebases to GRDB.swift under Swift 6 strict concurrency, and re-pointed Realm apps away from MongoDB's acquisition roadmap risk.
If you're still weighing your options at a broader level, it's worth taking time to compare the top iOS databases before committing to a schema.
If you are weighing a rewrite, a phased migration, or just want a second opinion before you touch the database file, talk to our team. We answer engineering questions directly, no sales script.
