Skip to content

Kapper vs Ktorm

A detailed comparison between Kapper and Ktorm, both Kotlin-focused database libraries.

📁 Source Code: View complete comparison examples in the comparison directory.

Overview

AspectKapperKtorm
PhilosophySQL-first, lightweightDSL-first, type-safe
API StyleFunction-basedObject-oriented + DSL
SQL ControlRaw SQL preferredDSL generates SQL
Type SafetyCompile-time mappingCompile-time DSL
Learning CurveMinimalModerate

Basic Operations

Kapper

kotlin
data class User(val id: Long?, val name: String, val email: String)

// Direct SQL queries
val users = connection.query<User>("SELECT * FROM users WHERE active = true")
val user = connection.querySingle<User>("SELECT * FROM users WHERE id = ?", 1)

// Simple inserts
val userId = connection.execute(
    "INSERT INTO users (name, email) VALUES (?, ?) RETURNING id",
    "Alice", "alice@example.com"
)

Ktorm

kotlin
// Table definition required
object Users : Table<User>("users") {
    val id = long("id").primaryKey().bindTo { it.id }
    val name = varchar("name").bindTo { it.name }
    val email = varchar("email").bindTo { it.email }
    val active = boolean("active").bindTo { it.active }
}

data class User(val id: Long?, val name: String, val email: String, val active: Boolean)

// DSL queries
val users = database.from(Users).select().where { Users.active eq true }.map { Users.createEntity(it) }
val user = database.from(Users).select().where { Users.id eq 1 }.map { Users.createEntity(it) }.firstOrNull()

// DSL inserts
val userId = database.insert(Users) {
    set(it.name, "Alice")
    set(it.email, "alice@example.com")
    set(it.active, true)
}

Complex Queries

Kapper - Raw SQL

kotlin
data class UserStats(
    val userId: Long,
    val userName: String,
    val postCount: Int,
    val avgRating: Double
)

val stats = connection.query<UserStats>("""
    SELECT 
        u.id as userId,
        u.name as userName,
        COUNT(p.id) as postCount,
        COALESCE(AVG(p.rating), 0) as avgRating
    FROM users u
    LEFT JOIN posts p ON u.id = p.user_id
    WHERE u.created_at > ?
    GROUP BY u.id, u.name
    HAVING COUNT(p.id) > 5
    ORDER BY avgRating DESC
    LIMIT ?
""", cutoffDate, limit)

Ktorm - Type-safe DSL

kotlin
// Requires additional table definitions
object Posts : Table<Post>("posts") {
    val id = long("id").primaryKey()
    val userId = long("user_id")
    val rating = double("rating")
    val createdAt = timestamp("created_at")
}

// Complex DSL query
val stats = database
    .from(Users)
    .leftJoin(Posts, on = Users.id eq Posts.userId)
    .select(
        Users.id,
        Users.name,
        count(Posts.id).aliased("postCount"),
        avg(Posts.rating).aliased("avgRating")
    )
    .where { Users.createdAt greater cutoffDate }
    .groupBy(Users.id, Users.name)
    .having { count(Posts.id) greater 5 }
    .orderBy(avg(Posts.rating).desc())
    .limit(limit)
    .map { row ->
        UserStats(
            userId = row[Users.id]!!,
            userName = row[Users.name]!!,
            postCount = row.getInt("postCount"),
            avgRating = row.getDouble("avgRating")
        )
    }

Configuration and Setup

Kapper

kotlin
// Minimal setup with any DataSource
val dataSource = HikariDataSource().apply {
    jdbcUrl = "jdbc:postgresql://localhost/mydb"
    username = "user"
    password = "password"
}

// Ready to use immediately
val users = dataSource.connection.use { connection ->
    connection.query<User>("SELECT * FROM users")
}

Ktorm

kotlin
// Requires Database instance configuration
val database = Database.connect(
    url = "jdbc:postgresql://localhost/mydb",
    driver = "org.postgresql.Driver",
    user = "user",
    password = "password"
)

// Table definitions are required before use
object Users : Table<User>("users") {
    val id = long("id").primaryKey()
    val name = varchar("name")
    val email = varchar("email")
}

val users = database.from(Users).select().map { Users.createEntity(it) }

Next Steps

Source Examples

Released under the Apache 2.0 License.