Exposed 1.0.0-rc-3 Help

Get started with Exposed's DAO API

In this tutorial, you’ll learn how to use Exposed’s Data Access Object (DAO) API to store and retrieve data in a relational database by building a simple console application.

By the end of this tutorial, you’ll be able to do the following:

  • Configure a database connection using an in-memory database.

  • Define database tables and corresponding DAO entities.

  • Perform basic CRUD (Create, Read, Update, and Delete) operations using object-oriented style.

Prerequisites

Before starting this tutorial, ensure that you have the following installed on your machine:

Create a new project

First, you will need a basic Kotlin project setup to build upon. You can download a pre-initialized project or follow the steps below to generate a new project with Gradle.

  1. In a terminal window, navigate to the destination where you want to create your project and run the following commands to create a new folder and change directory into it:

    mkdir exposed-dao-kotlin-app cd exposed-dao-kotlin-app
  2. Run the gradle init task to initialize a new Gradle project:

    gradle init

    When prompted, select the following options:

    • 1: Application project type.

    • 2: Kotlin implementation language.

    For the other questions, press enter to use the default values. The output will look like the following:

    Select type of build to generate: 1: Application 2: Library 3: Gradle plugin 4: Basic (build structure only) Enter selection (default: Application) [1..4] Select implementation language: 1: Java 2: Kotlin 3: Groovy 4: Scala 5: C++ 6: Swift Enter selection (default: Java) [1..6] 2 Enter target Java version (min: 7, default: 21): Project name (default: exposed-kotlin-app): Select application structure: 1: Single application project 2: Application and library project Enter selection (default: Single application project) [1..2] Select build script DSL: 1: Kotlin 2: Groovy Enter selection (default: Kotlin) [1..2] Select test framework: 1: kotlin.test 2: JUnit Jupiter Enter selection (default: kotlin.test) [1..2] Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] > Task :init To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.8/samples/sample_building_kotlin_applications.html BUILD SUCCESSFUL in 28s 1 actionable task: 1 executed
  3. Once the project has been initialized, open the project folder in your IDE. To open the project in IntelliJ IDEA, use the following command:

    idea .

Add dependencies

Before you start using Exposed, you need to add dependencies to your project.

  1. Navigate to the gradle/libs.versions.toml file and define the Exposed and H2 versions and artifacts:

    [versions] //... exposed = "1.0.0-rc-3" h2 = "2.2.224" [libraries] //... exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" } exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" } exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" } h2 = { module = "com.h2database:h2", version.ref = "h2" }
    • The exposed-core module provides the foundational components and abstractions needed to work with databases in a type-safe manner and includes the DSL API.

    • The exposed-dao module allows you to work with the Data Access Object (DAO) API.

    • The exposed-jdbc module is an extension of the exposed-core module that adds support for Java Database Connectivity (JDBC).

  2. Navigate to the app/build.gradle.kts file and add the Exposed and H2 database modules into the dependencies block:

    dependencies { //... implementation(libs.exposed.core) implementation(libs.exposed.dao) implementation(libs.exposed.jdbc) implementation(libs.h2) //... }
  3. In intelliJ IDEA, click on the notification Gradle icon (intelliJ IDEA gradle icon) on the right side of the editor to load Gradle changes.

Configure a database connection

Whenever you access a database using Exposed, you start by obtaining a connection and creating a transaction. To configure the database connection, use the Database.connect() function.

  1. Navigate to app/src/main/kotlin/org/example/ and open the App.kt file.

  2. Replace the contents of the App.kt file with the following implementation:

    package org.example import org.jetbrains.exposed.v1.jdbc.Database fun main() { Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") }

    The Database.connect() function creates an instance of a class that represents the database and takes two or more parameters. In this case, the connection URL and the driver.

    • jdbc:h2:mem:test is the database URL to connect to:

      • jdbc specifies that this is a JDBC connection.

      • h2 indicates that the database is an H2 database.

      • mem specifies that the database is in-memory, meaning the data will only exist in memory and will be lost when the application stops.

      • test is the name of the database.

    • org.h2.Driver specifies the H2 JDBC driver to be used for establishing the connection.

With this, you've added Exposed to your Kotlin project and configured a database connection. You're now ready to define your data model and engage with the database using Exposed's DAO API.

Define a table object

Exposed's DAO API provides the base IdTable class and its subclasses to define tables that use a standard id column as the primary key. To define the table object, follow the steps below.

  1. In the app/src/main/kotlin/org/example/ folder, create a new Task.kt file.

  2. Open Task.kt and add the following table definition:

    package org.example import org.jetbrains.exposed.v1.core.dao.id.IntIdTable object Tasks : IntIdTable("tasks") { val title = varchar("name", 128) val description = varchar("description", 128) val isCompleted = bool("completed").default(false) }

    In the IntIdTable constructor, passing the name tasks configures a custom name for the table. If you don't provide a name, Exposed will derive it from the object name, which may lead to unexpected results depending on naming conventions.

    The Tasks object defines the following columns:

    • title and description are String columns, created using the varchar() function. Each column has a maximum length of 128 characters.

    • isCompleted is a Boolean column, defined using the bool() function. Using the default(false) call, you configure the default value to false.

    The IntIdTable class automatically adds an auto-incrementing integer id column as the primary key for the table. At this point, you have defined a table with columns, which essentially creates the blueprint for the tasks table.

Define an entity

When using the DAO approach, each table defined using IntIdTable must be associated with a corresponding entity class. The entity class represents individual records in the table and is uniquely identified by a primary key.

To define the entity, update your Task.kt file with the following code:

// ... import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntityClass // ... class Task(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<Task>(Tasks) var title by Tasks.title var description by Tasks.description var isCompleted by Tasks.isCompleted override fun toString(): String { return "Task(id=$id, title=$title, completed=$isCompleted)" } }
  • Task extends IntEntity, which is a base class for entities with an Int-based primary key.

  • The EntityID<Int> parameter represents the primary key of the database row this entity maps to.

  • The companion object extends IntEntityClass<Task>, linking the entity class to the Tasks table.

  • Each property (title, description, and isCompleted) is delegated to its corresponding column in the Tasks table using Kotlin's by keyword.

  • The toString() function customizes how a Task instance is represented as a string. This is especially useful for debugging or logging. When printed, the output will include the entity’s ID, title, and completion status.

Create and query a table

With Exposed’s DAO API, you can interact with your database using a type-safe, object-oriented syntax similar to working with regular Kotlin classes. When executing any database operations, you must run them inside a transaction.

A transaction is represented by an instance of the Transaction class, within which you can define and manipulate data using its lambda function. Exposed will automatically manage the opening and closing of the transaction in the background, ensuring seamless operation.

Open your App.kt file and add the following transaction function:

package org.example import org.jetbrains.exposed.v1.jdbc.Database import org.jetbrains.exposed.v1.jdbc.SchemaUtils import org.jetbrains.exposed.v1.jdbc.transactions.transaction fun main() { Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") transaction { SchemaUtils.create(Tasks) val task1 = Task.new { title = "Learn Exposed DAO" description = "Follow the DAO tutorial" } val task2 = Task.new { title = "Read The Hobbit" description = "Read chapter one" isCompleted = true } println("Created new tasks with ids ${task1.id} and ${task2.id}") val completed = Task.find { Tasks.isCompleted eq true }.toList() println("Completed tasks: ${completed.count()}") } }

First, you create the tasks table using the SchemaUtils.create() method. The SchemaUtils object holds utility methods for creating, altering, and dropping database objects.

Once the table has been created, you use the IntEntityClass extension method .new() to add two new Task records:

val task1 = Task.new { title = "Learn Exposed DAO" description = "Follow the DAO tutorial" } val task2 = Task.new { title = "Read The Hobbit" description = "Read chapter one" isCompleted = true }

In this example, task1 and task2 are instances of the Task entity, each representing a new row in the Tasks table. Within the new block, you set the values for each column. Exposed will translate the functions into the following SQL queries:

INSERT INTO TASKS ("name", DESCRIPTION, COMPLETED) VALUES ('Learn Exposed DAO', 'Follow the DAO tutorial', FALSE) INSERT INTO TASKS ("name", DESCRIPTION, COMPLETED) VALUES ('Read The Hobbit', 'Read chapter one', TRUE)

With the .find() method you then perform a filtered query, retrieving all tasks where isCompleted is true:

val completed = Task.find { Tasks.isCompleted eq true }.toList()

Before you test the code, it would be handy to be able to inspect the SQL statements and queries Exposed sends to the database. For this, you need to add a logger.

Enable logging

At the beginning of your transaction block, add the following to enable SQL query logging:

import org.jetbrains.exposed.v1.core.StdOutSqlLogger fun main() { transaction { addLogger(StdOutSqlLogger) // ... } }

Run the application

In IntelliJ IDEA, click on the run button (intelliJ IDEA run icon) to start the application.

The application will start in the Run tool window at the bottom of the IDE. There you will be able to see the SQL logs along with the printed results:

SQL: SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'MODE' SQL: CREATE TABLE IF NOT EXISTS TASKS (ID INT AUTO_INCREMENT PRIMARY KEY, "name" VARCHAR(128) NOT NULL, DESCRIPTION VARCHAR(128) NOT NULL, COMPLETED BOOLEAN DEFAULT FALSE NOT NULL) SQL: INSERT INTO TASKS ("name", DESCRIPTION, COMPLETED) VALUES ('Learn Exposed DAO', 'Follow the DAO tutorial', FALSE) SQL: INSERT INTO TASKS ("name", DESCRIPTION, COMPLETED) VALUES ('Read The Hobbit', 'Read chapter one', TRUE) Created new tasks with ids 1 and 2 SQL: SELECT TASKS.ID, TASKS."name", TASKS.DESCRIPTION, TASKS.COMPLETED FROM TASKS WHERE TASKS.COMPLETED = TRUE Completed tasks: 1

Update and delete a task

Let’s extend the app’s functionality by updating and deleting a task.

  1. In the same transaction() function, add the following code to your implementation:

    transaction { // ... // Update task1.title = "Try Exposed DAO" task1.isCompleted = true println("Updated task1: $task1") // Delete task2.delete() println("Remaining tasks: ${Task.all().toList()}") }

    You update the value of a property just as you would with any property in a Kotlin class:

    task1.title = "Try Exposed DAO" task1.isCompleted = true

    Similarly, to delete a task, you use the .delete() method on the entity:

    task2.delete()
  2. In IntelliJ IDEA, click the rerun button (intelliJ IDEA rerun icon) to restart the application.

    You should now see the following result:

    SQL: SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'MODE' SQL: CREATE TABLE IF NOT EXISTS TASKS (ID INT AUTO_INCREMENT PRIMARY KEY, "name" VARCHAR(128) NOT NULL, DESCRIPTION VARCHAR(128) NOT NULL, COMPLETED BOOLEAN DEFAULT FALSE NOT NULL) SQL: INSERT INTO TASKS ("name", DESCRIPTION, COMPLETED) VALUES ('Learn Exposed DAO', 'Follow the DAO tutorial', FALSE) SQL: INSERT INTO TASKS ("name", DESCRIPTION, COMPLETED) VALUES ('Read The Hobbit', 'Read chapter one', TRUE) Created new tasks with ids 1 and 2 SQL: SELECT TASKS.ID, TASKS."name", TASKS.DESCRIPTION, TASKS.COMPLETED FROM TASKS WHERE TASKS.COMPLETED = TRUE Completed tasks: 1 Updated task1: Task(id=1, title=Try Exposed DAO, completed=true) SQL: UPDATE TASKS SET COMPLETED=TRUE, "name"='Try Exposed DAO' WHERE ID = 1 SQL: DELETE FROM TASKS WHERE TASKS.ID = 2 SQL: SELECT TASKS.ID, TASKS."name", TASKS.DESCRIPTION, TASKS.COMPLETED FROM TASKS Remaining tasks: [Task(id=1, title=Try Exposed DAO, completed=true)]

Next steps

Great job! You've built a simple console application using Exposed's DAO API to create, query, and manipulate task data in an in-memory database.

Now that you've covered the fundamentals, you're ready to dive deeper into what the DAO API offers. Continue exploring CRUD operations or learn how to define relationships between entities. These next chapters will help you build more complex, real-world data models using Exposed’s type-safe, object-oriented approach.

Last modified: 27 November 2025