I have a small test table in Postgres with a DATE field, and data class in Ktor app. When mapping the database table row result to TestItem the 'updated' LocalDate field generated the error.
import DatabaseFactory.dbQuery
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.plugins.cors.routing.CORS
import io.ktor.server.plugins.defaultheaders.DefaultHeaders
import io.ktor.server.response.respond
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import kotlinx.datetime.LocalDate
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.core.Column
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.StdOutSqlLogger
import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.datetime.date
import org.jetbrains.exposed.v1.jdbc.Database
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
object DatabaseFactory{
fun init() {
val driver = "org.postgresql.Driver"
val url = "jdbc:postgresql://192.168.0.XXX:5432/mydb"
val user = "postgres"
val password = "postgres"
val db = Database.connect(url, driver, user = user, password = password)
transaction {
addLogger(StdOutSqlLogger)
}
}
fun <T> dbQuery(block: () -> T): T =
transaction { block() }
}
object TestTable: Table (name="test") {
val id: Column<Int> = integer("id")
val updated: Column<LocalDate> = date("updated")
}
@Serializable
data class TestItem(
val id: Int,
val updated: LocalDate
)
interface DAOFacade {
suspend fun getId(id: Int): TestItem?
}
class DAOFacadeImpl : DAOFacade {
private fun resultRowToTestId(row: ResultRow): TestItem = TestItem(
id = row[TestTable.id],
updated = row[TestTable.updated]
)
override suspend fun getId(id: Int): TestItem? {
val foundId = dbQuery {
TestTable
.selectAll()
.where { TestTable.id eq id }
.map(::resultRowToTestId)
.firstOrNull()
}
return foundId
}
}
val dao: DAOFacade = DAOFacadeImpl()
fun main() {
embeddedServer(Netty, host = "0.0.0.0", port = 8080, module = Application::module)
.start(wait = true)
}
fun Application.module() {
configureSerialization()
DatabaseFactory.init()
configureRouting()
configureHTTP()
}
fun Application.configureSerialization() {
install(ContentNegotiation) {
json()
}
}
fun Application.configureRouting() {
routing {
route("/") {
get("/{id}") {
val requestId = call.parameters["id"] ?: ""
val idFound: TestItem? = dao.getId(requestId.toInt())
if (idFound == null) {
call.respond(HttpStatusCode.BadRequest, message = "Not Found")
} else {
call.respond(HttpStatusCode.OK, message = idFound)
}
}
}
}
}
fun Application.configureHTTP() {
install(DefaultHeaders) {
header("X-Engine", "Ktor") // will send this header with each response
}
install(CORS) {
allowMethod(HttpMethod.Options)
allowHeader(HttpHeaders.Authorization)
allowHeader("MyCustomHeader")
anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
}
}
My build.gradle.kts:
plugins {
kotlin("jvm") version "2.2.0"
kotlin("plugin.serialization") version "2.2.20-Beta1"
id("io.ktor.plugin") version "3.2.2"
application
}
application {
mainClass.set("MainKt")
}
group = "net.karpenkov"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
dependencies {
implementation("org.slf4j:slf4j-api:2.0.3")
implementation("org.jetbrains.exposed:exposed-core:1.0.0-beta-4")
implementation("org.jetbrains.exposed:exposed-dao:1.0.0-beta-4")
implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0-beta-4")
implementation("org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0-beta-4")
implementation("io.ktor:ktor-server-core-jvm:3.2.3")
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:3.2.3")
implementation("io.ktor:ktor-server-content-negotiation-jvm:3.2.3")
implementation("org.postgresql:postgresql:42.5.1")
implementation("io.ktor:ktor-server-cors-jvm:3.2.3")
implementation("io.ktor:ktor-server-default-headers:3.2.3")
implementation("io.ktor:ktor-server-host-common-jvm:3.2.3")
implementation("io.ktor:ktor-server-auth-jvm:3.2.3")
implementation("io.ktor:ktor-server-netty-jvm:3.2.3")
implementation("io.ktor:ktor-server-auth:3.2.3")
implementation("io.ktor:ktor-server-sessions:3.2.3")
implementation("io.ktor:ktor-server-freemarker:3.2.3")
implementation("ch.qos.logback:logback-classic:1.4.11")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1")
implementation("io.ktor:ktor-server-status-pages:2.1.2")
implementation("com.typesafe:config:1.4.2")
}
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(24)
}
ktor {
development = true
fatJar {
archiveFileName.set("testLocaleDate.jar")
}
}
Run .jdks/azul-24.0.1/bin/java from Idea IDE
Error (from ./gradlew run):
Caused by: java.lang.ClassNotFoundException: kotlinx.datetime.Instant
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
Caused by: java.lang.ClassNotFoundException: kotlinx.datetime.Instant
java.lang.NoClassDefFoundError: kotlinx/datetime/Instant
at org.jetbrains.exposed.v1.datetime.KotlinLocalDateColumnType.longToLocalDate(KotlinDateColumnType.kt:229)
at org.jetbrains.exposed.v1.datetime.KotlinLocalDateColumnType.valueFromDB(KotlinDateColumnType.kt:202)
at org.jetbrains.exposed.v1.datetime.KotlinLocalDateColumnType.valueFromDB(KotlinDateColumnType.kt:187)
at org.jetbrains.exposed.v1.core.ResultRow.rawToColumnValue(ResultRow.kt:99)
at org.jetbrains.exposed.v1.core.ResultRow.getInternal$lambda$5$lambda$4$lambda$3(ResultRow.kt:87)
at org.jetbrains.exposed.v1.core.vendors.DatabaseDialectKt.withDialect(DatabaseDialect.kt:152)
at org.jetbrains.exposed.v1.core.ResultRow.getInternal$lambda$5(ResultRow.kt:86)
at org.jetbrains.exposed.v1.core.ResultRow$ResultRowCache.cached(ResultRow.kt:218)
I can't find any documentation to troubleshoot this simple code. What might be a problem?
The latest 0.7.1 kotlinx-datetime deprecated Instant so the code fails in the runtime. The easiest solution is to switch to 0.7.1-0.6.x-compat version of the same library.
From the README:
Deprecation of
Instant
kotlinx-datetimeversions earlier than0.7.0used to providekotlinx.datetime.Instantandkotlinx.datetime.Clock. The Kotlin standard library started including its own, identicalkotlin.time.Instantandkotlin.time.Clock, as it became evident thatInstantwas also useful outside the datetime contexts.Here is the recommended procedure for migrating from
kotlinx-datetimeversion0.6.xor earlier to0.7.x:
First, simply try upgrading to
0.7.1. If your project has a dependency onkotlinx-datetime, but doesn't have dependencies on other libraries that are themselves reliant on an olderkotlinx-datetime, you are good to go: the code should compile and run. This applies both to applications and to libraries!If your project depends on other libraries that themselves use an older version of
kotlinx-datetime, then your code may fail at runtime with aClassNotFoundExceptionforkotlinx.datetime.Instantorkotlinx.datetime.Clock, or maybe even fail to compile. In that case, please check if the affected libraries you have as dependencies have already published a new release adapted to useInstantandClockfromkotlin.time.If you use
kotlinx-serializationto serialize theInstanttype, update that dependency to use 1.9.0 or a newer version.If all else fails, use the compatibility release of
kotlinx-datetime. Instead of the version0.7.1, use0.7.1-0.6.x-compat. This artifact still containskotlinx.datetime.Instantandkotlinx.datetime.Clock, ensuring that third-party libraries reliant on them can still be used. This artifact is less straightforward to use than0.7.1, so only resort to it when libraries you don't control require that the removed classes still exist.[...]
As I mentioned above, the 4th point was the solution in my case.