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 !
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.