micronaut-projects / micronaut-projects/micronaut-validation
`@AssertTrue` on methods without `is`/`get` prefix silently ignored when using `@RequestBean` + `@Introspected` data class (KSP)
- Dominant language
- Java
- Stars
- 9
- Forks
- 8
- Avg merge
- 20h 7m
- Merged PRs (30d)
- 9
Description
### Description:
When using `@RequestBean` with a Kotlin data class annotated with `@Introspected`, `@AssertTrue` constraint on a method without `is`/`get` prefix is silently ignored — the method is never called by the validator. If the method is renamed to start with `is` or `get`, KSP fails at compile time with:
```
Primary Constructor or @Creator Method for Bindable property [checkEquality] found,
but there is no constructor/method parameter with name equal to [checkEquality].
```
### Steps to reproduce:
```kotlin
package com.example
import io.micronaut.core.annotation.Introspected
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces
import io.micronaut.http.annotation.RequestBean
import io.micronaut.runtime.Micronaut.run
import jakarta.validation.Valid
import jakarta.validation.constraints.AssertTrue
import jakarta.validation.constraints.Max
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotBlank
/**
*
*/
fun main(args: Array) {
run(*args)
}
/**
*
*/
@Introspected
data class TestDto(
@field:Min(1)
@field:Max(10)
val id: Long,
@field:NotBlank
val text: String
) {
@AssertTrue
fun isCheckEquality() =
text == "$id"
}
/**
*
*/
@Controller("/test")
open class TestController {
@Get
@Produces(MediaType.TEXT_PLAIN)
open fun handle(
@Valid @RequestBean request: TestDto
): String {
println(request)
return "ok"
}
}
```
**build.gradle.kts**
```kotlin
plugins {
id("org.jetbrains.kotlin.jvm") version "2.3.20"
id("org.jetbrains.kotlin.plugin.allopen") version "2.3.20"
id("com.google.devtools.ksp") version "2.3.6"
id("io.micronaut.application") version "5.0.0-M1"
id("com.gradleup.shadow") version "9.4.1"
id("io.micronaut.aot") version "5.0.0-M1"
}
version = "0.1"
group = "com.example"
val kotlinVersion=project.properties.get("kotlinVersion")
repositories {
maven("https://central.sonatype.com/repository/maven-snapshots/") {
mavenContent { snapshotsOnly() }
}
mavenCentral()
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") {
mavenContent { snapshotsOnly() }
}
}
dependencies {
compileOnly("io.micronaut.validation:micronaut-validation")
ksp("io.micronaut.validation:micronaut-validation-processor")
ksp("io.micronaut:micronaut-http-validation")
ksp("io.micronaut.serde:micronaut-serde-processor")
implementation("io.micronaut.kotlin:micronaut-kotlin-runtime")
implementation("io.micronaut.serde:micronaut-serde-jackson")
implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
compileOnly("io.micronaut:micronaut-http-client")
runtimeOnly("ch.qos.logback:logback-classic")
runtimeOnly("tools.jackson.module:jackson-module-kotlin")
testImplementation("io.micronaut:micronaut-http-client")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
application {
mainClass = "com.example.ApplicationKt"
}
java {
sourceCompatibility = JavaVersion.toVersion("25")
}
graalvmNative.toolchainDetection = false
graalvmNative {
binaries {
all {
buildArgs.add("-H:+SharedArenaSupport")
}
}
}
micronaut {
runtime("netty")
testRuntime("junit5")
processing {
incremental(true)
annotations("com.example.*")
}
aot {
// Please review carefully the optimizations enabled below
// Check https://micronaut-projects.github.io/micronaut-aot/latest/guide/ for more details
optimizeServiceLoading = false
convertYamlToJava = false
precomputeOperations = true
cacheEnvironment = true
optimizeClassLoading = true
deduceEnvironment = true
optimizeNetty = true
replaceLogbackXml = true
}
}
tasks.named("dockerfile") {
baseImage = "eclipse-temurin:25-jre"
}
// https://docs.gradle.org/current/userguide/upgrading_major_version_9.html#test_task_fails_when_no_tests_are_discovered
tasks.withType().configureEach {
failOnNoDiscoveredTests = false
}
```
### Environment Information
Micronaut: 5.0.0-SNAPSHOT
Kotlin: 2.3.20
KSP: 2.3.6
Java: 25.0.2 (Temurin)
micronaut-gradle-plugin: 5.0.0-M1
Contributor guide
Research direction
Start by reproducing the supplied Kotlin data class with @RequestBean, @Introspected, and @AssertTrue, then inspect the KSP-generated introspection and validation metadata. Done means an @AssertTrue method without an is/get prefix is invoked during request validation, while the prefixed form does not trigger the reported KSP constructor or bindable-property error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- kotlin
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100