Core Java

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

FeatureYaviHibernate ValidatorOther Validation Frameworks
API StyleFluent 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.
IntegrationLightweight 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 ConstraintsEasily 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 ValidationDirect support for conditional validation through programmatic APIs.Achievable but often requires custom logic or grouped constraints.Rarely supported natively; typically requires manual logic.
Error ReportingProvides 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.
PerformanceHigh 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 UseIdeal 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.
SuitabilityBest 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, and phoneNumber.
    • The class includes a parameterized constructor for initializing objects and getter methods to access these attributes.
    • This forms the foundation for the validation logic.
  • 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. The age must be between 18 and 65, inclusive. The email must follow a valid email format.
      • Conditional validation: The phoneNumber is validated only if the user’s age 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.
    • The validator is built using the ValidatorBuilder class, which provides a fluent and composable API.
  • Step 3: Validate an Argument in a Method
    • The registerUser method takes a User object as input and validates it using the previously defined userValidator.
    • 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.
  • 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.

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.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button