Search code examples
jsfjakarta-eebean-validationhibernate-validator

Why is JSF applying the Bean Validation of a shadowed private field?


I have encountered some surprising behaviour in Hibernate Validator and JSF. I would like to know whether the behaviour is a bug, or a misunderstanding in my own expectations.

I have this Facelets page:

<?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">
    <h:body>
        <h:form>
            <h:inputText value="#{backingBean.someClass.someField}"/>
            <h:commandButton value="submit" action="#{backingBean.submit1()}"/>
        </h:form>
    </h:body>
</html>

And I have this backing bean:

import java.util.Set;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.hibernate.validator.constraints.NotEmpty;

@ManagedBean
@RequestScoped
public class BackingBean {

    private SomeClass someClass = new SomeSubClass();

    public SomeClass getSomeClass() {
        return someClass;
    }

    public void setSomeClass(SomeClass someClass) {
        this.someClass = someClass;
    }

    public void submit1() {
        System.out.println("BackingBean: " + someClass.getSomeField());
        ((SomeSubClass) someClass).submit2();
    }

    public static class SomeClass {

        private String someField;

        public String getSomeField() {
            return someField;
        }

        public void setSomeField(String someField) {
            this.someField = someField;
        }
    }

    public static class SomeSubClass extends SomeClass {

        @NotEmpty
        private String someField;

        private void submit2() {
            System.out.println("SomeSubClass: " + someField);
        }
    }

    public static void main(String[] args) {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        SomeClass someClass = new SomeSubClass();
        someClass.setSomeField("ham");
        Set<ConstraintViolation<SomeClass>> errors = validator.validate(someClass);
        for (ConstraintViolation<SomeClass> error : errors) {
            System.out.println(error);
        }

    }
}

Note that SomeSubClass.someField has the same name as a private variable in the parent class. This should not matter because you cannot shadow a private field.

Two things to note:

  1. If you run the main method in BackingBean, the validator will always return a validation error (message "may not be empty") because SomeSubClass.someField is always null. This behaviour seems correct to me.
  2. If you submit the form with a blank value, you will receive a "may not be empty" error message; if you enter a value, it will pass validation, but you will see in the console that the value of SomeSubClass.someField is still null. This behaviour seems incorrect to me.
    • If you change the name of SomeSubClass.someField to someField2, you may submit the form with an empty value. This behaviour seems incorrect to me.

It appears that the JSF's Validation Phase and Hibernate Validator disagree on this behaviour. JSF is applying the @NotEmpty validator of a private field in a subclass to a field of the same name in the parent class, but Hibernate Validator does not show this behaviour when tested in isolation.

Can anyone explain this behaviour? Is it a bug, or a misunderstanding in my own expectations?

Using:

  • GlassFish Server Open Source Edition 3.1.2.2
  • Mojarra 2.1.6
  • Hibernate Validator 4.3.0.Final

Solution

  • JSF uses EL to get/set model values conform Javabean rules. EL uses reflection to inspect and invoke public getters and setters. In other words, #{backingBean.someClass.someField} actually uses getSomeField() and setSomeField() to get/set the model/submitted value. The field being manipulated is then actually the one in SomeClass.

    Bean Validation uses reflection to inspect fields and ignores the presence of public getters/setters. In other words, BV of #{backingBean.someClass.someField} actually takes place on SomeSubClass.

    This explains everything. But I agree that this is confusing and appears "incorrect". It'll work as expected when you also override the getter and setter in SomeSubClass.

    public static class SomeSubClass extends SomeClass {
    
        @NotEmpty
        private String someField;
    
        private void submit2() {
            System.out.println("SomeSubClass: " + someField);
        }
    
        @Override
        public String getSomeField() {
            return someField;
        }
    
        @Override
        public void setSomeField(String someField) {
            this.someField = someField;
        }
    }
    

    This way EL will see it and use that as model value.

    This is however awkward (an @Override which merely calls super will flip static code style analysis tools like Sonar, PMD, Findbugs, etc). A better alternative is to move @NotEmpty to an overridden getter:

    public static class SomeSubClass extends SomeClass {
    
        private void submit2() {
            System.out.println("SomeSubClass: " + getSomeField());
        }
    
        @Override
        @NotEmpty
        public String getSomeField() {
            return super.getSomeField();
        }
    }
    

    BV also supports this construct, so it'll stay working.