Aram Gevorgyan
Aram Gevorgyan

Reputation: 2195

How to perform validation in JSF, how to create a custom validator in JSF

I would like to perform validation in some of my input components such as <h:inputText> using some Java bean method. Should I use <f:validator> or <f:validateBean> for this? Where can I read more about it?

Upvotes: 17

Views: 28698

Answers (1)

BalusC
BalusC

Reputation: 1109522

The standard way is to implement the Validator interface.

@FacesValidator("fooValidator")
public class FooValidator implements Validator<String> {

    @Override
    public void validate(FacesContext context, UIComponent component, String value) throws ValidatorException {
        if (value == null || value.isEmpty()) {
            // Let required="true" handle it.
        }

        // ...

        if (valueIsInvalid) {
            throw new ValidatorException(new FacesMessage("Value is invalid!"));
        }
    }
}

The component argument represents the component to which the validator is attached, which is usually an instance of UIInput. The value argument represents the value to be validated, which is usually the submitted and converted value of the associated UIInput component.

The @FacesValidator will register it to JSF with validator ID myValidator so that you can reference it in validator attribute of any <h:inputXxx>/<h:selectXxx> component as follows:

<h:inputText id="foo" value="#{bean.foo}" validator="fooValidator" />
<h:message for="foo" />

If you consider the value invalid, then just throw ValidatorException. This way the input field will be marked invalid (i.e. UIInput#isValid() will return false), and the bean property behind the value attribute of the input field (the #{bean.foo}) won't be updated with the submitted/converted value, and the message of the ValidatorException will be displayed in the <h:message> associated with the input field.

Do note that checking for null/blank value should be delegated to the required="true" attribute like so:

<h:inputText id="foo" value="#{bean.foo}" validator="fooValidator"
    required="true" requiredMessage="Please fill out foo" />
<h:message for="foo" />

You can also use EL in validator attribute of any <h:inputXxx>/<h:selectXxx> component wherein you reference a managed bean method having exactly the same method signature (the same method arguments) as Validator#validate(). I.e. taking FacesContext, UIComponent and Object arguments in this order.

<h:inputText id="foo" value="#{bean.foo}" validator="#{bean.validateFoo}" />
<h:message for="foo" />
public void validateFoo(FacesContext context, UIComponent component, String value) throws ValidatorException {
    if (value == null || value.isEmpty()) {
        // Let required="true" handle it.
    }

    // ...

    if (valueIsInvalid) {
        throw new ValidatorException(new FacesMessage("Value is invalid!"));
    }
}

This is only useful if the validator needs to access another property present in the same managed bean. If it doesn't need to, then this approach is considered tight-coupling (poor practice thus), and you should split out the validator to its own class implementing the Validator interface.

In case you wish to validate based on multiple input values and/or bean properties, much better is to pass them as <f:attribute> of the component. More detail can be found in the answer to JSF doesn't support cross-field validation, is there a workaround?

You can also use <f:validator> taghandler, which would be the only way if you intend to attach multiple validators on the same component:

<h:inputText id="foo" value="#{bean.foo}">
    <f:validator validatorId="fooValidator" />
</h:inputText>
<h:message for="foo" />

This will execute the @FacesValidator("fooValidator") shown above.

You can also use <f:validator binding> to reference a concrete validator instance somewhere in the EL scope, which can be specified and supplied the following way:

<h:inputText id="foo" value="#{bean.foo}">
    <f:validator binding="#{fooValidator}" />
</h:inputText>
<h:message for="foo" />
@Named("fooValidator")
public class FooValidator implements Validator<String> {

    @Override
    public void validate(FacesContext context, UIComponent component, String value) throws ValidatorException {
        if (value == null || value.isEmpty()) {
            // Let required="true" handle it.
        }

        // ...

        if (valueIsInvalid) {
            throw new ValidatorException(new FacesMessage("Value is invalid!"));
        }
    }
}

Note that thus @Named is being used instead of @FacesValidator. The old @ManagedBean is also supported here instead of @Named. Historically, this was a trick in order to be able to use @EJB and @Inject in a validator. See also How to inject in @FacesValidator with @EJB, @PersistenceContext, @Inject, @Autowired

Or this way, which in turn can easily be supplied as a lambda:

<h:inputText id="foo" value="#{bean.foo}">
    <f:validator binding="#{bean.fooValidator}" />
</h:inputText>
<h:message for="foo" />
public Validator<String> getFooValidator() {
    return (context, component, value) -> {
        if (value == null || value.isEmpty()) {
            // Let required="true" handle it.
        }

        // ...

        if (valueIsInvalid) {
            throw new ValidatorException(new FacesMessage("Value is invalid!"));
        }
    };
}

Also here applies the same problem of tight-coupling when this validator doesn't need any other property from the same bean.

To get a step further, you can use JSR303 bean validation. This validates fields based on annotations. So you can have just a

@Foo
private String foo;

Without the need to explicitly register any validator in XHTML side. If you're using JPA for persistence, by default this validator will also be executed during insert/update in DB. Since it's going to be a whole story, here are just some links to get started:

There's also a <f:validateBean> tag, but this is only useful if you intend to disable the JSR303 bean validation. You then put the input components (or even the whole form) inside <f:validateBean disabled="true">.

Upvotes: 39

Related Questions