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]
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.