How to perform 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
andsetXXX
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.