Search code examples
springspring-mvcjspjstl

form:error message shows up in every field (List)


I have some input fields that are members of an Arraylist of one object. I.E. The below code is inside a c:forEach loop.

<tr> <td><form:label path="person[${index}].firstname">First name</form:label></td> <td><form:input path="person[${index}].firstname" /></td> <td><form:errors path="person[${index}].firstname" cssClass="error" /></td> </tr>

This way the errors are not showing up at all. One the other hand, this code:

<tr> <td><form:label path="person[${index}].firstname">First name</form:label></td> <td><form:input path="person[${index}].firstname" /></td> <td><form:errors path="person.firstname" cssClass="error" /></td> </tr>

shows the errors but the errors are shown in every textfield of this page field.

Any ideas?


Solution

  • In your second approach,

    <tr>
         <td><form:label path="person[${index}].firstname">First name</form:label></td>
         <td><form:input path="person[${index}].firstname" /></td>
         <td><form:errors path="person.firstname" cssClass="error" /></td>
    </tr>
    

    Spring will bind error message for attribute with every text box where you mapped <form:errors with path = person.firstname> It's correct spring behaviour.

    There is no direct way to handle the error in way which you expect.

    The solution is to create a custom Validator for Collection (List of object) and a @ControllerAdvice that registers that Validator in the WebDataBinders.

    Validator :

    import java.util.Collection;
    
    import org.springframework.validation.Errors;
    import org.springframework.validation.ValidationUtils;
    import org.springframework.validation.Validator;
    import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
    
    /**
     * Spring {@link Validator} that iterates over the elements of a 
     * {@link Collection} and run the validation process for each of them
     * individually.
     *   
     * @author DISID CORPORATION S.L. (www.disid.com)
     */
    public class CollectionValidator implements Validator {
    
      private final Validator validator;
    
      public CollectionValidator(LocalValidatorFactoryBean validatorFactory) {
        this.validator = validatorFactory;
      }
    
      @Override
      public boolean supports(Class<?> clazz) {
        return Collection.class.isAssignableFrom(clazz);
      }
    
      /**
       * Validate each element inside the supplied {@link Collection}.
       * 
       * The supplied errors instance is used to report the validation errors.
       * 
       * @param target the collection that is to be validated
       * @param errors contextual state about the validation process
       */
      @Override
      @SuppressWarnings("rawtypes")
      public void validate(Object target, Errors errors) {
        Collection collection = (Collection) target;
        for (Object object : collection) {
          ValidationUtils.invokeValidator(validator, object, errors);
        }
      }
    }
    

    ControllerAdvice :

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
    import org.springframework.web.bind.WebDataBinder;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.InitBinder;
    
    /**
     * Controller advice that adds the {@link CollectionValidator} to the 
     * {@link WebDataBinder}.
     * 
     * @author DISID CORPORATION S.L. (www.disid.com)
     */
    @ControllerAdvice
    public class ValidatorAdvice {
    
      @Autowired
      protected LocalValidatorFactoryBean validator;
    
    
      /**
       * Adds the {@link CollectionValidator} to the supplied 
       * {@link WebDataBinder}
       * 
       * @param binder web data binder.
       */
      @InitBinder
      public void initBinder(WebDataBinder binder) {
        binder.addValidators(new CollectionValidator(validator));
      }
    }