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:
The latest Gradle distribution.
A Java Development Kit (JDK), version 8 or higher.
An integrated development environment (IDE), such as IntelliJ IDEA Ultimate.
We recommend that you install IntelliJ IDEA Ultimate which comes with built-in database tools and the Exposed plugin for code completion and inspections. However, you can use another IDE of your choice.
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.
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-appRun the
gradle inittask to initialize a new Gradle project:gradle initWhen prompted, select the following options:
1: Applicationproject type.2: Kotlinimplementation 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 executedOnce 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.
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-coremodule provides the foundational components and abstractions needed to work with databases in a type-safe manner and includes the DSL API.The
exposed-daomodule allows you to work with the Data Access Object (DAO) API.The
exposed-jdbcmodule is an extension of theexposed-coremodule that adds support for Java Database Connectivity (JDBC).
Navigate to the app/build.gradle.kts file and add the Exposed and H2 database modules into the
dependenciesblock:dependencies { //... implementation(libs.exposed.core) implementation(libs.exposed.dao) implementation(libs.exposed.jdbc) implementation(libs.h2) //... }In intelliJ IDEA, click on the notification 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.
Navigate to app/src/main/kotlin/org/example/ and open the App.kt file.
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:testis the database URL to connect to:jdbcspecifies that this is a JDBC connection.h2indicates that the database is an H2 database.memspecifies that the database is in-memory, meaning the data will only exist in memory and will be lost when the application stops.testis the name of the database.
org.h2.Driverspecifies 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.
In the app/src/main/kotlin/org/example/ folder, create a new Task.kt file.
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
IntIdTableconstructor, passing the nametasksconfigures 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
Tasksobject defines the following columns:titleanddescriptionareStringcolumns, created using thevarchar()function. Each column has a maximum length of 128 characters.isCompletedis aBooleancolumn, defined using thebool()function. Using thedefault(false)call, you configure the default value tofalse.
The
IntIdTableclass automatically adds an auto-incrementing integeridcolumn as the primary key for the table. At this point, you have defined a table with columns, which essentially creates the blueprint for thetaskstable.
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:
TaskextendsIntEntity, which is a base class for entities with anInt-based primary key.The
EntityID<Int>parameter represents the primary key of the database row this entity maps to.The
companion objectextendsIntEntityClass<Task>, linking the entity class to theTaskstable.Each property (
title,description, andisCompleted) is delegated to its corresponding column in theTaskstable using Kotlin'sbykeyword.The
toString()function customizes how aTaskinstance 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:
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:
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:
With the .find() method you then perform a filtered query, retrieving all tasks where isCompleted is true:
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:
Run the application
In IntelliJ IDEA, click on the run button () 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:
Update and delete a task
Let’s extend the app’s functionality by updating and deleting a task.
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 = trueSimilarly, to delete a task, you use the
.delete()method on the entity:task2.delete()In IntelliJ IDEA, click the rerun button (
) 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.