Close

JSF - Using Custom Converter and Validator for the same Input component

[Last Updated: Sep 15, 2017]

This example shows how to use a custom converter and a custom validator for the same input component. The converter is invoked before the validator. The value converted by the converter is passed to the validator for validation. This example will demonstrate the distinct responsibilities of the converter and the validator.

Example

public class Employee {
  private long id;
  private String name;
  private String department;
  private String role;
    .............
}

Creating a Converter

This custom converter will return an employee object either for an input of employee id or for an input of employee name. The 'by' property will be the attribute of the validator tag.

@FacesConverter("empConverter")
public class EmployeeConverter implements Converter {
  private String by;

  @Override
  public Object getAsObject(FacesContext context, UIComponent component, String value) {
      if ("name".equalsIgnoreCase(by)) {
          return EmployeeService.Instance.findByName(value);
      } else if ("id".equalsIgnoreCase(by)) {
          return EmployeeService.Instance.findById(value);
      }
      return null;
  }

  @Override
  public String getAsString(FacesContext context, UIComponent component, Object value) {
      Employee employee = (Employee) value;
      if ("name".equals(by)) {
          return employee.getName();
      } else if ("id".equals(by)) {
          return Long.toString(employee.getId());
      }
      return null;
  }

  public String getBy() {
      return by;
  }

  public void setBy(String by) {
      this.by = by;
  }
}
public enum EmployeeService {
  Instance;
  private List<Employee> list = new ArrayList<>();

  EmployeeService(){
      list.add(new Employee(100, "Sara", "Admin", "Manager"));
      list.add(new Employee(200, "Mike", "IT", "Developer"));
  }

  public Employee findById(String idString) {
      try {
          long id = Long.parseLong(idString);
          return list.stream()
                     .filter(e -> e.getId() == id)
                     .findAny()
                     .orElse(null);
      } catch (NumberFormatException e) {
          return null;
      }
  }

  public Employee findByName(String name) {
      return list.stream()
                 .filter(e -> e.getName().equals(name))
                 .findAny()
                 .orElse(null);
  }
}

The above converter returns null for invalid id or invalid name instead of throwing an exception. That will be the validator responsibility to throw ValidatorException for the invalid input; the validator will treat a null value for the employee as an invalid input.

Creating a Validator

The Employee object returned from our above validator will be passed as input value to this custom validator. Here the validation constraint is that employee object must not be null or must have the role as specified with the validator's attribute 'role' value.

@FacesValidator("empValidator")
public class EmployeeValidator implements Validator {
  private String role;

  @Override
  public void validate(FacesContext context, UIComponent component,
                       Object value) throws ValidatorException {
      String errorMessage = null;
      if (!(value instanceof Employee)) {
          errorMessage = "No employee found.";
      } else if (!((Employee) value).getRole().equalsIgnoreCase(role)) {
          errorMessage = "Employee does not have the role: " + role;
      }

      if (errorMessage != null) {
          FacesMessage msg =
                  new FacesMessage("Invalid Employee Input", errorMessage);
          msg.setSeverity(FacesMessage.SEVERITY_ERROR);
          throw new ValidatorException(msg);
      }
  }

  public String getRole() {
      return role;
  }

  public void setRole(String role) {
      this.role = role;
  }
}

This is also important to understand that if above validation fails then later phases (like 'update model values') are skipped and 'render response' phase is directly executed to show the error message.

Creating converter and validator tags

src\main\webapp\WEB-INF\mylib.taglib.xml

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
        version="2.0">
    <namespace>http://example.com/ui</namespace>
    <tag>
        <tag-name>employeeConverter</tag-name>
        <converter>
            <converter-id>empConverter</converter-id>
        </converter>
        <attribute>
            <name>by</name>
            <type>java.lang.String</type>
        </attribute>
    </tag>
    <tag>
        <tag-name>employeeValidator</tag-name>
        <validator>
            <validator-id>empValidator</validator-id>
        </validator>
        <attribute>
            <name>role</name>
            <type>java.lang.String</type>
        </attribute>
    </tag>
</facelet-taglib>

Registering the tags

src\main\webapp\WEB-INF\web.xml

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
    <context-param>
        <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
        <param-value>/WEB-INF/mylib.taglib.xml</param-value>
    </context-param>
    <context-param>
        <param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
        <param-value>true</param-value>
    </context-param>
</web-app>

The param javax.faces.VALIDATE_EMPTY_FIELDS is false by default, i.e. validator is not invoked for empty or null input. As our converter returns null for invalid input, it is necessary to invoke validator for that scenario as well. So we are setting this parameter to true.

JSF page

src/main/webapp/index.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:u="http://example.com/ui">
<h:head></h:head>
<h:body>
    <h2>JSF custom converter and Validator example</h2>
    <h:form>
        <h:panelGrid columns="3">

            <h:outputLabel for="mgr" value="Manager Id: "/>
            <h:inputText id="mgr" value="#{employeeBean.manager}">
                <u:employeeConverter by="id" />
                <u:employeeValidator role="manager"/>
            </h:inputText>
            <h:message for="mgr" style="color:red"/>

            <h:outputLabel for="dev" value="Developer Name: "/>
            <h:inputText id="dev" value="#{employeeBean.developer}">
                <u:employeeConverter by="name" />
                <u:employeeValidator role="developer"/>
            </h:inputText>
            <h:message for="dev" style="color:red"/>

            <span></span>
            <h:commandButton value="Submit"/>
        </h:panelGrid>
        <div>
            Manager:
            <h:outputText value="#{employeeBean.manager}"/>
        </div>
        <div>
            Developer:
            <h:outputText value="#{employeeBean.developer}"/>
        </div>
    </h:form>
</h:body>
</html>

Managed Bean

@ManagedBean
@ViewScoped
public class EmployeeBean {
  private Employee manager;
  private Employee developer;

  public Employee getManager() {
      return manager;
  }

  public void setManager(Employee manager) {
      this.manager = manager;
  }

  public Employee getDeveloper() {
      return developer;
  }

  public void setDeveloper(Employee developer) {
      this.developer = developer;
  }
}

To try examples, run embedded tomcat (configured in pom.xml of example project below):

mvn tomcat7:run-war

Output

Submitting invalid inputs:

In following screenshot, the first input is correct but second is not. Overall validation fails so model values are not updated.

Submitting valid inputs.

Example Project

Dependencies and Technologies Used:

  • jsf-api 2.2.14: This is the master POM file for Oracle's Implementation of the JSF 2.2 Specification.
  • jsf-impl 2.2.14: This is the master POM file for Oracle's Implementation of the JSF 2.2 Specification.
  • JDK 1.8
  • Maven 3.3.9

JSF Custom Converter and Validator Example Select All Download
  • custom-validator-and-converter-together
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • EmployeeConverter.java
          • webapp
            • WEB-INF

    See Also