Bean Validation is the Java standard for validation and can be used in Kotlin as well. However, there are also two popular alternative libraries for validation available in Kotlin: Konform and Valiktor. Both implement validation in a more kotlin-like way without annotations. In this post we will look at Valiktor.
Getting started with Valiktor
First we need to add the Valiktor dependency to our project.
For Maven:
<dependency> <groupId>org.valiktor</groupId> <artifactId>valiktor-core</artifactId> <version>0.12.0</version> </dependency>
For Gradle:
implementation 'org.valiktor:valiktor-core:0.12.0'
Now let's look at a simple example:
class Article(val title: String, val text: String) { init { validate(this) { validate(Article::text).hasSize(min = 10, max = 10000) validate(Article::title).isNotBlank() } } }
Within the init block we call the validate(..) function to validate the Article object. validate(..) accepts two parameters: The object that should be validated and a validation function. In the validation function we define validation constraints for the Article class.
Now we try to create an invalid Article object with:
Article(title = "", text = "some article text")
This causes a ConstraintViolationException to be thrown because the title field is not allowed to be empty.
More validation constraints
Let's look at a few more example validation rules:
validate(this) { // Multiple constraints can be chained validate(Article::authorEmail) .isNotBlank() .isEmail() .endsWith("@cool-blog.com") // Nested validation // Checks that Article.category.name is not blank validate(Article::category).validate { validate(Category::name).isNotBlank() } // Collection validation // Checks that no Keyword in the keywords collection has a blank name validate(Article::keywords).validateForEach { validate(Keyword::name).isNotBlank() } // Conditional validation // if the article is published the permalink field cannot be blank if (isPublished) { validate(Article::permalink).isNotBlank() } }
Validating objects from outside
In the previous examples the validation constraints are implemented within the objects init block. However, it is also possible to perform the validation outside the class.
For example:
val person = Person(name = "") validate(person) { validate(Person::name).isNotBlank() }
This validates the previously created Person object and causes a ConstraintViolationException to be thrown (because name is empty)
Creating a custom validation constraint
To define our own validation methods we need two things: An implementation of the Constraint interface and an extension method. The following snippet shows an example validation method to make sure an Interable<T> does not contain duplicate elements:
object NoDuplicates : Constraint fun <E, T> Validator<E>.Property<Iterable<T>?>.hasNoDuplicates() = this.validate(NoDuplicates) { iterable: Iterable<T>? -> if (iterable == null) { return@validate true } val list = iterable.toList() val set = list.toSet() set.size == list.size }
This adds a method named hasNoDuplicates() to Validator<E>.Property<Iterable<T>?>. So this method can be called for fields of type Iterable<T>. The extension method is implemented by calling validate(..) with our Constraint and passing a validation function.
In the validation function we implement the actual validation. In this example we simply convert the Iterable to a List and then the List to a Set. If duplicate elements are present both collections have a different size (a Set does not contain duplicate elements).
We can now use our hasNoDuplicates() validation method like this:
class Article(val keywords: List<Keyword>) { init { validate(this) { validate(Article::keywords).hasNoDuplicates() } } }
Conclusion
Valiktor is an interesting alternative for validation in Kotlin. It provides a fluent DSL to define validation rules. Thoes rules are defined in standard Kotlin code (and not via annotations) which makes it easy to add conditional logic. Valiktor comes with many predefined validation constraints. Custom constraints easily be implemented using extension functions.
Comments
Mark R+V - Wednesday, 7 April, 2021
Very good - useful. Might use it in a project...
Yevgen - Friday, 17 March, 2023
Looks exactly what I am looking for! Thank you!
Leave a reply