https://github.com/making/yavi
Yet Another Validation for Java (A lambda based type safe validation framework)
https://github.com/making/yavi
java kotlin validation validation-library validator
Last synced: about 2 months ago
JSON representation
Yet Another Validation for Java (A lambda based type safe validation framework)
- Host: GitHub
- URL: https://github.com/making/yavi
- Owner: making
- License: apache-2.0
- Created: 2018-08-21T10:39:38.000Z (almost 7 years ago)
- Default Branch: develop
- Last Pushed: 2025-04-12T00:02:01.000Z (3 months ago)
- Last Synced: 2025-04-13T13:19:08.909Z (3 months ago)
- Topics: java, kotlin, validation, validation-library, validator
- Language: Java
- Homepage: https://yavi.ik.am
- Size: 2.51 MB
- Stars: 795
- Watchers: 23
- Forks: 62
- Open Issues: 21
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
## YAVI (*Y*et *A*nother *V*al*I*dation)
[](https://www.apache.org/licenses/LICENSE-2.0) [](https://maven-badges.herokuapp.com/maven-central/am.ik.yavi/yavi) [](https://www.javadoc.io/doc/am.ik.yavi/yavi) [](https://github.com/making/yavi/actions)

YAVI (pronounced jɑ-vάɪ)
is a lambda based type safe validation for Java.### Why YAVI?
YAVI sounds as same as a Japanese slang "YABAI (ヤバイ)" that means awesome or awful depending on the context (like "Crazy").
If you use YAVI, you will surely understand that it means the former.The concepts are
* No reflection!
* No (runtime) annotation!
* Not only Java Beans!
* Zero dependency!If you are not a fan of [Bean Validation](https://beanvalidation.org/), YAVI will be an awesome alternative.
YAVI has the following features:
* Type-safe constraints, unsupported constraints cannot be applied to the wrong type
* Fluent and intuitive API
* Constraints on any object. Java Beans, [Records](https://openjdk.java.net/jeps/395), [Protocol Buffers](https://developers.google.com/protocol-buffers), [Immutables](https://immutables.github.io/) and anything else.
* Lots of powerful built-in constraints
* Easy custom constraints
* Validation for groups, conditional validation
* Validation for arguments before creating an object
* Support for API and combination of validation results and validators that incorporate the concept of functional programmingSee [the reference documentation](https://yavi.ik.am) for details.
### Presentations
* 2021-07-01 YAVIの紹介 (Japanese) [[Deck](https://docs.google.com/presentation/d/1ZcGN7qZpZ92XD6FwdHxpxJ1duF1kqilI97zq-0JpzsA/edit?usp=sharing)] [[Recording](https://www.youtube.com/watch?v=o0-u6QSBlv8)]
### Getting Started
> This content is derived from https://hibernate.org/validator/documentation/getting-started/
Welcome to YAVI.
The following paragraphs will guide you through the initial steps required to integrate
YAVI into your application.#### Prerequisites
* [Java Runtime](http://www.oracle.com/technetwork/java/index.html) >= 8
* [Apache Maven](http://maven.apache.org/)#### Project set up
In order to use YAVI within a Maven project, simply add the following dependency to
your `pom.xml`:```xml
am.ik.yavi
yavi
0.16.0```
This tutorial uses JUnit 5 and AssertJ. Add the following dependencies as needed:
```xml
org.junit.jupiter
junit-jupiter-api
5.11.4
testorg.assertj
assertj-core
3.26.3
test```
#### Applying constraints
Let’s dive into an example to see how to apply constraints:
Create `src/main/java/com/example/Car.java` and write the following code.
```java
package com.example;import am.ik.yavi.builder.ValidatorBuilder;
import am.ik.yavi.core.Validator;public record Car(String manufacturer, String licensePlate, Integer seatCount) {
public static final Validator validator = ValidatorBuilder.of()
.constraint(Car::manufacturer, "manufacturer", c -> c.notNull())
.constraint(Car::licensePlate, "licensePlate", c -> c.notNull().greaterThanOrEqual(2).lessThanOrEqual(14))
.constraint(Car::seatCount, "seatCount", c -> c.greaterThanOrEqual(2))
.build();
}
```The `ValidatorBuilder.constraint` is used to declare the constraints which should be
applied to the return values of getter for the `Car` instance:* `manufacturer` must never be null
* `licensePlate` must never be null and must be between 2 and 14 characters long
* `seatCount` must be at least 2> You can find [the complete source code](https://github.com/making/gs-yavi) on GitHub.
#### Validating constraints
To perform a validation of these constraints, you use a `Validator` instance. To
demonstrate this, let’s have a look at a simple unit test:Create `src/test/java/com/example/CarTest.java` and write the following code.
```java
package com.example;import am.ik.yavi.core.ConstraintViolations;
import org.junit.jupiter.api.Test;import static org.assertj.core.api.Assertions.assertThat;
class CarTest {
@Test
void manufacturerIsNull() {
final Car car = new Car(null, "DD-AB-123", 4);
final ConstraintViolations violations = Car.validator.validate(car);assertThat(violations.isValid()).isFalse();
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message()).isEqualTo("\"manufacturer\" must not be null");
}@Test
void licensePlateTooShort() {
final Car car = new Car("Morris", "D", 4);
final ConstraintViolations violations = Car.validator.validate(car);assertThat(violations.isValid()).isFalse();
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message()).isEqualTo("The size of \"licensePlate\" must be greater than or equal to 2. The given size is 1");
}@Test
void seatCountTooLow() {
final Car car = new Car("Morris", "DD-AB-123", 1);
final ConstraintViolations violations = Car.validator.validate(car);assertThat(violations.isValid()).isFalse();
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message()).isEqualTo("\"seatCount\" must be greater than or equal to 2");
}@Test
void carIsValid() {
final Car car = new Car("Morris", "DD-AB-123", 2);
final ConstraintViolations violations = Car.validator.validate(car);assertThat(violations.isValid()).isTrue();
assertThat(violations).hasSize(0);
}
}
````Validator` instances are thread-safe and may be reused multiple times.
The `validate()` method returns a `ConstraintViolations` instance, which you can iterate
in order to see which validation errors occurred. The first three test methods show some
expected constraint violations:* The `notNull()` constraint on `manufacturer` is violated in `manufacturerIsNull()`
* The `greaterThanOrEqual(int)` constraint on `licensePlate` is violated
in `licensePlateTooShort()`
* The `greaterThanOrEqual(int)` constraint on `seatCount` is violated
in `seatCountTooLow()`If the object validates successfully, `validate()` returns an empty `ConstraintViolations`
as you can see in
`carIsValid()`. You can also check if the validation was successful with
the `ConstraintViolations.isValid` method.### Always Valid model?
Are you dissatisfied with the classic validation approach of creating an object (that might be invalid) first and then validating it? If you want to create a model that can only be generated and exist in a valid state, YAVI can help you achieve this as well.
In this case, create your `Car` class as follows:
```java
package com.example;import am.ik.yavi.arguments.Arguments3Validator;
import am.ik.yavi.core.Validated;
import am.ik.yavi.validator.Yavi;public final class Car {
private static Arguments3Validator validator = Yavi.arguments()
._string("manufacturer", c -> c.notNull())
._string("licensePlate", c -> c.notNull().greaterThanOrEqual(2).lessThanOrEqual(14))
._integer("seatCount", c -> c.greaterThanOrEqual(2))
.apply(Car::new);private final String manufacturer;
private final String licensePlate;
private final Integer seatCount;
public static Validated of(String manufacturer, String licensePlate, Integer seatCount) {
return validator.validate(manufacturer, licensePlate, seatCount);
}private Car(String manufacturer, String licensePlate, Integer seatCount) {
this.manufacturer = manufacturer;
this.licensePlate = licensePlate;
this.seatCount = seatCount;
}public String manufacturer() {
return manufacturer;
}public String licensePlate() {
return licensePlate;
}public Integer seatCount() {
return seatCount;
}}
````Arguments3Validator` is a validator that validates three arguments (`String`, `String`, `Integer`) and creates a `Car` instance only when validation succeeds.
With this approach, since the `Car` class's constructor is private, you need to create a `Car` instance through the `of` factory method. The `of` method validates the arguments before creating a `Car` instance. It returns the validation result wrapped in a `Validated` type. If validation fails, you cannot obtain a `Car` instance.
The test code changes to:
```java
package com.example;import am.ik.yavi.core.ConstraintViolations;
import am.ik.yavi.core.Validated;
import org.junit.jupiter.api.Test;import static org.assertj.core.api.Assertions.assertThat;
class CarTest {
@Test
void manufacturerIsNull() {
Validated validated = Car.of(null, "DD-AB-123", 4);
assertThat(validated.isValid()).isFalse();
ConstraintViolations violations = validated.errors();
assertThat(violations.isValid()).isFalse();
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message()).isEqualTo("\"manufacturer\" must not be null");
}@Test
void licensePlateTooShort() {
Validated validated = Car.of("Morris", "D", 4);
assertThat(validated.isValid()).isFalse();
ConstraintViolations violations = validated.errors();
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message())
.isEqualTo("The size of \"licensePlate\" must be greater than or equal to 2. The given size is 1");
}@Test
void seatCountTooLow() {
Validated validated = Car.of("Morris", "DD-AB-123", 1);
assertThat(validated.isValid()).isFalse();
ConstraintViolations violations = validated.errors();
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message()).isEqualTo("\"seatCount\" must be greater than or equal to 2");
}@Test
void carIsValid() {
Validated validated = Car.of("Morris", "DD-AB-123", 2);
assertThat(validated.isValid()).isTrue();
Car car = validated.value();
assertThat(car.manufacturer()).isEqualTo("Morris");
assertThat(car.licensePlate()).isEqualTo("DD-AB-123");
assertThat(car.seatCount()).isEqualTo(2);
}}
```Would you prefer to perform validation within the constructor rather than through a factory method? (And would you like to keep using records?) This can also be achieved with YAVI.
This time, let's modify the `Car` class as follows:```java
package com.example;import am.ik.yavi.arguments.Arguments3Validator;
import am.ik.yavi.validator.Yavi;public record Car(String manufacturer, String licensePlate, Integer seatCount) {
private static Arguments3Validator validator = Yavi.arguments()
._string("manufacturer", c -> c.notNull())
._string("licensePlate", c -> c.notNull().greaterThanOrEqual(2).lessThanOrEqual(14))
._integer("seatCount", c -> c.greaterThanOrEqual(2))
.apply(Car::new);public Car {
validator.lazy().validated(manufacturer, licensePlate, seatCount);
}
}
```With this approach, validation is performed at the constructor stage, and invalid objects will not be created - instead, a `ConstraintViolationsException` will be thrown.
(Using the `lazy` method prevents recursive creation of `Car` instances in the constructor even when validation succeeds. This is necessary to avoid a StackOverflow error.)
The test code changes to:
```java
package com.example;import am.ik.yavi.core.ConstraintViolations;
import am.ik.yavi.core.ConstraintViolationsException;
import org.junit.jupiter.api.Test;import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;class CarTest {
@Test
void manufacturerIsNull() {
assertThatThrownBy(() -> {
new Car(null, "DD-AB-123", 4);
}).isInstanceOf(ConstraintViolationsException.class).satisfies(e -> {
ConstraintViolations violations = ((ConstraintViolationsException) e).violations();
assertThat(violations.isValid()).isFalse();
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message()).isEqualTo("\"manufacturer\" must not be null");
});
}@Test
void licensePlateTooShort() {
assertThatThrownBy(() -> {
new Car("Morris", "D", 4);
}).isInstanceOf(ConstraintViolationsException.class).satisfies(e -> {
ConstraintViolations violations = ((ConstraintViolationsException) e).violations();
assertThat(violations.isValid()).isFalse();
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message())
.isEqualTo("The size of \"licensePlate\" must be greater than or equal to 2. The given size is 1");
});
}@Test
void seatCountTooLow() {
assertThatThrownBy(() -> {
new Car("Morris", "DD-AB-123", 1);
}).isInstanceOf(ConstraintViolationsException.class).satisfies(e -> {
ConstraintViolations violations = ((ConstraintViolationsException) e).violations();
assertThat(violations.isValid()).isFalse();
assertThat(violations).hasSize(1);
assertThat(violations.get(0).message()).isEqualTo("\"seatCount\" must be greater than or equal to 2");
});
}@Test
void carIsValid() {
assertThatCode(() -> {
new Car("Morris", "DD-AB-123", 2);
}).doesNotThrowAnyException();
}}
```#### Reusable and composable validators
Let's take a look at a more advanced usage.
The following constraints are domain rules that can potentially be used not only for the `Car` class:
> * `manufacturer` must never be null
> * `licensePlate` must never be null and must be between 2 and 14 characters long
> * `seatCount` must be at least 2In YAVI, you can define small validators for each value and then combine them to compose validators for objects.
Please take a look at the following definition:
```java
package com.example;import am.ik.yavi.builder.IntegerValidatorBuilder;
import am.ik.yavi.builder.StringValidatorBuilder;
import am.ik.yavi.core.Validated;public final class Car {
public static StringValidator manufacturerValidator = StringValidatorBuilder
.of("manufacturer", c -> c.notNull())
.build();public static StringValidator licensePlateValidator = StringValidatorBuilder
.of("licensePlate", c -> c.notNull().greaterThanOrEqual(2).lessThanOrEqual(14))
.build();public static IntegerValidator seatCountValidator = IntegerValidatorBuilder
.of("seatCount", c -> c.greaterThanOrEqual(2))
.build();private static Arguments3Validator validator = manufacturerValidator
.split(licensePlateValidator)
.split(seatCountValidator)
.apply(Car::new);private final String manufacturer;
private final String licensePlate;
private final Integer seatCount;
public static Validated of(String manufacturer, String licensePlate, Integer seatCount) {
return validator.validate(manufacturer, licensePlate, seatCount);
}// omitted
}
```After defining validators for `manufacturer`, `licensePlate`, and `seatCount`, it combines these three validators (`StringValidator` is a validator that validates a `String` and returns an object of type `T`, and `IntegerValidator` is a validator that validates an `Integer` and returns an object of type `T`) to create an `Arguments3Validator`.
These small validators can also be used for generating other objects, which prevents domain rules from being scattered throughout the code.
#### Where to go next?
That concludes the 5 minutes tour through the world of YAVI. If you want a more complete
introduction, it is recommended to read "[Using YAVI](https://yavi.ik.am/#using-yavi)" in the reference document.### Required
* Java 8+
### License
Licensed under the Apache License, Version 2.0.