Skip to content

Flash ORM - Kotlin Usage Guide ​

A comprehensive guide to using Flash ORM with Kotlin projects, featuring JDBC, Exposed, and R2DBC driver support.

Table of Contents ​

Prerequisites ​

  • Kotlin 1.9+
  • JVM 17+
  • Gradle (Kotlin DSL or Groovy) or Maven build system
  • Flash ORM CLI installed via npm, pip, or direct binary

Quick Start ​

1. Initialize a Kotlin Project ​

bash
# Initialize with PostgreSQL
flash init --postgresql

# Flash ORM auto-detects Gradle/Maven Kotlin projects

2. Generate Code ​

bash
# After defining schema and queries:
flash generate

3. Configure Your flash.toml ​

toml
[gen.kotlin]
enabled = true
out = "src/main/kotlin/com/myapp/db"
package = "com.myapp.db"
driver = "jdbc"

Configuration ​

Flash TOML Settings ​

SettingDefaultDescription
enabledfalseEnable Kotlin code generation
outflash_genOutput directory for generated Kotlin files
packagederived from out dirKotlin package name (e.g., com.example.db)
driverjdbcDriver type: jdbc, exposed, or r2dbc

Driver-Specific Config ​

toml
[gen.kotlin]
enabled = true
out = "src/main/kotlin/com/myapp/db"
package = "com.myapp.db"
driver = "jdbc"

Package Auto-Detection ​

Flash ORM automatically detects your Kotlin package from build files during flash init:

Gradle Kotlin DSL (build.gradle.kts) ​

The package is derived from the group property:

kotlin
group = "com.kotlin.app"
// Generates: package = "com.kotlin.app"

Also supports the group(...) function syntax:

kotlin
group("com.kotlin.app")

Gradle Groovy (build.gradle) ​

groovy
group = 'com.example'
// Generates: package = "com.example"

Maven with Kotlin (pom.xml) ​

xml
<project>
    <groupId>com.example</groupId>
    <artifactId>kotlin-app</artifactId>
</project>
<!-- Generates: package = "com.example.kotlin-app" -->

Generated Code Overview ​

Running flash generate produces:

Models File ​

All models are generated in a single Models.kt file:

src/main/kotlin/com/myapp/db/Models.kt:

kotlin
package com.myapp.db

import java.util.UUID
import java.time.LocalDateTime

data class Users(
    val id: UUID?,
    val name: String?,
    val email: String?,
    val createdAt: LocalDateTime?,
    val updatedAt: LocalDateTime?
)

enum class UserRole {
    ADMIN,
    USER,
    MODERATOR
}

Per-Query-File Query Classes ​

Each .sql query file generates a dedicated query class:

src/main/kotlin/com/myapp/db/Users.kt:

kotlin
package com.myapp.db

import java.sql.Connection
import java.sql.PreparedStatement

class UsersQueries(private val conn: Connection) {
    private val stmts = mutableMapOf<String, PreparedStatement>()

    fun close() { stmts.values.forEach { it.close() }; stmts.clear() }

    fun getUser(id: Int): Users? {
        val sql = """SELECT id, name, email FROM users WHERE id = ?"""
        val stmt = stmts.getOrPut("getUser") { conn.prepareStatement(sql) }
        stmt.setInt(1, id)
        stmt.executeQuery().use { rs ->
            return if (rs.next()) Users(rs.getObject("id", java.util.UUID::class.java), rs.getString("name"), rs.getString("email"), rs.getTimestamp("created_at")?.toLocalDateTime(), rs.getTimestamp("updated_at")?.toLocalDateTime()) else null
        }
    }
}

Unified Query Interface ​

src/main/kotlin/com/myapp/db/Queries.kt:

kotlin
package com.myapp.db

import java.sql.Connection

class Queries(private val conn: Connection) {
    private val users = UsersQueries(conn)

    companion object {
        fun newq(conn: Connection): Queries = Queries(conn)
    }

    fun getUser(id: Int): Users? = users.getUser(id)

    fun close() { users.close() }
}

Driver Options ​

JDBC (Default) ​

Default driver for standard JVM database access. Uses java.sql.Connection and PreparedStatement with statement caching.

Best for: Most applications, Spring Boot with JDBC, raw JDBC.

toml
[gen.kotlin]
enabled = true
driver = "jdbc"

Exposed ​

Generates code that uses JetBrains Exposed ORM's Database object and exec() method.

Best for: Projects already using Exposed ORM.

toml
[gen.kotlin]
enabled = true
driver = "exposed"

Generated code wraps queries in a transaction:

kotlin
import org.jetbrains.exposed.sql.Database

class UsersQueries(private val db: Database) {
    fun getUser(id: Int): Users? = transaction(db) {
        exec(sql, args = listOf(id)) { rs ->
            if (rs.next()) Users(/* ... */) else null
        }
    }
}

R2DBC ​

Generates reactive code compatible with R2DBC Connection.

Best for: Reactive/WebFlux applications.

toml
[gen.kotlin]
enabled = true
driver = "r2dbc"

Generated code uses createStatement() with bind parameters:

kotlin
import io.r2dbc.spi.Connection

class UsersQueries(private val conn: Connection) {
    fun getUser(id: Int): Users? {
        val stmt = conn.createStatement(sql)
        stmt.bind(0, id)
        // Subscribe to stmt.execute() with reactor/coroutine bridge
        return null // Replace with reactive mapping
    }
}

Note: R2DBC generates a blocking stub that you should bridge to your reactive framework (Project Reactor or Kotlin coroutines).

Working with JDBC ​

Using HikariCP Connection Pool ​

kotlin
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import com.myapp.db.Queries

fun main() {
    val config = HikariConfig().apply {
        jdbcUrl = System.getenv("DATABASE_URL")
    }
    val ds = HikariDataSource(config)

    ds.connection.use { conn ->
        val db = Queries.newq(conn)
        val user = db.getUser(42)
        println(user)
    }
}

Spring Boot Integration ​

kotlin
import org.springframework.stereotype.Repository
import com.myapp.db.Queries
import javax.sql.DataSource

@Repository
class UserRepository(private val ds: DataSource) {
    fun findById(id: Int): Users? =
        ds.connection.use { conn ->
            Queries.newq(conn).getUser(id)
        }
}

Working with Exposed ​

kotlin
import org.jetbrains.exposed.sql.Database
import com.myapp.db.Queries

fun main() {
    val db = Database.connect(System.getenv("DATABASE_URL"))
    val queries = Queries.newq(db)
    val user = queries.getUser(42)
    println(user)
}

Working with R2DBC ​

kotlin
import io.r2dbc.spi.ConnectionFactories
import com.myapp.db.Queries

fun main() {
    val connectionFactory = ConnectionFactories.get(System.getenv("DATABASE_URL"))
    val mono = MonoSingle(connectionFactory.create()) { conn ->
        val queries = Queries.newq(conn)
        queries.getUser(42)
    }
}

ScyllaDB / Cassandra Support ​

For ScyllaDB or Cassandra (CQL), the Kotlin generator produces code using the DataStax Java Driver (CqlSession).

Configuration ​

toml
[database]
provider = "scylla"
url_env = "DATABASE_URL"

[gen.kotlin]
enabled = true
out = "src/main/kotlin/com/myapp/db"

Generated CQL Model ​

kotlin
package com.myapp.db

import java.util.UUID
import java.time.LocalDateTime

data class Messages(
    val channelId: UUID?,
    val id: UUID?,
    val authorId: UUID?,
    val content: String?,
    val type: Int?,
    val mentionUserIds: Set<UUID>?,
    val createdAt: LocalDateTime?
)

Usage with CqlSession ​

kotlin
import com.datastax.oss.driver.api.core.CqlSession
import com.myapp.db.Queries

fun main() {
    CqlSession.builder().build().use { session ->
        val db = Queries.newq(session)
        val message = db.getMessage(channelId, messageId)
        println(message)
    }
}

Schema Definition ​

Define your database schema in db/schema/schema.sql:

sql
CREATE TABLE users (
    id UUID PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    role user_role NOT NULL DEFAULT 'user',
    metadata JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TYPE user_role AS ENUM ('admin', 'user', 'moderator');

Writing Queries ​

Write parameterized queries in .sql files using annotations:

db/queries/users.sql:

sql
-- name: GetUser :one
SELECT * FROM users WHERE id = ?;

-- name: ListUsers :many
SELECT id, name, email FROM users ORDER BY name;

-- name: CreateUser :exec
INSERT INTO users (id, name, email, role) VALUES (?, ?, ?, ?);

-- name: CountUsersByRole :one
SELECT COUNT(*) FROM users WHERE role = ?;

Annotations:

  • :one — Returns a single row (or null)
  • :many — Returns a list of rows
  • :exec — Returns affected row count

Building and Running ​

Gradle Kotlin DSL ​

kotlin
dependencies {
    implementation("com.zaxxer:HikariCP:5.1.0")
    implementation("org.jetbrains.exposed:exposed-core:0.44.1") // for Exposed driver
    implementation("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")          // for R2DBC driver
}

Maven ​

xml
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.1.0</version>
</dependency>

Run ​

bash
# Generate code
flash generate

# Build with Gradle
gradle build

# Build with Maven
mvn compile

Best Practices ​

  1. Use nullable types — Generated code uses Type? for all fields since database values may be null
  2. Place generated code in your source tree — Set out = "src/main/kotlin/com/myapp/db" for Gradle/Maven
  3. Use package config — Always set the package explicitly to match your project structure
  4. Use HikariCP for JDBC — Connection pooling is essential for production
  5. Kotlin Coroutines with R2DBC — Bridge generated stubs to kotlinx.coroutines for reactive flows
  6. Close resources — Use .use {} blocks or close() on your Queries instance
  7. Run flash generate after schema changes — Regenerate whenever you modify your schema or queries

Released under the MIT License.