javaspringvalidationthymeleaf

How to display errors in Thymeleaf


I have a custom password validation implemented using ConstraintValidator, and I need to display error messages when the validation fails. But error messages are not being displayed when the validation fails.

Interface:

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = {PasswordConstraintValidator.class})
@Documented
public @interface Password {
    String message() default "{messages.validation.password}";

    int maxLength() default 100;

    int minLength() default 8;

    int upperCaseCount() default 2;

    int lowerCaseCount() default 2;

    int specialCharCount() default 1;

    Class<?>[] groups() default {};

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

Implementation

@Component
public class PasswordConstraintValidator implements ConstraintValidator<Password, String> {

    private int minLength;

    private int maxLength;

    private int upperCaseCount;

    private int lowerCaseCount;

    private int specialCharCount;

    @Override
    public void initialize(final Password password) {
        minLength = password.minLength();
        maxLength = password.maxLength();
        upperCaseCount = password.upperCaseCount();
        lowerCaseCount = password.lowerCaseCount();
        specialCharCount = password.specialCharCount();
    }

    @Override
    public boolean isValid(final String string, final ConstraintValidatorContext constraintValidatorContext) {
        return validatePassword(string);
    }

// method validatePassword(string){}

My registration form looks like this:

<form xmlns:th="http://www.thymeleaf.org" th:action="@{/register/user}" method="post"
      th:object="${userDto}" style="width: 22rem;" th:fragment="reg-user-form">

<!-- Password -->
    <div class="form-outline mb-4">
        <label class="form-label text-white h5" for="password" th:text="#{label.password}">Password</label>
        <input type="password" id="password" th:field="*{password}" class="form-control bg-dark text-white rounded border-2" style="border-color: #2f3944;" required />
        <div class="text-danger" th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></div>
    </div>

</form>

The controller methods looks like this:

 @GetMapping(USER_REGISTER_URL)
 public String regUser(final Model model) {
    model.addAttribute("userDto", new RequestUserDto());
    return USER_REGISTER_PAGE;
 }


@PostMapping(USER_REGISTER_URL)
public String regUser(@Valid @ModelAttribute final RequestUserDto userDto,
                          final BindingResult bindingResult, final Model model) {

     if (bindingResult.hasErrors()) {
         model.addAttribute("userDto", userDto);
         model.addAttribute("errors", bindingResult.getAllErrors());
         return USER_REGISTER_PAGE;
     }

      userService.createUser(userDto);
      return LOGIN_PAGE_REDIRECT;
}

I checked if there were errors in bindingResult by printing them out with System.out.println

Field error in object 'requestUserDto' on field 'password': rejected value [awdsaw]; codes [Password.requestUserDto.password,Password.password,Password.java.lang.String,Password]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [requestUserDto.password,password]; arguments []; default message [password],2,100,8,1,2]; default message [The Password is incorrect. Max length of password - 100, min length of password 8, must contain: upper case—at least 2, lower case - at least 2, special char - 1]

However, Thymeleaf is not displaying the error message, and this happens not only with the custom validation but also with annotations like @Size:

Field error in object 'requestUserDto' on field 'firstName': rejected value [a]; codes [Size.requestUserDto.firstName,Size.firstName,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [requestUserDto.firstName,firstName]; arguments []; default message [firstName],50,10]; default message [size must be between 10 and 50]


Solution

  • Issue:

    The issue was that Thymeleaf was not displaying validation errors properly for the fields.

    What I did to resolve the issue:

    Bind the form data properly using @ModelAttribute:

    @Controller
    @RequestMapping(UserRegisterController.BASE_URL)
    public class UserRegisterController {
    
        public static final String BASE_URL = "/register";
    
        private static final String USER = "user";
    
       // Autowired constructor
    
       @GetMapping(USER_REGISTER_URL)
        public String regUser(final Model model, @ModelAttribute(USER) final RequestUserDto user) {
            model.addAttribute(USER, user);
            return USER_REGISTER_PAGE;
        }
    
        @PostMapping(USER_REGISTER_URL)
        public String regUser(@Valid @ModelAttribute(USER) final RequestUserDto user,
                              final BindingResult bindingResult, final Model model) {
            if (bindingResult.hasErrors()) {
                model.addAttribute(USER, user);
                return USER_REGISTER_PAGE;
            }
    
            userService.createUser(user);
            return LOGIN_PAGE_REDIRECT;
        }
    

    Also i changed th:object="${userDto}" to th:object="${user}" in tag.

    Result:

    By making sure that the attribute names in the controller and the template were consistent, Thymeleaf was able to correctly display validation errors for individual fields.