What it is
Exposed is JetBrains' SQL library for Kotlin, offering both a typed DSL that mirrors SQL and a lightweight DAO layer.
Declare tables as objects. The DSL builds queries from those column references, so names and types are checked at compile time.
Installation
implementation("org.jetbrains.exposed:exposed-core:0.55.0")Getting started
The smallest useful thing you can do with it, and what each part means.
kotlin
object Books : IntIdTable() {
val title = varchar("title", 200).index()
val year = integer("year")
val authorId = reference("author_id", Authors)
}
transaction {
val recent = Books
.innerJoin(Authors)
.select(Books.title, Authors.name)
.where { (Books.year greaterEq 1990) and (Authors.country eq "US") }
.orderBy(Books.year to SortOrder.DESC)
.limit(10)
.map { it[Books.title] to it[Authors.name] }
Books.insert {
it[title] = "Dune"
it[year] = 1965
it[authorId] = herbertId
}
}Advanced usage
Where the library earns its place over a simpler alternative.
kotlin
// A plain transaction blocks the thread — wrong inside a coroutine.
suspend fun findBooks(): List<Book> = newSuspendedTransaction(Dispatchers.IO) {
Books.selectAll().map { it.toBook() }
}
// One statement instead of N.
transaction {
Books.batchInsert(incoming) { book ->
this[Books.title] = book.title
this[Books.year] = book.year
}
}
// Bulk update in SQL, not in memory.
transaction {
Books.update({ Books.year less 1900 }) { it[archived] = true }
}Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- No transaction in context
- The query ran outside a transaction block, or on a different thread than the one that opened it.
- Lazy relation access fails
- The DAO loaded the entity outside the transaction that is now closed. Map to a plain data class inside the transaction.
Best practices
- Use newSuspendedTransaction inside coroutines; a plain transaction blocks the thread.
- Prefer the DSL over the DAO for anything with joins or complex filtering.
- Use batchInsert for bulk writes rather than looping single inserts.
- Configure the HikariCP pool explicitly; the defaults are not tuned for production.
Background
Why it exists, and what it was reacting to.
Exposed lets you choose your level of abstraction: the DSL keeps SQL visible and type-checked, while the DAO API provides active-record convenience for simpler models.
