Validate Objects In Yavi
Validation is a critical part of software development to ensure data integrity and correctness. Yavi (Yet Another Validator for Java) is a lightweight and powerful library for data validation in Java. Let us delve into understanding how Java Yavi can be used to validate objects effectively, ensuring they meet specific constraints and conditions.
1. Introduction
Validation is an essential aspect of modern software development, ensuring data accuracy, reliability, and compliance with business rules. In Java, validation frameworks like Bean Validation (Hibernate Validator) are widely used. However, these frameworks often come with limitations, such as restricted customization and verbosity. This is where Yavi (Yet Another Validator for Java) shines as a robust alternative.
Yavi is a lightweight, functional, and extensible validation library designed for modern Java applications. It provides a declarative, type-safe, and composable approach to validation, allowing developers to define complex validation rules in a clean and maintainable manner. With Yavi, you can easily validate objects, method arguments, and fields without relying on annotations or rigid configurations.
1.1 Key Features of Yavi
- Functional API: Yavi uses a functional programming paradigm, making it intuitive and readable.
- Fluent Syntax: Validation rules can be chained fluently for better readability and reduced boilerplate code.
- Extensibility: Developers can define custom constraints and conditional validations, enabling dynamic rule application.
- Composable Validators: Smaller, reusable validators can be composed into larger, more complex ones.
- Lightweight Dependency: Yavi has minimal dependencies, ensuring it integrates seamlessly with existing Java projects.
- Annotation-Free: Unlike traditional frameworks, Yavi allows you to define validations programmatically, avoiding annotation clutter.
1.2 Use cases of Yavi
Yavi is particularly well-suited for use cases where:
- You need fine-grained control over validation logic.
- Validation rules depend on dynamic or conditional factors.
- You want to avoid annotation-heavy designs or tightly coupled validation logic.
By adopting Yavi, developers can enhance code quality, maintainability, and flexibility in handling complex validation requirements. Whether you’re working on monolithic or microservice architectures, Yavi fits well into modern development practices, especially in functional and reactive programming paradigms.
1.3 Comparison
Feature | Yavi | Hibernate Validator | Other Validation Frameworks |
---|---|---|---|
API Style | Fluent and programmatic API, allowing highly customizable and dynamic validations. | Annotation-based, ideal for declarative validations tied to JavaBeans properties. | Varies; can include custom annotations, XML configurations, or programmatic approaches. |
Integration | Lightweight and standalone, with minimal dependencies. | Part of the Bean Validation (JSR 380) standard and integrates well with frameworks like Spring and Java EE. | Depends on the framework; some are tightly integrated with specific ecosystems. |
Custom Constraints | Easily supports custom constraints using lambdas and predicates. | Allows creating custom annotations, but the process is more verbose compared to Yavi. | Custom constraint support depends on the framework and its API complexity. |
Conditional Validation | Direct support for conditional validation through programmatic APIs. | Achievable but often requires custom logic or grouped constraints. | Rarely supported natively; typically requires manual logic. |
Error Reporting | Provides detailed and easily customizable error messages for each constraint violation. | Generates standard error messages with limited customization via annotations. | Error reporting mechanisms vary, often requiring additional libraries for detailed messages. |
Performance | High performance for validations as it avoids reflection and annotation processing. | Slightly slower due to annotation-based validation and reflection usage. | Performance varies depending on implementation and feature set. |
Ease of Use | Ideal for developers who prefer fluent, code-based validation logic. | Suitable for projects needing declarative validation with minimal code. | Ease of use depends on the framework’s learning curve and documentation. |
Suitability | Best for projects that require dynamic, programmatic validation of objects. | Best suited for standard Java applications requiring JSR 380 compliance. | Depends on the specific framework’s strengths and project requirements. |
2. Dependencies
To start using Yavi in your Java project, you need to include its dependency in your build configuration. Depending on the build tool you are using, the process may vary slightly. Below are the steps for adding the Yavi dependency using Maven and Gradle.
2.1 Using Maven
Add the following dependency to the <dependencies>
section of your pom.xml
file:
<dependency> <groupId>am.ik.yavi</groupId> <artifactId>yavi-core</artifactId> <version>your__jar__version</version> </dependency>
After adding the dependency, make sure to update your Maven project by running:
mvn clean install
- Or use your IDE’s Maven tool to refresh dependencies.
2.2 Using Gradle
For projects using Gradle, add the following line to the dependencies
section of your build.gradle
file:
implementation 'am.ik.yavi:yavi-core:your__jar__version'
After adding the dependency, refresh your Gradle project by:
- Running
gradle build
or./gradlew build
. - Or using your IDE’s Gradle tool to sync the dependencies.
Note: Ensure that you are using the latest version of Yavi, as updates may introduce new features or fixes.
3. Code Example
The provided code demonstrates the use of Yavi for data validation in a Java application. It is divided into multiple steps to ensure a structured approach to validation.
package com.jcg.example; import am.ik.yavi.builder.ValidatorBuilder; import am.ik.yavi.core.Validator; import am.ik.yavi.core.ConstraintViolations; import am.ik.yavi.constraint.annotation.Constraint; import java.util.Objects; // Step 1: Define the User class public class User { private String name; private int age; private String email; private String phoneNumber; // Constructor public User(String name, int age, String email, String phoneNumber) { this.name = name; this.age = age; this.email = email; this.phoneNumber = phoneNumber; } // Getters public String getName() { return name; } public int getAge() { return age; } public String getEmail() { return email; } public String getPhoneNumber() { return phoneNumber; } // Step 2: Define the Validator public static Validator userValidator() { return ValidatorBuilder.of() // Simple validations .constraint(User::getName, "name", c -> c.notBlank().lessThanOrEqual(50)) .constraint(User::getAge, "age", c -> c.greaterThanOrEqual(18).lessThanOrEqual(65)) .constraint(User::getEmail, "email", c -> c.email()) // Conditional validation: Validate phone number only if age > 30 .constraintOnCondition( user -> user.getAge() > 30, b -> b.constraint(User::getPhoneNumber, "phoneNumber", c -> c.notBlank().pattern("^\\+?[0-9]{10,15}$")) ) // Custom constraint: Ensure name contains at least one uppercase letter .constraint(User::getName, "name", c -> c.predicate( name -> name.chars().anyMatch(Character::isUpperCase), "mustContainUpperCase" )) .build(); } // Step 3: Validate an argument in a method public static void registerUser(User user) { var result = userValidator().validate(user); if (!result.isValid()) { throw new IllegalArgumentException("Validation failed: " + result.errors()); } System.out.println("User registered successfully: " + user.getName()); } public static void main(String[] args) { // Step 4: Test validations User validUser = new User("John Doe", 35, "john.doe@example.com", "+1234567890"); User invalidUser = new User("john", 25, "invalid-email", null); System.out.println("Validating Valid User:"); registerUser(validUser); // Should pass System.out.println("\nValidating Invalid User:"); try { registerUser(invalidUser); // Should fail } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); } } }
3.1 Code Explanation
Below is an explanation of the code:
- Step 1: Define the User Class
- The
User
class is a simple Java bean that represents a user entity with four attributes:name
,age
,email
, andphoneNumber
. - The class includes a parameterized constructor for initializing objects and getter methods to access these attributes.
- This forms the foundation for the validation logic.
- The
- Step 2: Define the Validator
- The
userValidator
method defines a validator using Yavi’s fluent API. This validator applies various constraints:- Simple validations: The
name
must not be blank and can have a maximum length of 50 characters. Theage
must be between 18 and 65, inclusive. Theemail
must follow a valid email format. - Conditional validation: The
phoneNumber
is validated only if the user’sage
is greater than 30. If validated, the phone number must not be blank and must match a specific pattern for phone numbers (e.g., “+1234567890”). - Custom constraint: The
name
must contain at least one uppercase letter. This is implemented using a custom predicate that checks if any character in the name is uppercase.
- Simple validations: The
- The validator is built using the
ValidatorBuilder
class, which provides a fluent and composable API.
- The
- Step 3: Validate an Argument in a Method
- The
registerUser
method takes aUser
object as input and validates it using the previously defineduserValidator
. - If validation fails, an
IllegalArgumentException
is thrown, displaying the validation errors. - If the validation succeeds, the method prints a success message with the user’s name.
- The
- Step 4: Test Validations
- The
main
method tests the validation logic with two user instances:validUser
: A user with all attributes satisfying the validation rules. The program registers this user successfully.invalidUser
: A user with an invalid email format, no uppercase characters in the name, and a missing phone number. The program throws an exception, displaying the validation errors.
- This step demonstrates how Yavi validations are applied and how errors are handled in real scenarios.
- The
3.2 Code Output
3.2.1 Positive case
For a valid user, the following message is displayed:
Validating Valid User: User registered successfully: John Doe
This happens because all validation rules are satisfied:
- Name: “John Doe” is not blank, its length is less than or equal to 50 characters, and it contains at least one uppercase letter.
- Age: 35 is within the valid range (18 to 65).
- Email: “john.doe@example.com” is in a valid email format.
- Phone Number: “+1234567890” matches the specified pattern and is validated because the age is greater than 30.
Since all conditions are met, the user is successfully registered.
3.2.2 Negative case
For an invalid user, the following message is displayed:
Validating Invalid User: Validation failed: [email must be a valid email, name must contain at least one uppercase letter]
This happens because some validation rules are not met:
- Name: “john” violates the custom constraint requiring at least one uppercase letter.
- Age: 25 is valid (between 18 and 65).
- Email: “invalid-email” does not match the valid email format.
- Phone Number: Validation is skipped because the user’s age is ≤ 30. However, this field is irrelevant as other validations already failed.
As a result, the program throws an IllegalArgumentException
, listing the failed constraints:
- “email must be a valid email.”
- “name must contain at least one uppercase letter.”
4. Conclusion
Yavi provides a versatile approach to validation in Java applications, from simple validations to complex conditional and custom constraints. Its functional and fluent API ensures clean and maintainable validation logic. With support for annotations and argument validation, Yavi stands out as a robust solution for developers.