Skip to main content
1

Implement Validatable and declare the rules

A class that knows its own rules implements Validatable and returns them from validation(). Each line starts with a property, says what null means for it, and lists its constraints:
User.kt
::name required { minLength(2) } reads as the requirement: the name is required, and has at least 2 characters. ::age ifPresent { min(18) } says the age may be missing, but when it is there, it is at least 18.
2

Validate

validate() runs every rule and returns a ValidationResult:
validate() is a suspend function, so a rule can query a database or an API. Outside a coroutine, call validateBlocking() instead. It runs the same rules and blocks the thread until they finish.
3

Read the violations

The three rules are broken at once, and each violation has a path, a message and a code:Show the message to people and branch on the code in programs: codes never change with the language of the messages.
4

Or throw at the edge of your system

In a controller or a handler, validateOrThrow() throws a ValidationException that carries the same list, so one error handler can turn every invalid request into a response:

What null means

required, ifPresent and forbidden, the start of every rule.

Nested objects

valid() follows the data, and the path follows it too.

Rules across fields

One of two fields, a total that matches its items.

Every constraint

175 of them, by the type of value they check.