Support multi-field validation
            by enabling class-level bean validation on CDI based backing
            beans.  This feature causes a temporary copy of the bean
            referenced by the value attribute, for the sole
            purpose of populating the bean with field values already
            validated by <f:validateBean /> and then
            performing class-level validation on the copy.  Regardless
            of the result of the class-level validation, the copy is
            discarded.  This feature must explicitly be enabled by
            setting the application parameter specified in the javadoc
            for the symbolic constant
            jakarta.faces.validator.BeanValidator.ENABLE_VALIDATE_WHOLE_BEAN_PARAM_NAME.
            If this parameter is not set, or is set to false, this tag
            must be a no-op.  A non-normative example follows the
            specification of the feature.
At a high level, the feature provides for
            a UIInput subclass that maintains its own
            special private Validator that uses information
            from one or more <f:validateBean />s to
            perform class-level bean validation.  For discussion, this
            special Validator is called
            the wholeBeanValidator.
This tag must be backed by a UIInput
            component with the following specializations.
Override getSubmittedValue() to return a
            non-null non empty String.  This
            allows UIInput.validate() to
            call wholeBeanValidator.validate().
Override setConverter() to be a no-op.
        It does not make sense to allow a converter to be
        installed.
Override addValidator() to be a no-op
        unless the argument is an instance
        of wholeBeanValidator.  It does not make sense to
        allow additional validators to be installed.
Override validate() to take the
            following actions.
If the feature is not enabled, return immediately.
If the wholeBeanValidator has not yet
            been installed, instantiate and pass it to 
                this.addValidator().
Call super.validate().
The wholeBeanValidator must have
            a validate() method that performs the following
            actions.  Due to the above specification, this method will
            only ever be passed the special UIInput
            component.
Resolve the value of the component to
            its Object.  Assume that
            this value is the bean whose properties are
            intended to be populated by components whose values are each
            validated by <f:validateBean /> tags.
            For discussion, this bean is called the candidate
            bean and the properties and their respective values are
            called the candidate values.  If the candidate
            bean cannot be referenced, return immediately
            from validate().  Use the information recorded
            by each of those <f:validateBean /> tags
            to ensure that none of the candidate values are
            invalid.  If any of them are invalid, return immediately
            from validate().  This ensures class-level
            validation is only performed on an instance whose fields are
            all individually valid.
Otherwise it can be assumed that all field-level validations for this class-level validation have passed.
Class-level bean validation must operate on a sufficiently populated bean instance. This differs from Faces field-level validation, which prevents beans from being populated with invalid values. To accomodate this difference, the candidate bean must be copied, populated with the already-validated candidate values, and then subjected to class-level validation. The copying must proceed in the following order.
Invoke the newInstance() method on the
            bean's Class.  If this throws
            any Exception, swallow it and
            continue.
If the bean implements Serializable, use
            that to copy the bean instance.
Otherwise, if the bean
            implements Cloneable, clone the bean
            instance.
Otherwise, if the bean has a copy constructor, use that to copy the bean instance.
If none of these techniques yields a copy,
            throw FacesException.
Populate the copied bean with the candidate values.
Obtain a reference to
            a jakarta.validation.Validator instance using the
            same steps described in the javadoc
            for jakarta.faces.validator.BeanValidator.validate().
            Let the instance be called beanValidator for
            discussion.
Obtain the value of the validationGroups
            attribute using the same steps described in the javadoc
            for jakarta.faces.validator.BeanValidator.validate().
            If this value is not present or not valid,
            throw FacesException.
Call the validate method on
            beanValidator, passing the populated copied bean
            and the validation groups as arguments.  The copied bean can
            be discarded at this point.
If the
            returned Set<ConstraintViolation> is
            non-empty, for each element in the Set, create
            a FacesMessage where the summary and detail are
            the return from
            calling ConstraintViolation.getMessage().
            Capture all such FacesMessage instances into
            a Collection and pass them
            to ValidatorException.  Using information
            recorded by the <f:validateBean />
            tag(s), call setValid(false) on all of the
            components whose values contributed to this class-level
            validation.  This is essential to prevent the invalid value
            from being set into the model during the update model values
            phase.  Finally, throw the exception.
This tag must be placed in the component tree after all of the fields that are to be included in the multi-field validation. If this precondition is not met, the results of applying this tag are unspecified.
This tag must be used in concert
            with <f:validateBean /> and Bean
            Validation. Here is a brief example of the common case of
            ensuring two password fields are individually valid and also
            both the same.  The feature requires the use of
            the validationGroups attribute on all of
            the <f:validateBean /> tags and
            the <f:validateWholeBean /> tag.
First, the ConstraintValidator
            implementation.
public class PasswordValidator implements ConstraintValidator<Password, PasswordHolder> {
  @Override
  public void initialize(Password constraintAnnotation) { }
  @Override
  public boolean isValid(PasswordHolder value, ConstraintValidatorContext context) {
    boolean result;
    
    result = value.getPassword1().equals(value.getPassword2());
    return result;
  }
}
            Note that a PasswordHolder instance is
            passed to the isValid() method.  This method
            will only be called if the individual properties of
            the PasswordHolder are valid.  This fact allows
            the isValid() method to inspect the properties
            and perform effective class-level validtion.
Next, the Constraint.
@Constraint(validatedBy=PasswordValidator.class)
@Target(TYPE)
@Retention(RUNTIME)
@interface Password {
    String message() default "Password fields must match";
    Class[] groups() default {};
    Class[] payload() default {};
}
            Now the backing bean constrained by
            this Constraint.  Note the use
            of groups.  Note the fact that the bean
            implements Cloneable.
@Named
@RequestScoped
@Password(groups = PasswordValidationGroup.class)
public class BackingBean implements PasswordHolder, Cloneable {
    
    private String password1;
    
    private String password2;
    public BackingBean() {
        password1="";
        password2="";
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        BackingBean other = (BackingBean) super.clone();
        other.setPassword1(this.getPassword1());
        other.setPassword2(this.getPassword2());
        return other;
    }
    
    @NotNull(groups=PasswordValidationGroup.class)
    @Size(max=16, min=8, message="Password must be between 8 and 16 characters long",
            groups = PasswordValidationGroup.class)
    @Override
    public String getPassword1() {
        return password1;
    }
    public void setPassword1(String password1) {
        this.password1 = password1;
    }
    @NotNull(groups=PasswordValidationGroup.class)
    @Size(max=16, min=8, message="Password must be between 8 and 16 characters long",
            groups = PasswordValidationGroup.class)
    @Override
    public String getPassword2() {
        return password2;
    }
    public void setPassword2(String password2) {
        this.password2 = password2;
    }
    
}
            Finally, the Facelets view.
<h:panelGrid columns="2">
    <h:outputText value="Password" />  
    <h:inputSecret id="password1" value='#{backingBean.password1}'>
        <f:validateBean validationGroups="PasswordValidationGroup" />
    </h:inputSecret>
    
    <h:outputText value="Password again" /> 
    <h:inputSecret id="password2" value='#{backingBean.password2}'>
        <f:validateBean validationGroups="PasswordValidationGroup" />
    </h:inputSecret>
    
</h:panelGrid>
<f:validateWholeBean value='#{backingBean}' 
   validationGroups="PasswordValidationGroup" />
            | Info | Value | 
|---|---|
| Component Type | com.sun.faces.ext.validateWholeBean | 
                        
| Handler Class | None | 
| Renderer Type | None | 
| Description | None | 
| Name | Required | Type | Description | 
|---|---|---|---|
disabled | false | jakarta.el.ValueExpression
                                (must evaluate to java.lang.Boolean)
					 | 
                 A boolean value enabling or disabling this validation component.  | 
                        
id | false | jakarta.el.ValueExpression
                                (must evaluate to java.lang.String)
					 | 
                 Component identifier of the UIInput component to be created.  | 
                        
validationGroups | true | jakarta.el.ValueExpression
                                (must evaluate to java.lang.String)
					 | 
                 A comma-separated list of validation groups. A validation group is a fully-qualified class name.  | 
                        
value | true | jakarta.el.ValueExpression
                                (must evaluate to java.lang.Object)
					 | 
                 A ValueExpression referencing the bean to be validated.  |