bean validation not working with kotlin (JSR 380)
Answer (Kotlin 1.3.70)
Make sure to compile the kotlin code with jvm target 1.8 or greater and enable this feature by providing the -Xemit-jvm-type-annotations
when compiling.
For Spring Boot projects you only have to do the following changes (tested with Spring Boot 2.3.3 and Kotlin 1.4.0):
- In your pom set the following property:
<properties> <java.version>11</java.version> <kotlin.version>1.4.0</kotlin.version> </properties>
- Add
<arg>-Xemit-jvm-type-annotations</arg>
to thekotlin-maven-plugin
:<build> <plugin> <artifactId>kotlin-maven-plugin</artifactId> <groupId>org.jetbrains.kotlin</groupId> <configuration> <args> <arg>-Xjsr305=strict</arg> <arg>-Xemit-jvm-type-annotations</arg> </args> <compilerPlugins> <plugin>spring</plugin> </compilerPlugins> </configuration> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-allopen</artifactId> <version>${kotlin.version}</version> </dependency> </dependencies> </plugin> </build>
Sample Project
Jetbrains Release Notes
Workaround (pre Kotlin 1.3.70)
Rafal G. already pointed out that we could use a custom validator as a workaround. So here's some code:
The Annotation:
import javax.validation.Constraint
import javax.validation.Payload
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
import kotlin.annotation.AnnotationTarget.CONSTRUCTOR
import kotlin.annotation.AnnotationTarget.FIELD
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
import kotlin.reflect.KClass
@MustBeDocumented
@Constraint(validatedBy = [NoNullElementsValidator::class])
@Target(allowedTargets = [FUNCTION, FIELD, ANNOTATION_CLASS, CONSTRUCTOR, VALUE_PARAMETER, TYPE_PARAMETER])
@Retention(AnnotationRetention.RUNTIME)
annotation class NoNullElements(
val message: String = "must not contain null elements",
val groups: Array<KClass<out Any>> = [],
val payload: Array<KClass<out Payload>> = []
)
The ConstraintValidator:
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
class NoNullElementsValidator : ConstraintValidator<NoNullElements, Collection<Any>> {
override fun isValid(value: Collection<Any>?, context: ConstraintValidatorContext): Boolean {
// null values are valid
if (value == null) {
return true
}
return value.stream().noneMatch { it == null }
}
}
And finally the updated User class:
data class User(
@field:NotEmpty
@field:NoNullElements
var roles: MutableSet<Role> = HashSet()
)
Altough validation works now, the resulting ConstrainViolation is slightly different. For example the elementType
and propertyPath
differs as you can see below.
Java:
Kotlin:
Source is available here: https://github.com/DarkAtra/jsr380-kotlin-issue/tree/workaround
Thanks again for your help Rafal G.
Try adding ?
like this:
data class User(
@field:Valid
@field:NotEmpty
var roles: MutableSet<@NotNull Role?> = HashSet()
)
Then the kotlin compiler should realise roles could be null
, and it might honor the validation, I know little about JSR380 so i'm just guessing though.