Chapter 6. Implementing Constraint Validation in a Java Web App

Table of Contents

1. Java Annotations for Persistent Data Management and Constraint Validation
1.1. JPA database constraint annotations
1.2. Bean validation annotations
2. New Issues
3. Make a JPA Entity Class Model
4. Style the User Interface with CSS
5. Write the Model Code
5.1. Type mapping
5.2. Encode the constraints as annotations
5.3. Checking uniqueness constraints
5.4. Dealing with model related exceptions
5.5. Requiring non-empty strings
6. Validation in the View Layer
6.1. Form validation for the Create Object use case
6.2. Form validation for the Update Object use case
7. Defining a Custom Validation Annotation
8. Run the App and Get the Code
9. Possible Variations and Extensions
9.1. Object-level constraint validation
9.2. Alternative class level custom constraint validation

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 second part of the tutorial we show how to express integrity constraints in a Java model class, and how to have constraints validated transparently on critical lifecycle events of bean objects (such as persist) with JPA, and on form submission with JSF.

We again consider the single-class data management problem that was considered in Part 1 of this tutorial. So, again, the purpose of our app is to manage information about books. But now we also consider the data integrity rules (also called 'business rules') that govern the management of book data. These integrity rules, or constraints, can be expressed in a UML class diagram as as shown in Figure 6.1 below.

Figure 6.1. A platform-independent design model with the class Book and two invariants

A platform-independent design model with the class Book and two invariants

In this simple model, the following constraints have been expressed:

  1. Due to the fact that the isbn attribute is declared to be the standard identifier of Book, it is mandatory and unique.

  2. The isbn attribute has a pattern constraint requiring its values to match the ISBN-10 format that admits only 10-digit strings or 9-digit strings followed by "X".

  3. The title attribute is mandatory.

  4. The year attribute is mandatory and has an interval constraint, however, of a special form since the maximum is not fixed, but provided by the calendaric function nextYear().

Notice that the edition attribute is not mandatory, but optional, as indicated by its multiplicity expression [0..1]. In addition to the constraints described in this list, there are the implicit range constraints defined by assigning the datatype NonEmptyString as range to isbn and title, Integer to year, and PositiveInteger to edition.

The meaning of the design model can be illustrated by a sample data population for the model class Book:

Table 6.1. Sample data for Book

ISBN Title Year Edition
006251587X Weaving the Web 2000 3
0465026567 Gödel, Escher, Bach 1999 2
0465030793 I Am A Strange Loop 2008

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/JPA/JSF apps support this goal to some degree. There are two types of constraint annotations:

  1. Database constraint annotations, which are part of JPA, 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 database constraint annotations

The JPA database constraint annotations specify constraints to be used by the underlying database 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 values of the itemCode column allowed in the generated items table are mandatory and 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 Java's Bean Validation approach, Java runtime validation can be defined in the form of bean validation annotations placed on a property, method, or class.

Table 6.2. JavaBean 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 n the past (@Past).

All bean validation annotations have an optional message attribute for defining a specific error message. In the following example, we add two @NotNull annotations with messages, a @Size and a @Min annotation to the database 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;
}