JSF validation tutorial

Data validation is a key element of every application and JSF makes no exception to it. In this tutorial we will introduce the basic concepts of JSF validation using the validation options available since JSF 2.0.

NOTE: If you are looking for the latest updates with Jakarta EE Validation, we recommend checking this article: Getting started with Jakarta Bean Validation

Data validation allow you to focus on business logic rather than the tedious qualifications of input data such as null checks, length qualifiers, range boundaries, etc.

Thus, it makes sense, then, that the validation processes happen before component data is bound to your backing bean model in the update model data lifecycle phase. The following picture shows the validation process in the JSF lifecycle.

jsf validation tutorial

The standard data validation

The standard data validation can be applied to allow restrictions on UIInputs. Basically you can apply to the fields to check that they are used in the correct range(minimum-maximum) and that they are not null

Example:

 <h:inputText id="name" value="#{sampleBean.userName}" >
  <f:validateLength minimum="5" maximum="20" />
 </h:inputText>
 
 <h:inputText id="age" required="true"
 value="#{sampleBean.age}">
  <f:validateLongRange minimum="18" maximum="50"/>
 </h:inputText>    
 
 <h:messages style="color:red" />

The above code will check for valid values of the “name” and “age” field, however it displays a generic error message which might not be fit for your purpose. In order to customize your messages, you need to add a message bundle element to your faces-config.xml

 <application>
  <message-bundle>ValidationMessages</message-bundle>
 </application>

This will register the file ValidationMessages.properties as message bundle in your application.
Here are some possible attributes which you can set to define custom messages:

javax.faces.validator.LongRangeValidator.MAXIMUM={1}: Value is greater than allowable maximum of ''{0}''
javax.faces.validator.LongRangeValidator.MINIMUM={1}: Value is less than allowable minimum of ''{0}''
javax.faces.validator.LengthValidator.MAXIMUM={1}: Value is greater than allowable maximum of ''{0}''
javax.faces.validator.LengthValidator.MINIMUM={1}: Value is less than allowable minimum of ''{0}''
javax.faces.component.UIInput.REQUIRED={0}: Field required
javax.faces.validator.LongRangeValidator.NOT_IN_RANGE={2}: Field needs to be between the expected values of {0} and {1}

For the complete list of Message properties available check the Message.properties file which is packed under JBOSS_HOME/modules/system/layers/base/com/sun/jsf-impl/main/jsf-impl-X.x.x.jar  .


Custom validation components

You will need to build your own custom validation components for data types that aren’t supported by the standard JSF validators: for example e-mail addresses, zip codes or complex password validators.

Using a custom validator requires defining a Class which implements the javax.faces.validator.Validator interface:

package sample;
import java.util.regex.*;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.*;

public class ValidatorBean implements Validator
{

    private static final String EMAIL_REGEXP =
            "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";


    @Override
    public void validate(FacesContext context, UIComponent c, Object val) throws ValidatorException
    {
        String email = (String) val;
        Pattern mask = null;
        mask = Pattern.compile(EMAIL_REGEXP);
        Matcher matcher = mask.matcher(email);

        if (!matcher.matches()) {
            FacesMessage message = new FacesMessage();
            message.setDetail("Please enter a valid email");
            message.setSummary("Email not valid");
            message.setSeverity(FacesMessage.SEVERITY_ERROR);
            throw new ValidatorException(message);
        }


    }

}

Then the validator needs to be declared in faces-config.xml

 <validator>
  <validator-id>sample.ValidatorBean"</validator-id>
  <validator-class>sample.ValidatorBean</validator-class>
 </validator>

Then, in your view, you can reference the Validator using the validatorId property:

 <h:inputText id="email" value="#{sampleBean.email}">
  <f:validator validatorId="sample.ValidatorBean"/>
</h:inputText>

Note for JSF 2 users: you can skip completely the configuration in faces-config and simply add the @FacesValidator(“sample.ValidatorBean”) at class level


JSF 2 and Bean validation

The Bean Validation JSR (JSR-303) defines a generic, tier-independent mechanism for specifying data validation constraints. The specification includes several standard constraint annotations (eg. @NotNull, @Size, @Min, @Max, etc…) and also allows custom constraints to be defined.

JSF 2 provides built-in integration with JSR-303 constraints. When you are using bean validation in your application, JSF automatically uses the constraints for beans that are referenced by UIInput values.

Example:

package sample;

import javax.faces.bean.*;
import javax.validation.constraints.*;

@ManagedBean
@RequestScoped
public class SampleBean {

 @Size(min = 1, message = "Please enter the Email")
 @Pattern(regexp = "[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+", message = "Email format is invalid.")
 private String email;

 @Size(min = 5, max=20, message = "Please enter a valid username (5-20 characters)")
 private String userName;

 @Size(min = 5, max = 20, message = "Please enter a valid password (5-10 characters)")
 private String password;

 // getter and setters methods here
 
}

By default, the Bean Validator is enabled, therefore our JSF pages do not need to contain any info about the Bean Validator. When validation constraints fail, any associated error messages are automatically translated into FacesMessages by the JSF implementation.

You can however disable the validator for any field of the ManagedBean

<h:inputText value="#{sampleBean.userName}">
 <f:validateBean disabled="true" />
</h:inputText>

In addition, the validationGroups attribute may be used to manually specify which validation groups should be taken into account when validating a particular component:

 @Size(min = 1, message = "Please enter the Email",groups = sample.MyGroup.class )
 @Pattern(regexp = "[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+", message = "Email format is invalid.")
 private String email;

 @Size(min = 5, max=20, message = "Please enter a valid username (5-20 characters)",groups = sample.MyGroup.class)
 private String userName;

 @Size(min = 5, max = 20, message = "Please enter a valid password (5-20 characters)",groups = sample.MyGroup.class)
 private String password;

Here sample.MyGroups is just a place holder interface which is used to define the group of fields.
Then you can reference the validation groups as follows:

 <h:inputText value="#{sampleBean.userName}">
  <f:validateBean validationGroups="sample.MyGroup.class "/>
 </h:inputText>

Beyond JSF Validation using Seam Faces

All the above validation tactics can be even enhanced by Seam Faces framework. The purpose of Seam Faces is to provide a fully integrated CDI programming model to the JavaServer Faces (JSF) 2.0 web-framework. With features such as observing Events, providing injection support for life-cycle artifacts (FacesContext, NavigationHandler,) and more.

You can learn more about Seam Faces module here.

Performing cross-field validation with Seam Faces is absolutely easy using Dependency Injection and <s:validateForm> component in the form you wish to validate. Here’s an example of it:

<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:f="http://java.sun.com/jsf/core"
 xmlns:s="http://jboss.org/seam/faces">
 <h:form id="form">
   City:  <h:inputText id="city" value="#{bean.city}" /> <br/>
   State: <h:inputText id="state" value="#{bean.state}" /> <br/>
   Zip:   <h:inputText id="zipCode" value="#{bean.zip}" /> <br/>
  <h:commandButton id="submit" value="Submit" action="#{bean.submit}" />
 
  <s:validateForm validatorId="addressValidator" fields="zip=zipCode" />
 </h:form>
</html>

And here’s the Faces Validator:

@FacesValidator("addressValidator")
public class AddressValidator implements Validator
{
 @Inject Directory directory;
 @Inject 
 @InputField String city;
 @Inject 
 @InputField String state;
 @Inject 
 @InputField ZipCode zip;

 

 public void validate(FacesContext context, UIComponent c, Object val) throws ValidatorException {
  if (!directory.exists(city, state, zip))  {
     throw new ValidatorException("Invalid address. Please try again.");
  }
 }
}