I am using Spring Data JPA to model and validate my data. In this case I got a class that got both a password
and confirm
field:
public class RegistrationForm {
private String password;
private String confirm;
// ...
}
and now I want to check if they both match. I figured out I can create a method for it using @AssertTrue
:
@AssertTrue(message = "Passwords don't match")
private boolean isPasswordMatch() {
return password.equals(confirm);
}
Now in my controller I validate this class and this method runs just fine. My problem now is that I couldn't figure out how to display this error in my Thymeleaf template. I usually used this for fields with validation:
<span th:if="${#fields.hasErrors('username')}" th:errors="*{username}"></span>
But that doesn't work with methods. Now I investigated a bit and found out that if I name the method something like isXXX
then it is going to put a field into the Errors
instance named XXX
. In this case, it would be a field called passwordMatch
. I could verify this using a debugger.
It doesn't work, even though that field error exists as a ViolationFieldError
like any other error. For reference I tried this:
<span th:if="${#fields.hasErrors('passwordMatch')}" th:errors="*{passwordMatch}"></span>
I simply get an error saying that the property is not readable:
Caused by: org.springframework.beans.NotReadablePropertyException: Invalid property 'passwordMatch' of bean class [me.squidxtv.tacocloud.model.RegistrationForm]:
Bean property 'passwordMatch' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Note: I am currently going through "Spring in Action" 6th edition and this comes up in chapter 5 "Securing Spring" but my question isn't related to it, because the book itself doesn't implement validation for that class.
If you use #fields.hasErrors('passwordMatch')
, it expects a property passwordMatch
so one way would be making the isPasswordMatch()
method public
. This is probably the easiest way to do this. When doing that, you might want to change password.equals(confirm)
to Objects.equals(password, confirm)
.
However, Thymeleaf also allows you to access all errors without checking the fields by accessing #fields.errors()
or #fields.detailedErrors()
.
If you want to iterate over all errors, you can do so using #fields.errors()
:
<div th:if="${#fields.hasErrors()}" th:each="err : *{#fields.errors()}">
<!-- Tell the user about the error -->
<span th:text=${err}></span>
</div>
However, that doesn't let you filter for specific errors. If you want to use the information that the error occured in passwordMatch
, you need to use detailedErrors()
. You can iterate over that as follows:
<div th:if="${#fields.hasErrors()}" th:each="err : *{#fields.detailedErrors()}">
Error in <span th:text=${err.fieldName}></span>: <span th:text=${err.message}></span>
</div>
With that information, you can also filter for passwordMatch
:
<th:block th:if="${#fields.hasErrors()}" th:each="err : *{#fields.detailedErrors()}">
<div th:if="${'passwordMatch'.equals(err.fieldName)}" th:text=${err.message}></div>
</th:block>