Skip to main content
Validity checks that your data follows its rules, and tells you, in one pass, every rule it breaks. You write the rules in Kotlin, next to the data, with the words of the requirement:
Each broken rule becomes a Violation with three things:

A path

Where the value is: email, shipping.zipCode, items[1].quantity, prices[USD].

A message

For people: must be a valid email. Configurable, and translatable.

A code

For programs: email. Stable across versions and languages.

The problem it solves

Most validation code starts as a few ifs in a controller. Then the rules multiply: a field required only for companies, a total that must match its items, a coupon that must exist in the database, a message in Portuguese for some users. The ifs spread, they stop at the first error, and nobody can tell which rules a request really follows. Validity gives those rules one place and one vocabulary:
  • Rules read like the requirements. ::email required { email() } is the sentence from the ticket.
  • Conditions are plain Kotlin. “Required if”, “only for companies” and “after the check-in” are an if, a when and a field of the object. No validation groups, no annotations on the class.
  • null is decided once. required, ifPresent and forbidden settle what null means, and every constraint after them works on the non-null type.
  • Every error at once. A form shows all its errors in one round trip, and an API returns all of them in one response.

How it compares

Each of them is a solid choice, and some go where Validity does not yet: Konform and Akkurate run on Kotlin Multiplatform, and Jakarta Validation plugs straight into the frameworks that read its annotations.

The ideas behind these docs

1

A validation is a list of rules about one object

You declare it in the class with Validatable, or beside it with validation<T> { }. Both run the same engine and accept the same constraints.
2

A property starts with what null means

required, ifPresent or forbidden come first. Inside their block, the value is never null.
3

Constraints are functions on the value

email(), minLength(3), cpf(): there are 175 of them, and your own look exactly the same.
4

Validating returns every violation

validate() gives you a list. validateOrThrow() throws it, for the edge of your system.

Nothing hidden

No annotations, no code generation, no kotlin-reflect. validity-core depends on nothing but the Kotlin standard library and kotlinx.coroutines, and phone numbers live in their own module, validity-phone, so libphonenumber is there only if you ask for it.

Install it

Add one dependency to Gradle or Maven.

Validate your first class

From zero to a list of violations in five minutes.