Cross field validation with HibernateValidator displays no error messages

12,061

Could you try your isValid method to be like this? (this is certainly working for me in live project):

 public boolean isValid(final Object value, final ConstraintValidatorContext cvc){
    boolean toReturn = false;

    try{
        final Object firstObj = BeanUtils.getProperty(value, firstFieldName );
        final Object secondObj = BeanUtils.getProperty(value, secondFieldName );

        //System.out.println("firstObj = "+firstObj+"   secondObj = "+secondObj);

        toReturn = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
    }
    catch (final Exception e){
        System.out.println(e.toString());
    }
    //If the validation failed
    if(!toReturn) {
        cvc.disableDefaultConstraintViolation();
        //In the initialiaze method you get the errorMessage: constraintAnnotation.message();
        cvc.buildConstraintViolationWithTemplate(errorMessage).addNode(firstFieldName).addConstraintViolation();
    }
    return toReturn;
}

Also I see that you are implementing the ConstraintValidator interface with an Object, literally. It should be the backing object that you have from your form:

tempBean // the one that you specify in the commandName actually.

So you implementation should like this:

 implements ConstraintValidator<FieldMatch, TempBean>

This is probably not the issue here, but as a future reference, this is how it should be.

UPDATE

Inside your FieldMatch interface/annotation you have two methods : first and second, add one more called errorMessage for example:

  Class<? extends Payload>[] payload() default {};

/**
 * @return The first field
 */
String first();

/**
 * @return The second field
 */
String second();

/**
  @return the Error Message
 */
String errorMessage

Look inside you method from the Validation class - you are getting the first and second field names there., so just add the errorMessage, like this for example:

  private String firstFieldName;
  private String secondFieldName;
  //get the error message name
  private String errorMessagename; 
public void initialize(final FieldMatch constraintAnnotation)
{
    firstFieldName = constraintAnnotation.first();
    secondFieldName = constraintAnnotation.second();
    errorMessageNAme = constraintAnnotation.errorMessage(); 

    //System.out.println("firstFieldName = "+firstFieldName+"   secondFieldName = "+secondFieldName);
}

Inside isValida get it, the same way you do for first and second field name and use it.

Share:
12,061
Tiny
Author by

Tiny

Just an orphan kid and have no more to say. Three things in general, cannot be avoided (at least I can never) Mother Mother-tongue Mother-land. They are always unique. I'm a family-less boy. My family was hunted leaving me all alone when my house targeted and deliberately set on a fire by a mob during a nonsense communal riot but I was survived by a rescue team with the help of firemen. As a survival, I didn't know whether it was my fortune or misfortune but when I recovered, the rescue team came to my home, one day. One of the members gave me a piece of paper in my hand in which the following text was written. lifeisnowhere. He asked me to read it carefully and I could hardly interpret the text as Life is now here, instead of Life is nowhere. All of them gave me a cute smile and went away and I decided to live peacefully and hopefully on their saying from then onwards and very soon. Because of this tragedy, I'm alone couldn't join a school but a curiosity to learn something made me a self-learner. I'm indeed a self-learner, so I'm likely not able to answer any questions on this site right now. In the field of computer science, my self-study mainly includes, QBASIC, C, C++, C#, VB, Java, JavaScript, PHP and a little about ASP.NET. Oracle, MySQL and MSSQL-Server with DBMS. and other theoretical subjects. I'm currently dealing with - Android and Java EE including Servlet, JSP-JSTL/EL (with Spring and Struts with ORM models JPA/Hibernate) and JSF.

Updated on June 17, 2022

Comments

  • Tiny
    Tiny almost 2 years

    I'm validating two fields, "password" and "confirmPassword" on the form for equality using HibernateValidator as specified in this answer. The following is the constraint descriptor (validator interface).

    package constraintdescriptor;
    
    import constraintvalidator.FieldMatchValidator;
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.Documented;
    import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
    import static java.lang.annotation.ElementType.TYPE;
    import java.lang.annotation.Retention;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    import java.lang.annotation.Target;
    
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Constraint(validatedBy = FieldMatchValidator.class)
    @Documented
    public @interface FieldMatch
    {
        String message() default "{constraintdescriptor.fieldmatch}";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
    
        /**
         * @return The first field
         */
        String first();
    
        /**
         * @return The second field
         */
        String second();
    
        /**
         * Defines several <code>@FieldMatch</code> annotations on the same element
         *
         * @see FieldMatch
         */
        @Target({TYPE, ANNOTATION_TYPE})
        @Retention(RUNTIME)
        @Documented
        public @interface List{
            FieldMatch[] value();
        }
    }
    

    The following is the constraint validator (the implementing class).


    package constraintvalidator;
    
    import constraintdescriptor.FieldMatch;
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import org.apache.commons.beanutils.BeanUtils;
    
    public final class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object>
    {
        private String firstFieldName;
        private String secondFieldName;
    
        public void initialize(final FieldMatch constraintAnnotation) {
            firstFieldName = constraintAnnotation.first();
            secondFieldName = constraintAnnotation.second();
            //System.out.println("firstFieldName = "+firstFieldName+"   secondFieldName = "+secondFieldName);
        }
    
        public boolean isValid(final Object value, final ConstraintValidatorContext cvc) {
            try {
                final Object firstObj = BeanUtils.getProperty(value, firstFieldName );
                final Object secondObj = BeanUtils.getProperty(value, secondFieldName );
                //System.out.println("firstObj = "+firstObj+"   secondObj = "+secondObj);
                return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
            }
            catch (final Exception e) {
                System.out.println(e.toString());
            }
            return true;
        }
    }
    

    The following is the validator bean which is mapped with the JSP page (as specified commandName="tempBean" with the <form:form></form:form> tag).

    package validatorbeans;
    
    import constraintdescriptor.FieldMatch;
    import javax.validation.constraints.Size;
    import org.hibernate.validator.constraints.NotEmpty;
    
    @FieldMatch.List({
        @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match", groups={TempBean.ValidationGroup.class})
    })
    
    public final class TempBean
    {        
        @NotEmpty(groups={ValidationGroup.class}, message="Might not be left blank.")
        private String password;
        @NotEmpty(groups={ValidationGroup.class}, message="Might not be left blank.")
        private String confirmPassword;
    
        public interface ValidationGroup {};
    
        //Getters and setters                
    }
    

    UPDATE

    It's all working correctly and does the validation intended. Just one thing remains is to display the specified error message above the TempBean class within @FieldMatch is not being displayed i.e only one question : how to display error messages on the JSP page when validation violation occurs?

    (the annotation @NotEmpty on both of the fields password and confirmPassword in the TempBean class works and displays the specified messages on violation, the thing is not happening with @FieldMatch).

    I'm using validation group based on this question as specified in this blog and it works well causing no interruption in displaying error messages (as it might seem to be).


    On the JSP page these two fields are specified as follows.

    <form:form id="mainForm" name="mainForm" method="post" action="Temp.htm" commandName="tempBean">
    
        <form:password path="password"/>
        <font style="color: red"><form:errors path="password"/></font><br/>
    
        <form:password path="confirmPassword"/>
        <font style="color: red"><form:errors path="confirmPassword"/></font><br/>
    
    </form:form>
    
  • Tiny
    Tiny over 11 years
    Tried and it worked, sir! Offering bounty requires some hours from now onwards, I will award it to your answer later when the system permits. Just say one additional thing please, in this method itself cvc.buildConstraintViolationWithTemplate("Passwords in both of the fields must match.").addNode(password).addConstraintViolation();, I have specified the error message. Is there a way to fetch the message specified in the @FieldMatch.List({})? No matter if there is no way to do so. It's minor thing. To me, It's a great thing to know it worked according to your answer. Thank you very much
  • Tiny
    Tiny over 11 years
    It's all working well through out my entire application now. Thank a lot, sir!
  • Eugene
    Eugene over 11 years
    @Tiny a pleasure I could help!
  • Harmeet Singh Taara
    Harmeet Singh Taara almost 10 years
    @Eugene is it possible this for internationalization ? \
  • Tiny
    Tiny over 9 years
    @HarmeetSinghTaara : That just requires a message key to be specified from a localized/internationalized property file replacing the actual message such as @FieldMatch.List({@FieldMatch(first = "password", second = "confirmPassword", message = "{message.key}", groups {TempBean.ValidationGroup.class})}) as in the question (message = "{message.key}").