Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

kotlin: cannot get validation api's annotation

==========UPDATE==========

when i change the annotation to @get:NotNull , @get:Min and @get:Max, the hibernate-validator can read these annotations success.

But what i still want to know is:

why the validation-api's annotation, such as @NotNull , @Min and @Max cannot be used on data class's members directly, while JPA's annotations can be????

==========Bellow is the origin question===========

when i tried to use validation-api's annotation on data class, the Validator class (from hibernate-validator) cannot get the annotations, so the validation failed.

i wrote a test case, which include 3 data classes:

  • 1st one use JPA annotation @Column and @Id, which can be read successfully by test case.
  • 2nd one use validation-api annotation @NotEmpty, @Min,@Max on members, these annotation cannot be read by test case
  • 3rd one use validation-api annotation @get:NotEmpty, @get:Min, @get:Max, the test case cannot read these annotation.

The target and retention of @Column , @NotNull, @Min and @Max are all: RUNTIME and FIELD

So, what happened in the behind? how can i use the validation annotations properly?

here is the test case:

import org.hibernate.validator.constraints.NotEmpty
import org.junit.Test
import javax.persistence.Column
import javax.persistence.Id
import javax.validation.constraints.Max
import javax.validation.constraints.Min
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaMethod

class KotlinFeatureTest2 {
    @Test
    fun test_get_annotation() {
        // can get field's annotation for BeanUseJPAAnnotation success
        println("Getting field annotations for BeanUseJPAAnnotation :")
        BeanUseJPAAnnotation::class.members.forEach {
            if (it is KProperty) {
                val field = it.javaField
                println("${field?.name}'s annotations:")
                field?.annotations?.forEachIndexed { i, an ->
                    println("        $i is: $an")
                }
            }
        }

        println("--------------------")
        println("Getting field annotations for BeanUseValidationAnnotation :")
        // CANT get field's annotation for BeanUseJPAAnnotation success
        BeanUseValidationAnnotation::class.members.forEach {
            if (it is KProperty) {
                val field = it.javaField
                println("${field?.name}'s annotations:")
                field?.annotations?.forEachIndexed { i, an ->
                    println("        $i is: $an")
                }
            }
        }

        println("--------------------")
        println("Getting field annotations for BeanUseValidationAnnotationOnMethod :")
        // CANT get field's annotation for BeanUseJPAAnnotation success
        BeanUseValidationAnnotationOnMethod::class.members.forEach {
            if (it is KFunction) {
                val method = it.javaMethod
                println("${method?.name}'s annotations: ")
                method?.annotations?.forEachIndexed { i, an ->
                    println("        $i is: $an")
                }
            }
        }
    }
}

data class BeanUseJPAAnnotation(
        @Column(name = "id") @Id val id: String,
        @Column(name = "user_name") val name: String)

data class BeanUseValidationAnnotation(
        @NotEmpty(message = "name can not be empty")
        val name: String,

        @Min(value = 1)
        @Max(value = 100)
        val age: Int
)

data class BeanUseValidationAnnotationOnMethod(
        @get:NotEmpty(message = "name can not be empty")
        val name: String,

        @get:Min(value = 1)
        @get:Max(value = 100)
        val age: Int)

and here are the output of this test case:

Getting field annotations for BeanUseJPAAnnotation :
id's annotations:
    0 is: @javax.persistence.Column(nullable=true, unique=false, precision=0, name=id, length=255, scale=0, updatable=true, columnDefinition=, table=, insertable=true)
    1 is: @javax.persistence.Id()
name's annotations:
    0 is: @javax.persistence.Column(nullable=true, unique=false, precision=0, name=user_name, length=255, scale=0, updatable=true, columnDefinition=, table=, insertable=true)
--------------------
Getting field annotations for BeanUseValidationAnnotation :
age's annotations:
name's annotations:
--------------------
Getting field annotations for BeanUseValidationAnnotationOnMethod :
component1's annotations: 
component2's annotations: 
copy's annotations: 
equals's annotations: 
hashCode's annotations: 
toString's annotations: 
like image 285
Ace.Yin Avatar asked Dec 13 '25 07:12

Ace.Yin


1 Answers

The following is the signature part of javax.persistence.Column:

@Target({METHOD, FIELD}) 
@Retention(RUNTIME)
public @interface Column {

On the contrary here's the same part of javax.validation.constraints.Min:

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface Min {

As you can see the JPA Persistence annotations target METHOD and FIELD hence Kotlin emits them on a FIELD level. However the validators API annotations target more constructs, the PARAMETER in particular. Given that when generating annotations for constructor declared properties Kotlin compiler chooses to annotate parameters only. The BeanUseValidationAnnotation constructor signature equivalent in Java would look like:

public BeanUseValidationAnnotation(@NotEmpty(message = "name can not be empty") @NotNull String name, @Min(1L) @Max(100L) int age) {

This behavior is stated in the documentation:

If you don't specify a use-site target, the target is chosen according to the @Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:

  • param
  • property
  • field
like image 117
miensol Avatar answered Dec 16 '25 21:12

miensol



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!