Chapter 9. Implementing Constraint Validation in a Java EE Web App

Table of Contents

1. Java Annotations for Persistent Data Management and Constraint Validation
1.1. JPA constraint annotations
1.2. Bean Validation annotations
2. New Issues
3. Make an Entity Class Model
4. Write the Model Code
4.1. Type mapping
4.2. Code the constraints as annotations
4.3. Checking uniqueness constraints
4.4. Dealing with model-related exceptions
4.5. Requiring non-empty strings
5. Write the View Code
5.1. Validation in the Create use case
5.2. Validation in the Update use case
6. Defining a Custom Validation Annotation
7. Run the App and Get the Code
8. Possible Variations and Extensions
8.1. Object-level constraint validation
8.2. JSF custom validators
9. Practice Projects
9.1. Project 1 - Validate movie data
9.2. Project 2 - Validate country data
10. Quiz Questions
10.1. Question 1: String length validation
10.2. Question 2: Requiring a value
10.3. Question 3: Defining a custom validation annotation
10.4. Question 4: JSF custom validator method
10.5. Question 5: Show validation error messages with JSF

The minimal web app that we have discussed in Part 1 has been limited to support the minimum functionality of a data management app only. For instance, it did not take care of preventing the user from entering invalid data into the app's database. In this chapter, we show how to express integrity constraints in a Java EE model class (called entity class), and how to have constraints automatically validated on critical life cycle events of entity objects with JPA, and on form submission with JSF.

1. Java Annotations for Persistent Data Management and Constraint Validation

The integrity constraints of a distributed app have to be checked both in model classes and in the underlying database, and possibly also in the UI. However, this requirement for three-fold validation should not imply having to define the same constraints three times in three different languages: in Java, in SQL and in HTML5/JavaScript. Rather, the preferred approach is to define the constraints only once, in the model classes, and then reuse these constraint definitions also in the underlying database and in the UI. Java EE apps support this goal to some degree. There are two types of constraint annotations:

  1. JPA constraint annotations specify constraints to be used for generating the database schema (with CREATE TABLE statements) such that they are then checked by the DBMS, and not by the Java runtime environment;

  2. Bean Validation annotations specify constraints to be checked by the Java runtime environment

In this section we discuss how to use some of the predefined constraint annotations and how to define a custom constraint annotation for the year property of the Book class, since its value has an upper bound defined by an expression ('next year').

1.1. JPA constraint annotations

The JPA constraint annotations specify constraints to be used by the underlying database management system after generating the database schema, but not for Java validation. Consider the @Id annotation in the following definition of an entity class Item:

@Entity @Table( name="items")
public class Item {
  @Id private String itemCode;
  private int quantity;
  ...  // define constructors, setters and getters
}

The @Id annotation of the itemCode attribute is mapped to a SQL primary key declaration for this attribute in the corresponding database table schema. As a consequence, the itemCode column of the generated items table must have a value in each row and these values have to be unique. However, these conditions are not checked in the Java runtime environment. JPA generates the following CREATE TABLE statement:

CREATE TABLE IF NOT EXISTS items (
  itemCode varchar(255) NOT NULL PRIMARY KEY,
  quantity int(11) DEFAULT NULL
)

Since nothing has been specified about the length of itemCode strings, the length is set to 255 by default. However, in our case we know that itemCode has a fixed length of 10, which can be enforced by using the @Column annotation, which has the following parameters:

  • name allows to specify a name for the column to be used when the table is created (by default, the attribute name of the entity class is used);

  • nullable is a boolean parameter that defines if the column allows NULL values (by default, it is true);

  • length is a positive integer, specifying the maximum number of characters allowed for string values of that column (by default, this is 255);

  • unique is a boolean parameter that defines if the values of the column must be unique (by default, it is false).

Using the @Column annotation, the improved Java/JPA code of the model class is:

@Entity @Table( name="items")
public class Item {
  @Id @Column( length=10)
  private String itemCode;
  @Column( nullable=false)
  private int quantity;
  ...  // define constructors, setters and getters
}

As a result, the generated CREATE TABLE statement now contains the additional constraints expressed for the columns itemCode and quantity:

CREATE TABLE IF NOT EXISTS items (
  itemCode varchar(10) NOT NULL PRIMARY KEY,
  quantity int(11) NOT NULL
)

1.2. Bean Validation annotations

In the Java EE Bean Validation approach, Java runtime validation can be defined in the form of bean validation annotations placed on a property, method, or class.

Table 9.1. Bean Validation annotations for properties

Constraint Type Annotations Examples
String Length Constraints @Size @Size( min=8, max=80) String message;
Cardinality Constraints (for arrays, collections and maps) @Size @Size( min=2, max=3) List<Member> coChairs;
Mandatory Value Constraints @NotNull @NotNull String name
Range Constraints for numeric attributes @Digits @Digits( integer=6, fraction=2) BigDecimal price;
Interval Constraints for integer-valued attributes @Min and @Max @Min(5) int teamSize
Interval Constraints for decimal-valued attributes @DecimalMin and @DecimalMax @DecimalMax("30.00") double voltage
Pattern Constraints @Pattern @Pattern( regexp="\\b\\d{10}\\b") String isbn;

In addition, there are annotations that require a date value to be in the future (@Future ) or in the past (@Past).

All Bean Validation annotations have an optional message attribute for defining a custom error message. In the following example, we add two @NotNull annotations with messages, a @Size and a @Min annotation to the JPA constraint annotations. The @NotNull annotations constrain the itemCode and the quantity attributes to be mandatory, while the @Min annotation constrains the quantity attribute to have a minimum value of 0:

@Entity @Table( name="items")
public class Item {
  @Id @Column( length=8)
  @NotNull( message="An item code is required!")
  @Size( min=8, max=8)
   private String itemCode;
  @Column( nullable=false)
  @NotNull( message="A quantity is required!")
  @Min(0)
   private int quantity;
}

Notice that that we need some duplicate logic in this example because the same constraints may have to be defined twice: as a JPA constraint annotation and as a Bean Validation annotation. For instance, for a mandatory attribute like quantity we have both a @Column( nullable=false) JPA constraint annotation and a @NotNull Bean Validation annotation.