Skip to main content

How to perform JavaBeans Validation

feature-javabeans-validation

2023年5月1日

0 分で読めます

JavaBeans Validation (Bean Validation) is a validation model available since the introduction of the Java Enterprise Edition 6 platform. It provides a standard way for different Java applications to validate input data. In a previous post, we discussed how Java DTOs are used in modern applications to prevent accidental data leaks.

In this article, we’ll create a new Java application and validate some example strings to demonstrate how JavaBean Validation works. But first, let’s review what Beans are. 

JavaBeans are Java objects containing properties and methods to store and transfer data between different parts of a Java application. Beans are reusable components encapsulating a Java application’s data and behavior. They’re a standard way of representing objects, following certain design patterns and conventions. 

  • JavaBeans contain properties, which are instance variables representing an object’s state. These variables should be private. They use getter and setter methods to access the data stored in a Bean.

  • No-argument constructors are used to create an instance of a Bean. We can add constructors to provide different ways of initializing a Bean.

  • JavaBeans are serializable, meaning their state can be saved to a file or transmitted over a network.

  • JavaBeans follow naming conventions, such as using the getXXX and setXXX methods to access and set the value of the properties.

  • JavaBeans often contain BeanInfo classes, which provide information about the bean to tools and IDEs.

We often use JavaBeans as building blocks to organize and structure data for larger applications. They typically contain instance variables (properties) and getter and setter (getXXX and setXXX) methods to access the data stored in a Bean. We also use JavaBeans to create custom objects we can reuse in different parts of an application, making it easier to maintain and update the code.

Using JavaBeans Validation

One of the primary uses of Beans is to validate user input by enabling us to specify constraints on the properties of a Bean, such as minimum and maximum lengths, allowed characters, and required fields. We then use these constraints to validate the data entered by users, ensuring that it meets the specified requirements before application processing.

The following example uses the Hibernate Validator in an Apache Maven project. The Hibernate Validator is the reference implementation for Bean Validation.

Creating a Java app

First, we need to create a dependency for the Hibernate Validator in our pom.xml file in Maven. Do so using the code below:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.5.Final</version>
</dependency>

Next, we add the Bean Validation API as a dependency in pom.xml using the following code:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

Now, we create a Java project in a new file called BeanValidation.java and import the necessary libraries:

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.constraints.Pattern;
import java.util.Set;
import javax.validation.ConstraintViolation;

Next, we create a simple Bean with a property to validate using the code below:

public class ExampleBean {
    @NotNull
    @Size(min=2, max=10)
    private String exampleString;

//Getters
    public String getExampleString() {
        return exampleString;
    }

//Setters
    public void setExampleString(String exampleString) {
        this.exampleString = exampleString;
    }
}

This example uses the @NotNull and @Size annotations to specify that the exampleString property can’t be null and must have a minimum length of two and a maximum length of ten characters. We establish the getExampleString and setExampleStrin methods as the getters and setters for the exampleString property in the ExampleBean class.

Seeing how Bean Validation works

To use the BeanValidation class to validate the Bean, we run the following:

public class BeanValidation {
    public static void main(String[] args) {
        ExampleBean bean = new ExampleBean();
        bean.setExampleString("hello");
// Create a Validator instance using the default validation factory      
  ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
// Validate an instance of the ExampleBean class and get a set of constraint violations, if any        
Set<ConstraintViolation<ExampleBean>> violations = validator.validate(bean);
        if (!violations.isEmpty()) {
            System.out.println("Errors:");
            for (ConstraintViolation<ExampleBean> violation : violations) {
                System.out.println(violation.getMessage());
            }
        } else {
            System.out.println("Validation successful");
        }
    }
}

Since we’ve set the string value to hello, it passes the validation check. If we set the exampleString property to a value that doesn’t meet the constraints specified in a Bean, the validate method returns a set of ConstraintViolation objects that describe the errors.

To handle errors when strings fail validation, we create error-handling cases that check for violations and display an error message if one occurs.

Here’s the complete code for the BeanValidation.java file:

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.ConstraintViolation;

public class ExampleBean {
    @NotNull
    @Size(min=2, max=10)
    private String exampleString;

    public String getExampleString() {
        return exampleString;
    }

    public void setExampleString(String exampleString) {
        this.exampleString = exampleString;
    }
}

public class BeanValidation {
    public static void main(String[] args) {
        ExampleBean bean = new ExampleBean();
        bean.setExampleString("hello");

        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        Set<ConstraintViolation<ExampleBean>> violations = validator.validate(bean);

        if (!violations.isEmpty()) {
            System.out.println("Errors:");
            for (ConstraintViolation<ExampleBean> violation : violations) {
                System.out.println(violation.getMessage());
            }
        } else {
            System.out.println("Validation successful");
        }
    }
}

This example returns an error if the input string is shorter than two characters, longer than ten characters, or null. If the string doesn’t match those constraints, it prints an error message displaying the unmet requirement.

Here’s an example of constraints for validating user login input with the email and password fields:

public class UserBean {
    @NotNull
    @Size(min=5, max=30)
    @Pattern(regexp = "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}")
    private String email;

    @NotNull
    @Size(min=8, max=15)
    @Pattern(regexp = "((?=.*[a-z])(?=.*\\d)(?=.*[A-Z])(?=.*[@#$%!]).{8,15})")
    private String password;

}

The UserBean class contains two properties: email and password. The email property ensures that inputs have a minimum length of five and a maximum length of thirty characters, and match the pattern of a valid email address. 

The password property ensures that the password isn’t null, has a minimum length of eight and a maximum length of fifteen characters, and contains at least one lowercase letter, one uppercase letter, one digit, and one special character. 

Understanding constraints

Constraints are defined using annotations — special types of metadata added to Java classes, fields, and methods. We can apply constraints to the properties of a Bean class in various places, including directly to the property, to a getter method for the property, or to a constructor parameter of the class.

Once constraints are defined for a Bean, validation uses a Validator object from the Bean Validation API. The Validator object is responsible for checking whether a Bean instance meets the constraints on its properties. If a Bean violates any constraints, the Validator returns a set of constraint violations indicating which properties failed validation and why.

JavaBeans have some built-in constraints available by default as part of the Bean Validation API, which we can use immediately. Some of the built-in constraints include @NotNull, @Size, @Pattern, @Min, @Max, @DecimalMin, and@DecimalMax. For a complete list of built-in constraints, review the Hibernate Validator documentation.

Custom constraints are constraints we define to meet specific validation requirements. We create them by defining a custom annotation and implementing a ConstraintValidator that defines the validation logic. 

Creating custom annotations

To create a custom constraint for Bean Validation, we must:

  • Create a custom annotation class and annotate it with @Constraint. This indicates that the annotated class is a custom constraint.

  • Implement the validation logic in a class that implements the ConstraintValidator interface. The class must have parameters with custom constraint annotation and the type of elements it can validate.

  • Register the custom constraint by adding an entry in the validation XML configuration file or annotating the custom constraint class with @Constraint(validatedBy=<Validator class>).

Here’s an example of a custom constraint named ValidSearchTerm:

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

@Documented
@Constraint(validatedBy = ValidSearchTermValidator.class)
@Target({ FIELD })
@Retention(RUNTIME)
public @interface ValidSearchTerm {

    String message() default "Search term contains invalid characters";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

We can then implement the validation logic that uses regular expressions to create the constraint. In this case, we want an ordinary alphanumeric search string that doesn’t have any special characters:

public class ValidSearchTermValidator implements ConstraintValidator<ValidSearchTerm, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        return value.matches("[A-Za-z0-9]+");
    }
}

Then, we use the custom constraint in a Bean like this:

public class SearchBean {
    @ValidSearchTerm
    private String searchTerm;

    public String getSearchTerm() {
        return searchTerm;
    }

    public void setSearchTerm(String searchTerm) {
        this.searchTerm = searchTerm;
    }
}

Final thoughts on Bean Validation

Bean Validation provides a unified and consistent way of validating data in Java applications, making it easier for us to write and maintain code that ensures secure data processing. We can use the Bean Validation model to save time and effort otherwise spent writing custom validation logic.

With Bean Validation, we can specify constraints on the properties of a JavaBean using annotations. These annotations can be either built-in or custom, defining user input requirements to be considered valid. The model then uses these constraints to validate the Bean and identify any errors.

Bean Validation also provides a way for us to handle errors during validation. The framework returns a list of constraint violations, and we can use this information to provide specific feedback to a user. This helps to improve the user experience and provides a more robust and secure application.

Overall, Bean Validation is an essential tool for ensuring the quality and security of Java applications. By following best practices with this model, we can ensure that our applications are well-designed, maintainable, and secure.