Exceptions after upgrading Kotlin 2.3.0 to Kotlin 2.3.20

69 views
Skip to first unread message

Julian Backes

unread,
Apr 7, 2026, 9:41:06 AMApr 7
to jooq...@googlegroups.com
Hello everyone,

We are using jOOQ Pro 3.20.11 (also tested with 3.21.1) together with Kotlin, and after upgrading Kotlin from 2.3.0 to 2.3.20 we are encountering exceptions when mapping jOOQ results to Kotlin data classes.

The simplest example would be:

data class SimpleIntTest(val id: Int)

This works without any issues in Kotlin 2.3.0, but in Kotlin 2.3.20 it throws the following exception:

org.jooq.exception.MappingException: No DefaultRecordMapper strategy applies to type class SimpleIntTest for row type (
  "public"."some_table"."id"
). Attempted strategies include (in this order):
- Is type an array (false)?
- Is type a Stream (false)?
- Does row type have only 1 column (false) and did ConverterProvider provide a Converter for type (check skipped)?
- Is type abstract (false)?
- Is type a org.jooq.Record (false)?
- Is type a mutable POJO (a POJO with setters or non-final members: false) and has a no-args constructor (check skipped)?
- Does type have a @ConstructorProperties annotated constructor (false) and is Settings.mapConstructorPropertiesParameterNames enabled (true)?
- Is type a java.lang.Record (check skipped) and is Settings.mapRecordComponentParameterNames enabled (false)?
- Is type a kotlin class (true) and is Settings.mapConstructorParameterNamesInKotlin enabled (true)?
- Is there a constructor that matches row type's degrees with nested fields (false) or flat fields (false)
- Is the type a top level class (true) or static nested class (false)?
-   (Inner classes cannot be created via reflection)
- Is Settings.mapConstructorParameterNames enabled (false)

    at org.jooq.impl.DefaultRecordMapper.init(DefaultRecordMapper.java:638)
    at org.jooq.impl.DefaultRecordMapper.<init>(DefaultRecordMapper.java:369)
    at org.jooq.impl.DefaultRecordMapper.<init>(DefaultRecordMapper.java:359)
    at org.jooq.impl.DefaultRecordMapperProvider.lambda$provide$0(DefaultRecordMapperProvider.java:77)
    at org.jooq.impl.Cache.run(Cache.java:124)
    at org.jooq.impl.DefaultRecordMapperProvider.provide(DefaultRecordMapperProvider.java:77)
    at org.jooq.impl.FieldsImpl.mapper(FieldsImpl.java:208)
    at org.jooq.impl.ResultQueryTrait.lambda$mapper$24(ResultQueryTrait.java:1567)
    at org.jooq.impl.DelayedRecordMapper.map(DelayedRecordMapper.java:67)
    at org.jooq.RecordMapper.apply(RecordMapper.java:87)
    at org.jooq.RecordMapper.apply(RecordMapper.java:72)
    at java.base/java.util.stream.Collectors.lambda$mapping$13(Collectors.java:432)
    at org.jooq.impl.Tools.collect(Tools.java:6913)
    at org.jooq.impl.AbstractCursor.collect(AbstractCursor.java:78)
    at org.jooq.impl.ResultQueryTrait.collect(ResultQueryTrait.java:360)
    at org.jooq.impl.ResultQueryTrait.fetchInto(ResultQueryTrait.java:1433)
    at (business code)

The query itself is very simple:

ctx.select(SOMETABLE.ID).from(SOMETABLE).fetchInto(SimpleIntTest::class.java)

I tried to debug it a bit, but I’m not sure whether this is actually a jOOQ issue or if something has changed in Kotlin.

As I understand it, jOOQ in DefaultRecordMapper.java tries to find the appropriate constructor of the data class. For that, jOOQ uses Kotlin Reflect. In Kotlin 2.3.0, Kotlin Reflect returns int as the Java type for the parameter, but in Kotlin 2.3.20 it returns Integer. Then jOOQ tries to invoke the constructor using this Integer, which fails because val id: Int "is compiled to" int id not Integer id.

I also didn’t immediately find anything in the Kotlin issue tracker (though I may have been searching incorrectly). Does anyone have an idea what might be going on here?

Best regards,
Julian

Lukas Eder

unread,
Apr 20, 2026, 5:29:31 AM (10 days ago) Apr 20
to jooq...@googlegroups.com
Hi Julian,

Thanks for your patience. I've tried reproducing your issue without success using our MCVE template:

Can you please provide a complete reproducer for this issue?

Best Regards,
Lukas

--
You received this message because you are subscribed to the Google Groups "jOOQ User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jooq-user+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/jooq-user/CAPv0rXGQb%3D-vMcn6KuSisUbXC3jjeUAfH%3D4E8jgJ6NvLO-FWLA%40mail.gmail.com.

Lukas Eder

unread,
Apr 20, 2026, 5:30:32 AM (10 days ago) Apr 20
to jooq...@googlegroups.com
For the record, this is what I tried:

$ git diff
diff --git a/jOOQ-mcve-kotlin-h2/src/test/kotlin/org/jooq/mcve/test/kotlin/h2/KotlinTest.kt b/jOOQ-mcve-kotlin-h2/src/test/kotlin/org/jooq/mcve/test/kotlin/h2/KotlinTest.kt
index 5309711..979b442 100644
--- a/jOOQ-mcve-kotlin-h2/src/test/kotlin/org/jooq/mcve/test/kotlin/h2/KotlinTest.kt
+++ b/jOOQ-mcve-kotlin-h2/src/test/kotlin/org/jooq/mcve/test/kotlin/h2/KotlinTest.kt
@@ -42,5 +42,8 @@ class KotlinTest {

         val record = ctx().fetchOne(TEST, TEST.CD.eq(42))
         assertNotNull(record?.id)
+
+        data class SimpleIntTest(val id: Int)
+        println(ctx().select(TEST.ID).from(TEST).fetchInto(SimpleIntTest::class.java))
     }
 }

This prints the expected output:

11:28:53,232 DEBUG [org.jooq.tools.LoggerListener                     ] - Executing query          : select "MCVE"."TEST"."ID" from "MCVE"."TEST"
11:28:53,251 DEBUG [org.jooq.tools.LoggerListener                     ] - Fetched result           : +----+
11:28:53,251 DEBUG [org.jooq.tools.LoggerListener                     ] -                          : |  ID|
11:28:53,251 DEBUG [org.jooq.tools.LoggerListener                     ] -                          : +----+
11:28:53,251 DEBUG [org.jooq.tools.LoggerListener                     ] -                          : |   1|
11:28:53,251 DEBUG [org.jooq.tools.LoggerListener                     ] -                          : +----+
11:28:53,253 DEBUG [org.jooq.tools.LoggerListener                     ] - Fetched row(s)           : 1
[SimpleIntTest(id=1)]

julian...@gmail.com

unread,
Apr 20, 2026, 4:17:35 PM (9 days ago) Apr 20
to jOOQ User Group

Hi Lukas,

Thank you very much for your reply!! I think I owe you an apology. As usual, I oversimplified my example too much at the last minute—specifically the query: you need to load more than one column from the database. So either select(TEST.ID, TEST.CD)... or selectFrom(TEST)....

I assume there is some kind of shortcut when loading only a single column? With more than one column, the data class of course doesn’t match 100%, but that is exactly our case as well. In this situation, jOOQ even indicates that you should include kotlin-reflect, so that is indeed required.

I was then also able to reproduce the exception in the MCVE, specifically with Kotlin 2.3.20, but not with Kotlin 2.3.0. Here is a (complete) diff—don’t forget to update the Kotlin version in two places (plugin and reflect) when testing :-) I hope this works now! ;-)

Best regards,
Julian

diff --git a/jOOQ-mcve-kotlin-h2/build.gradle.kts b/jOOQ-mcve-kotlin-h2/build.gradle.kts
index 61ec3b1..c48daf8 100644
--- a/jOOQ-mcve-kotlin-h2/build.gradle.kts
+++ b/jOOQ-mcve-kotlin-h2/build.gradle.kts
@@ -2,18 +2,19 @@ import org.jooq.impl.DSL
 import org.jooq.meta.jaxb.Logging
 
 plugins {
-    kotlin("jvm") version "1.9.22"
+    kotlin("jvm") version "2.3.20"
     id("org.jooq.jooq-codegen-gradle") version "3.21.2"
 }
 
 // TODO: Change the database driver configuration here and also in AbstractTest for the tests
-val dbUrl = "jdbc:h2:~/jooq-mcve-java-2"
+val dbUrl = "jdbc:h2:~/jooq-mcve-kotlin-2"
 val dbUsername = "sa"
 val dbPassword = ""
 
 dependencies {
     jooqCodegen("com.h2database:h2:2.2.224")
     implementation("${group}:jooq:${version}")
+    implementation("org.jetbrains.kotlin:kotlin-reflect:2.3.20")
     implementation("com.h2database:h2:2.2.224")
     testImplementation("junit:junit:4.13.2")
 }
@@ -76,4 +77,4 @@ tasks.named("compileKotlin") {
 
 tasks.named("jooqCodegen") {
     dependsOn(tasks.named("init"))
-}
\ No newline at end of file
+}
diff --git a/jOOQ-mcve-kotlin-h2/src/test/kotlin/org/jooq/mcve/test/kotlin/h2/KotlinTest.kt b/jOOQ-mcve-kotlin-h2/src/test/kotlin/org/jooq/mcve/test/kotlin/h2/KotlinTest.kt
index 5309711..2dd1181 100644
--- a/jOOQ-mcve-kotlin-h2/src/test/kotlin/org/jooq/mcve/test/kotlin/h2/KotlinTest.kt
+++ b/jOOQ-mcve-kotlin-h2/src/test/kotlin/org/jooq/mcve/test/kotlin/h2/KotlinTest.kt
@@ -42,5 +42,9 @@ class KotlinTest {


 
         val record = ctx().fetchOne(TEST, TEST.CD.eq(42))
         assertNotNull(record?.id)
+

+        ctx().selectFrom(TEST).fetchInto(SimpleIntTest::class.java)
     }
-}
\ No newline at end of file
+}
+
+data class SimpleIntTest(val id: Int)

Lukas Eder

unread,
Apr 21, 2026, 4:54:44 AM (9 days ago) Apr 21
to jooq...@googlegroups.com
OK, I can reproduce the issue when adding the kotlin-reflect library. I've created an issue for this:

Will investigate right away

Thanks,
Lukas

Lukas Eder

unread,
Apr 21, 2026, 5:26:31 AM (9 days ago) Apr 21
to jooq...@googlegroups.com
OK, the finding is simple. The jooq-kotlin library still depends on Kotlin 1.9. The reflection magic then struggles with the two versions of kotlin-reflect being on the classpath of the MCVE template. These reflection exceptions aren't currently logged, because they're usually false positives (e.g. kotlin-reflect isn't on the classpath, so we shouldn't repeatedly log things), or because of bugs like this one (so, logging extensively will be noisy as well).

If you exclude jooq-kotlin's transitive dependencies, then things should work.

I guess the solution here is to offer formal support for Kotlin 2.x via separate libraries, similar to how we support multiple Scala versions:

Best Regards,
Lukas

Julian Backes

unread,
Apr 21, 2026, 6:08:37 AM (9 days ago) Apr 21
to jooq...@googlegroups.com
Hi Lukas,

I'm not 100% sure if I can follow you. I was using the Gradle version of jOOQ-mcve-kotlin-h2 and I don't see any dependency to jooq-kotlin or to kotlin-reflect:1.9.25. I can see a dependency to jooq-kotlin in the Maven version which I was not using so I'm not sure if it is really a problem with the duplicate jars or if there is a different (or one more) problem. Here are the relevant snippets of the dependencies as reported by Gradle:

./gradlew :jOOQ-mcve-kotlin-h2:dependencies

compileClasspath - Compile classpath for 'main'.
+--- org.jetbrains.kotlin:kotlin-stdlib:2.3.20
|    \--- org.jetbrains:annotations:13.0
+--- org.jooq:jooq:3.21.2
|    \--- io.r2dbc:r2dbc-spi:1.0.0.RELEASE
|         \--- org.reactivestreams:reactive-streams:1.0.3
+--- org.jetbrains.kotlin:kotlin-reflect:2.3.20
|    \--- org.jetbrains.kotlin:kotlin-stdlib:2.3.20 (*)
\--- com.h2database:h2:2.2.224

runtimeClasspath - Runtime classpath of 'main'.
+--- org.jetbrains.kotlin:kotlin-stdlib:2.3.20
|    \--- org.jetbrains:annotations:13.0
+--- org.jooq:jooq:3.21.2
|    \--- io.r2dbc:r2dbc-spi:1.0.0.RELEASE
|         \--- org.reactivestreams:reactive-streams:1.0.3
+--- org.jetbrains.kotlin:kotlin-reflect:2.3.20
|    \--- org.jetbrains.kotlin:kotlin-stdlib:2.3.20 (*)
\--- com.h2database:h2:2.2.224

testCompileClasspath - Compile classpath for 'test'.
+--- org.jetbrains.kotlin:kotlin-stdlib:2.3.20
|    \--- org.jetbrains:annotations:13.0
+--- org.jooq:jooq:3.21.2
|    \--- io.r2dbc:r2dbc-spi:1.0.0.RELEASE
|         \--- org.reactivestreams:reactive-streams:1.0.3
+--- org.jetbrains.kotlin:kotlin-reflect:2.3.20
|    \--- org.jetbrains.kotlin:kotlin-stdlib:2.3.20 (*)
+--- com.h2database:h2:2.2.224
\--- junit:junit:4.13.2
     \--- org.hamcrest:hamcrest-core:1.3

testRuntimeClasspath - Runtime classpath of 'test'.
+--- org.jetbrains.kotlin:kotlin-stdlib:2.3.20
|    \--- org.jetbrains:annotations:13.0
+--- org.jooq:jooq:3.21.2
|    \--- io.r2dbc:r2dbc-spi:1.0.0.RELEASE
|         \--- org.reactivestreams:reactive-streams:1.0.3
+--- org.jetbrains.kotlin:kotlin-reflect:2.3.20
|    \--- org.jetbrains.kotlin:kotlin-stdlib:2.3.20 (*)
+--- com.h2database:h2:2.2.224
\--- junit:junit:4.13.2
     \--- org.hamcrest:hamcrest-core:1.3

Best Regards,
Julian

You received this message because you are subscribed to a topic in the Google Groups "jOOQ User Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/jooq-user/gU3fbSNrk_s/unsubscribe.
To unsubscribe from this group and all its topics, send an email to jooq-user+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/jooq-user/CAB4ELO6_Ae4fj%3Da_0pfchBwgsBudP8DRof0oNwMNhzxthc_KGg%40mail.gmail.com.

Lukas Eder

unread,
Apr 21, 2026, 10:35:12 AM (8 days ago) Apr 21
to jooq...@googlegroups.com
OK, I see, I was chasing a Maven specific issue of the MCVE template. Once that was addressed, the same issue appears as in the gradle build, which I had already worked around by specifying:

data class SimpleIntTest(val id: Int?)

Notice the nullable Int? type.

I've tracked this down to what we're doing internally via reflection:

println((SimpleIntTest::class.primaryConstructor!!.parameters[0].type.classifier!! as KClass<*>).java)

When you run this with kotlin-reflect 2.3.0, you're getting the correct value int
When you run this with kotlin-reflect 2.3.20, you're getting the incorrect value class java.lang.Integer

I suspect that's a regression in kotlin-reflect?


Lukas Eder

unread,
Apr 21, 2026, 10:43:14 AM (8 days ago) Apr 21
to jooq...@googlegroups.com
This example illustrates the difference:

data class C(val i: Int, val j: Int?)
println((C::class.primaryConstructor!!.parameters[0].type.classifier!! as KClass<*>).java)
println((C::class.primaryConstructor!!.parameters[1].type.classifier!! as KClass<*>).java)
println((C::class.primaryConstructor!!.parameters[0].type.javaType))
println((C::class.primaryConstructor!!.parameters[1].type.javaType))

printing:
class java.lang.Integer
class java.lang.Integer
int
class java.lang.Integer

So, perhaps we shouldn't use the classifier property in the first place.

But to be honest, rather than hacking around, patching this third party contributed reflection code one more time, perhaps it would be better to finally implement this properly using an extension, with actual Kotlin code:

Julian Backes

unread,
Apr 21, 2026, 10:56:19 AM (8 days ago) Apr 21
to jooq...@googlegroups.com

Lukas Eder

unread,
Apr 28, 2026, 7:44:42 AM (yesterday) Apr 28
to jooq...@googlegroups.com
I've reported this issue here:

It seems to affect only 2.3.20 and 2.3.21. It seems to have been fixed in 2.4.0-Beta2. Given that this is a (significant!) regression in a kotlin library, I don't want to take action in jOOQ. As a workaround, upgrade to 2.4.0-Beta2, or downgrade to 2.3.10 again

Best Regards,
Lukas

Reply all
Reply to author
Forward
0 new messages