YAVI validates Java objects with lambdas instead of reflection and annotations
Yet Another Validation for Java (A lambda based type safe validation framework)
At a glance
- What is it?
- YAVI is a Java validation library whose central bet is that you can describe constraints as code rather than as annotations, so a constraint applies to a type that can hold it and nothing is read reflectively at runtime. It has no dependencies, works on records, Protocol Buffers and Immutables as well as Java Beans, and adds argument validators for building objects that are valid by construction.
- Who is it for?
- YAVI fits a Java or Kotlin team that finds Bean Validation's annotations and reflection awkward, or that wants constraints to be visible in ordinary code and impossible to attach to the wrong type. Skip it if your project standardises on Bean Validation and the ecosystem integration that comes with it, since YAVI is a replacement rather than a bridge.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Four things YAVI refuses to do
The README lists the concepts of the library as four negatives, and the negatives are the design.
No reflection. No runtime annotation. Not only Java Beans. And zero dependency.
Each of those is a reaction to something. Reflection in a validator means the field to read is discovered at runtime, which costs performance, hides the dependency from the compiler and makes a typo in an annotation a runtime failure rather than a build failure. Runtime annotations mean the constraints live in metadata that a tool has to interpret, and that the same field can carry constraints the compiler knows nothing about. And not only Java Beans means the library is not tied to the getter and setter convention, which is what lets it validate records, Protocol Buffers and Immutables.
The consequence the README draws out is type safety of the constraints themselves. An unsupported constraint cannot be applied to the wrong type, which is the failure mode that hurts most in annotation based validation, where a size constraint accidentally lands on an integer field and is either ignored or throws at runtime.
The library positions itself as an alternative for people who are not fans of Bean Validation, and the documentation site is the reference. The primary language is Java, the topics list includes Kotlin, the licence is Apache-2.0, and the recent releases show active work, with 0.16.0 on 2025-04-28, 0.17.0 on 2026-09-02 and 0.17.1 on 2026-10-05, and the last push on 2026-10-19.
The ValidatorBuilder is where constraints live
The getting started section walks through a record, which is the clearest possible demonstration because a record has no setters and no annotations to put on.
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<Car> validator = ValidatorBuilder.<Car>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();
}Three things are happening in those three constraint calls. The first argument is a method reference, `Car::manufacturer`, and the README explains that the constraints are applied to the return values of the getter for the instance, which for a record is the accessor. The second argument is the name used in the violation message, which is why the messages in the tests read as `"manufacturer" must not be null` rather than with a generated property path. The third is the constraint itself, written as a lambda over a constraint builder, so the set of things you can call on it is decided by the type of the value.
That is the type safety in practice. `c.notNull()` is available for every type, `c.greaterThanOrEqual(2)` is available for numbers and for sized values, and the compiler refuses the combinations that make no sense.
The builder returns a `Validator<Car>` held in a static field, and the README says `Validator` instances are thread-safe and may be reused multiple times, which is what makes the pattern safe to keep in a static field on a record that is going to be constructed all over an application.
Size constraints on a string read like numeric ones
There is a detail in the example worth pausing on, because it is the kind of thing that costs an afternoon if you meet it in production rather than in a tutorial.
The `licensePlate` field is a `String`, and its constraint is written as `c.notNull().greaterThanOrEqual(2).lessThanOrEqual(14)`. Those method names look like integer comparisons. The README describes the rule in prose as the plate that must never be null and must be between 2 and 14 characters long, and the test asserts on the resulting message, which reads `"The size of "licensePlate" must be greater than or equal to 2. The given size is 1"`.
So for a string the numeric comparison applies to the size, not to a numeric value. The same builder method is reused for a number and for a sized value, and the type of the field decides the meaning. That is the price of a single fluent API, and the price is that the constraint call does not read the way it does for numbers. A team reading a builder chain quickly would benefit from a convention of their own here, and a comment on the field is cheap insurance.
The `seatCount` field is the contrast case, an `Integer` with `c.greaterThanOrEqual(2)`, and the message for it is `"seatCount" must be greater than or equal to 2`, with no mention of size.
ConstraintViolations is the result type, and it is iterable
Validation returns a `ConstraintViolations` instance, and the README describes it as something you can iterate in order to see which validation errors occurred, with an `isValid` method as the quick check.
The test code in the README shows the shape of a typical assertion.
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");
}So the result is a collection with a validity flag, an indexable accessor and a `message()` method on each entry. There is also a default message for a constraint, as the license plate case shows, where the message includes both the rule and the offending value, which means the message is generated rather than written by the caller.
The feature list adds a dimension that the tutorial does not show. Validation for groups lets you apply different sets of constraints in different situations, which is the classic use case for a create operation and an update operation that allow different fields to be absent. Conditional validation covers rules that depend on other fields. And there is support for combining validation results and validators around the idea of functional programming, so a validator can be composed rather than merely configured.
The last feature in the list is the one that changes the shape of the code rather than the rules, and it has its own section in the README.
Validating arguments before the object exists
The README frames this as the always valid model problem. It asks whether you are dissatisfied with the classic approach of creating an object that might be invalid first and validating it afterwards, and says that if you want a model that can only be generated and exist in a valid state, YAVI can help with that too.
The mechanism is a different set of types. The example imports `am.ik.yavi.arguments.Arguments3Validator` and `am.ik.yavi.core.Validated`, and the class is no longer a record that anyone can call the canonical constructor on. Instead, the arguments are validated, and the object is only produced if they pass.
The practical effect is that an invalid instance stops being a state that your code has to defend against later. Every consumer of the type receives something already known to be valid, and the checks happen once, at the boundary, rather than at every use.
The naming tells you how it scales. `Arguments3Validator` is the three argument form, so a factory-style API with an argument validator per arity is the shape of the library here, and the feature list names it directly as validation for arguments before creating an object.
This is the part of YAVI that a Bean Validation user cannot get from annotations at all, since annotations describe constraints on an existing object and cannot gate its construction. It is also the part that changes how a codebase is written, because a type that can only be constructed valid has to be designed that way from the start.
Constraints apply to anything you can read through an accessor
The claim that YAVI is not only for Java Beans is worth unpacking, because it is what makes the library usable in a modern Java codebase.
The feature list names Java Beans, Records, Protocol Buffers and Immutables, and adds anything else. The requirement in each case is the same, and it is the requirement the tutorial demonstrates: the builder needs a way to read the value of a field, expressed as a method reference, and the constraint is applied to the return value of that method.
For a record that is the accessor. For a Java Bean it is the getter. For a Protocol Buffers generated class it is the getter that the generated code provides. For an Immutables value it is the accessor. The library does not need to know which, because the method reference carries that information at the call site.
That is a smaller and more portable contract than a bean convention, and it is the reason a constraint can be attached to a type the library has never heard of. The cost is that the mapping from your model to the validator is written by hand, once per type, rather than being discovered automatically. For a large domain model that is real work, and it is the main thing to weigh against the annotation approach.
The `docs/` directory in the repository and the reference site both cover the rest of the constraint catalogue, and the README notes that the getting started content is derived from the Bean Validation documentation, which is a hint about how closely the two APIs are meant to line up for people arriving from the other side.
Adding it to a Maven project, and how the version moves
The installation is a single dependency, and the prerequisites are a Java runtime of 8 or above and Apache Maven.
<dependency>
<groupId>am.ik.yavi</groupId>
<artifactId>yavi</artifactId>
<version>0.17.1</version>
</dependency>The zero dependency claim matters here in a practical way. A validation library that pulls in a logging framework, a reflection library and a couple of utility modules is a set of version constraints in your build, and a library that pulls in nothing is not. The test dependencies in the tutorial are only there because the tutorial uses JUnit 5 and AssertJ, and they are scoped to tests.
The repository shows how the project itself is built and released, and it is a good indicator of how seriously the maintainer treats the release process. There is a `pom.xml` with the Maven wrapper alongside it, a `How-To-Release.md`, and a set of scripts for the version arithmetic, `set-release-version.sh`, `set-next-minor-version.sh`, `set-next-patch-version.sh` and `get-release-version.sh`, plus a `merge-main-to-develop.sh` script. The default branch is `develop`, and there is a `scripts/` directory and a `.springjavaformatconfig` for the formatter.
The release rhythm is a minor version every few months, with 0.17.0 in September 2026 and a patch release 0.17.1 a month later, and the last push on 2026-10-19. For a library you are about to put in front of a validation API, the 0.x numbering is the thing to watch, since it signals that the API can still move between minor versions, and the release scripts in the tree are where the maintainer controls that.
Editorial conclusion
YAVI fits a Java or Kotlin team that finds Bean Validation's annotations and reflection awkward, or that wants constraints to be visible in ordinary code and impossible to attach to the wrong type. Skip it if your project standardises on Bean Validation and the ecosystem integration that comes with it, since YAVI is a replacement rather than a bridge. Verify first that your objects are reachable through the accessors the builder expects, that the constraint set covers the cases Bean Validation handled for you, and that the 0.17 line's API matches the version you depend on.
Frequently asked questions
What is YAVI and what makes it different from Bean Validation?
YAVI is a lambda based type safe validation library for Java. The README lists its concepts as no reflection, no runtime annotation, not only Java Beans and zero dependency, and says an unsupported constraint cannot be applied to the wrong type, which it positions as an alternative to Bean Validation.
How do I add YAVI to a Maven project?
You add the dependency with groupId `am.ik.yavi`, artifactId `yavi` and a version such as `0.17.1` to your `pom.xml`. The prerequisites are a Java runtime of 8 or above and Apache Maven, and the library itself pulls in no dependencies.
How do I declare a constraint with YAVI?
You build a `Validator` with `ValidatorBuilder`, passing a method reference to the field's accessor, the name to use in violation messages, and a lambda over the constraint builder, for example `.constraint(Car::seatCount, "seatCount", c -> c.greaterThanOrEqual(2))`. The README notes that constraints are applied to the return values of the getter for the instance.
Can YAVI validate types that are not Java Beans?
Yes. The feature list names Java Beans, Records, Protocol Buffers and Immutables, and anything else, because the constraint is bound to a method reference that returns the value rather than to a bean convention the library has to discover.
How does YAVI help build objects that are always valid?
The README describes an always valid model approach that uses argument validators, with the example importing `Arguments3Validator` and `Validated`, so the arguments are checked before the object is created. The feature list names this as validation for arguments before creating an object.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/making-yavi)Was this useful?