Does Jetpack Room Support FTS5? Full-Text Search in Android Room Database
I was building an Android app that needed search functionality across thousands of articles. The basic LIKE '%query%' approach was too slow. I knew SQLite had FTS5 (Full-Text Search 5) with better performance, but when I tried to use it in Room, I hit a wall.
The Problem: Room Only Supports FTS3 and FTS4
I started by checking Room’s documentation for FTS5 support. What I found:
- Room has
@Fts3and@Fts4annotations for full-text search - No
@Fts5annotation exists - FTS5 offers BM25 ranking, phrase queries, and the NEAR operator - features missing in FTS3/FTS4
This is frustrating. FTS5 has been part of SQLite since 2015, yet Room doesn’t natively support it.
Current FTS Support in Room
Room supports FTS3 and FTS4 through annotations. Here’s how to use FTS4:
@Entity@Fts4data class Article( @PrimaryKey val id: Long, val title: String, val content: String)@Daointerface ArticleDao { @Query("SELECT * FROM Article WHERE Article MATCH :query") fun search(query: String): List<Article>}This works. The @Fts4 annotation creates a virtual table behind the scenes. But you miss out on FTS5 features like:
- BM25 ranking - Better relevance sorting
- Phrase queries - Exact phrase matching
- NEAR operator - Find words within N tokens of each other
- Better Unicode support - Improved tokenizer options
Workaround: Using FTS5 with RawQuery
You can still use FTS5 in Room. It just requires more work.
First, create your FTS5 virtual table manually:
@Database(entities = [Article::class], version = 1)abstract class AppDatabase : RoomDatabase() { abstract fun articleDao(): ArticleDao
override fun onCreate(db: SupportSQLiteDatabase) { // Create the FTS5 virtual table manually db.execSQL(""" CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts5 USING fts5( title, content, content='Article', content_rowid='id' ) """) }}Then use @RawQuery to search:
@Daointerface ArticleDao { @RawQuery fun searchFts5(query: SupportSQLiteQuery): List<Article>
@Insert fun insert(article: Article)
@Query("SELECT * FROM Article WHERE id IN (SELECT rowid FROM articles_fts5 WHERE articles_fts5 MATCH :query ORDER BY bm25(articles_fts5))") fun searchWithBm25(query: String): List<Article>}Using it in your code:
val searchTerm = "android development"val articles = articleDao.searchWithBm25(searchTerm)Wait, there’s a catch. The second approach with @Query still uses FTS5 table, but the MATCH query syntax needs proper formatting.
Keeping FTS5 Index in Sync
The bigger problem is keeping your FTS5 index synchronized with your main table. FTS5 doesn’t auto-update when you insert into the main table.
You need triggers:
override fun onCreate(db: SupportSQLiteDatabase) { // Create FTS5 table db.execSQL(""" CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts5 USING fts5( title, content, content='Article', content_rowid='id' ) """)
// Trigger on INSERT db.execSQL(""" CREATE TRIGGER IF NOT EXISTS article_ai AFTER INSERT ON Article BEGIN INSERT INTO articles_fts5(rowid, title, content) VALUES (new.id, new.title, new.content); END """)
// Trigger on UPDATE db.execSQL(""" CREATE TRIGGER IF NOT EXISTS article_au AFTER UPDATE ON Article BEGIN UPDATE articles_fts5 SET title = new.title, content = new.content WHERE rowid = new.id; END """)
// Trigger on DELETE db.execSQL(""" CREATE TRIGGER IF NOT EXISTS article_ad AFTER DELETE ON Article BEGIN DELETE FROM articles_fts5 WHERE rowid = old.id; END """)}This is error-prone. You’re managing database triggers manually.
What About Room 3.0?
I checked the Room 3.0 alpha01 release notes. There’s no mention of FTS5 support in the changelog.
The Reddit thread where someone asked “Will it support FTS5?” didn’t get an official response from the Android team.
My Recommendation
For simple search needs, stick with @Fts4. It covers most use cases:
@Entity@Fts4data class Note( @PrimaryKey val id: Long, val title: String, val body: String)
@Daointerface NoteDao { @Query("SELECT * FROM Note WHERE Note MATCH :query") fun search(query: String): List<Note>}For advanced search with BM25 ranking, you have two options:
- Use raw SQLite with Room - The
@RawQueryapproach above, but manage triggers yourself - Use a separate search library - SQLite directly, or external search like Meilisearch
The @RawQuery approach works, but adds complexity. You’re responsible for:
- Creating the FTS5 virtual table
- Managing triggers for CRUD operations
- Writing raw SQL queries
Why FTS5 Matters for Your App
Search performance directly affects user experience. Users expect instant results. A well-optimized FTS setup can make the difference between a sluggish app and a responsive one.
FTS5’s BM25 ranking gives more relevant results. Instead of returning results in arbitrary order, you get them sorted by relevance. This matters when users search through large datasets.
Before investing time in FTS5 workarounds, profile your actual search performance. FTS4 might be sufficient for your use case.
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: Jetpack Room 3.0 (alpha01): Some big changes ahead
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments