kotlin data class + bean validation jsr 303
Asked Answered
P

2

75

I'm trying to get Kotlin working with jsr 303 validation on a spring-data-rest project.

Given the following data class declarartion :

@Entity data class User(
    @Id 
    @GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
    var id: Long? = null,

    @Size(min=5, max=15)
    val name: String
)

The @Size annotation has no effect here, making me able to save a user with a name of 1 character.
It works well when executing the very same example but in a Java class instead of Kotlin.

This makes me think of a Kotlin problem.

Thanks in advance for you help !

Persecution answered 7/3, 2016 at 15:37 Comment(2)
what should be the target of the annotation? Constructor parameter or the field?Sherborn
Many thanks for the question. I desperately looking what is wrong !Fistula
S
134

You need to use Annotation use-site targets since the default for a property declared in the constructor is to target the annotation on the constructor parameter instead of the getter (which will be seen by JavaBeans compliant hosts) when there are multiple options available. Also using a data class might be inappropriate here (see note at end).

@Entity data class User(
    @Id
    @GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
    var id: Long? = null,

    @get:Size(min=5, max=15) // added annotation use-site target here
    val name: String
)

The property target from the Kotlin docs may look tempting, but it can only be seen from Kotlin and not Java. Usually get does the trick, and it is not needed on the bean set.

The docs describe the process as:

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

And the @Size annotation is:

@Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER})

Therefore since PARAMETER is a valid target, and multiple targets are available (parameter, field, method [get/set]) it choses PARAMETER which is not what you want. Therefore for a JavaBean host to see the property it will look for the getter (properties are defined by the getter/setter and not the backing field).

In one of the Java samples, it shows:

public class Book {
    private String title;
    private String description;

    // ...

    @NotEmpty(groups={FirstLevelCheck.class, Default.class})
    @Size(max=30)
    public String getTitle() {
        return title;
    }

    // ...
}

Which matches our usage of having it on the getter. If it were to be on the field like some of the validation annotations show, see the field use-site target. Or if the field must also be publicly accessible, see the @JvmField annotation in Kotlin.

NOTE: As mentioned in notes from others, you should likely consider NOT using a data class for entities if they use an auto-generated ID since it will not exist for new objects the same as for retrieved objects; and a data class will generate equals and hashCode to include all fields including the ones it should not. You can read guidance about this from the Hibernate docs.

Sedentary answered 7/3, 2016 at 15:37 Comment(5)
You rock ! Thanks ! It is also possible to use the @get:Size(min=5, max=15) annotation.Persecution
Although it's correct answer it's worth to note that you should not use data class for entity anyway. The reason is - it will generate equals and hashCode methods using all properties, including id, which is not desired behavior for JPA entity. See here: docs.jboss.org/hibernate/stable/core.old/reference/en/html/…Bundle
thanks @waste, I made an edit to add the note directly in the answer.Sedentary
"If there are explicit implementations of equals(), hashCode() or toString() in the data class body or final implementations in a superclass, then these functions are not generated, and the existing implementations are used" - kotlin docsDey
Oh god, Thank you soooooo much for this answer.I've lost a day about it figuring out why my annotation wasn't taking into consideration...Vociferate
B
20

Use the @get or @field targets for validation annotations. Annotations with the target @param(first default) and @property are not supported.

e.g:

From @NotEmpty To @field:NotEmpty

data class Student(
    @field:NotEmpty @field:Size(min= 2, message = "Invalid field") var name: String? = ""
)

GL

Benitobenjamen answered 14/6, 2020 at 15:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.