Backend Development 14 min read

Master YAVI: Powerful Java Bean Validation with Real‑World Code Samples

This article introduces the YAVI library, a type‑safe Java validation framework, and walks through its core features, custom constraints, nested and collection validation, conditional and group validation, fail‑fast mode, parameter validation with comprehensive code examples and output screenshots.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master YAVI: Powerful Java Bean Validation with Real‑World Code Samples

Introduction

YAVI is a powerful type‑safe validation library for Java applications, offering an alternative to Bean Validation. It can validate any object type, including Java Beans, records, Protocol Buffers, Immutables, etc., and provides built‑in constraints such as not‑empty, length, numeric range, and more.

It also supports custom constraints, constraint composition, and functional‑style API.

Key Features

Type‑safe constraints; unsupported constraints cannot be applied to wrong types.

Fluent and intuitive API.

Applicable to any object (Beans, Records, Protocol Buffers, Immutables).

Rich built‑in constraints.

Easy to define custom constraints.

Group and conditional validation.

Parameter validation before object creation.

Integration with functional programming concepts.

Practical Examples

2.1 Environment Setup

<code>&lt;dependency&gt;
  &lt;groupId&gt;am.ik.yavi&lt;/groupId&gt;
  &lt;artifactId&gt;yavi&lt;/artifactId&gt;
  &lt;version&gt;0.14.4&lt;/version&gt;
&lt;/dependency&gt;
</code>

Adding the dependency is enough; no extra configuration is required.

2.2 Defining and Obtaining a Core Validator

<code>Validator&lt;User&gt; validator = ValidatorBuilder.&lt;User&gt;of()
    .constraint(User::getName, "name", c -> c.notEmpty())
    .constraint(User::getIdNo, "idNo", c -> c.notNull().greaterThanOrEqual(2).lessThanOrEqual(14))
    .constraint(User::getAge, "age", c -> c.greaterThanOrEqual(18))
    .build();
</code>

The validator checks name , idNo , and age fields of a User object.

Usage Example

<code>@Test
public void testUser() {
    User user = new User("", "xxx", 17);
    ConstraintViolations violations = User.validator.validate(user);
    if (!violations.isValid()) {
        violations.forEach(v -> System.err.printf("Field [%s] error: %s%n", v.name(), v.message()));
    }
}
</code>

2.3 Nested Object Validation

Use nest to apply constraints to nested fields, delegating to another validator or defining inline constraints.

<code>public class Address { private City city; private Country country; /* getters/setters */ }
public class City { private String name; /* ... */ }
public class Country { private String name; /* ... */ }

interface AddressValidator {
    Validator&lt;Country&gt; countryValidator = ValidatorBuilder.&lt;Country&gt;of()
        .constraint(Country::getName, "name", c -> c.notEmpty().greaterThanOrEqual(2))
        .build();

    Validator&lt;City&gt; cityValidator = ValidatorBuilder.&lt;City&gt;of()
        .constraint(City::getName, "name", c -> c.notEmpty())
        .build();

    Validator&lt;Address&gt; validator = ValidatorBuilder.&lt;Address&gt;of()
        .nest(Address::getCountry, "country", countryValidator)
        .nest(Address::getCity, "city", cityValidator)
        .build();
}
</code>

2.4 Collection Validation

Apply constraints to each element of a Collection/Map/Array using forEach .

<code>public class Person { private final String name; /* getters/setters */ }
public class PersonVO { private final List&lt;Person&gt; persons; /* constructor, getters */ }

interface PersonValidator {
    Validator&lt;Person&gt; personValidator = ValidatorBuilder.&lt;Person&gt;of()
        .constraint(Person::getName, "name", c -> c.notEmpty().greaterThanOrEqual(2))
        .build();

    Validator&lt;PersonVO&gt; validator = ValidatorBuilder.&lt;PersonVO&gt;of()
        .forEach(PersonVO::getPersons, "persons", personValidator)
        .build();
}
</code>

2.5 Conditional Validation

Use constraintOnCondition (or constraintOnCondition with a ConstraintCondition ) to apply a constraint only when a predicate is true.

<code>Validator&lt;Customer&gt; validator = ValidatorBuilder.&lt;Customer&gt;of()
    .constraintOnCondition((c, g) -> !c.getName().isEmpty(),
        b -> b.constraint(Customer::getEmail, "email", c -> c.notEmpty()))
    .build();
</code>

2.6 Custom Constraint

Implement CustomConstraint to define a constraint not covered by built‑ins.

<code>public class OrderSnoConstraint implements CustomConstraint&lt;String&gt; {
    public boolean test(String s) { return s.startsWith("pack-"); }
    public String messageKey() { return "order.sno"; }
    public String defaultMessageFormat() { return "\"{0}\" order number must start with `pack-`"; }
}
</code>
<code>Validator&lt;Order&gt; validator = ValidatorBuilder.&lt;Order&gt;of()
    .constraint(Order::getSno, "sno", c -> c.notBlank().predicate(new OrderSnoConstraint()))
    .build();
</code>

2.7 Group Validation

Define groups with an enum implementing ConstraintGroup and apply constraints only for specific groups.

<code>public enum Group implements ConstraintGroup { CREATE, UPDATE, DELETE }

Validator<App> validator = ValidatorBuilder.&lt;App&gt;of()
    .constraintOnCondition(Group.CREATE.toCondition(),
        b -> b.constraint(App::getId, "id", c -> c.isNull()))
    .constraintOnCondition(Group.UPDATE.toCondition(),
        b -> b.constraint(App::getId, "id", c -> c.notNull()))
    .build();
</code>

2.8 Custom Error Messages

Attach message to a constraint to override the default violation message.

<code>Validator<User> validator = ValidatorBuilder.&lt;User&gt;of()
    .constraint(User::getName, "name", c -> c.notEmpty().message("{0} name must be provided"))
    .build();
</code>

2.9 Fail‑Fast Mode

Enable failFast(true) to stop validation after the first error.

<code>Validator<User> validator = ValidatorBuilder.&lt;User&gt;of()
    .constraint(User::getName, "name", c -> c.notEmpty())
    .constraint(User::getIdNo, "idNo", c -> c.notNull().greaterThanOrEqual(2).lessThanOrEqual(14))
    .constraint(User::getAge, "age", c -> c.greaterThanOrEqual(18))
    .failFast(true)
    .build();
</code>

2.10 Parameter Validation

Validate constructor arguments or ordinary method parameters with ArgumentsValidatorBuilder .

<code>public class Person {
    private final String name;
    private final String email;
    private final Integer age;
    public Person(String name, String email, Integer age) { this.name = name; this.email = email; this.age = age; }

    public static Arguments3Validator<String, String, Integer, Person> validator =
        ArgumentsValidatorBuilder.of(Person::new)
            .builder(b -> b
                ._string(Arguments1::arg1, "name", c -> c.notBlank().lessThanOrEqual(100))
                ._string(Arguments2::arg2, "email", c -> c.notBlank().lessThanOrEqual(100).email())
                ._integer(Arguments3::arg3, "age", c -> c.greaterThanOrEqual(0).lessThan(200)))
            .build();
}
</code>

Method‑argument validation works similarly using a static validator defined in the service class.

Javabackend developmentSpringdata validationYAVI
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.