I'm using Spring Boot 2.3.1 and Thymeleaf for a simple registration app so a user can create and save a Member
model to the database. I'm using the javax.validation
framework to ensure fields are not empty and it works fine when the model is valid, but an invalid model with missing fields kicks me out with a 400 error code to my default error.html
page, instead of taking the user back to the registration form where I can display field-level errors (e.g., "Username is required").
The model is simple and looks like this:
@Entity
@Table(name="member")
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@Email
@NotEmpty
private String username;
@NotEmpty
@Column(name="password", nullable = false)
private String password;
// ... getters and setters
The controller looks like this:
@PostMapping("/register")
public ModelAndView registerMember(HttpServletRequest request, HttpServletResponse response,
@ModelAttribute @Valid Member member, ModelMap model, BindingResult result){
log.info("Attempting to register a user");
// some other error checking not related to javax.validation ...
memberService.save(member);
return new ModelAndView("/success", model);
}
Finally, the (simplified) HTML/Thymeleaf form looks like this (register.html
):
<form th:action="@{/register}" th:object="${member}" method="post">
<input type="text" class="form-control" placeholder="Email" id="username" th:field="*{username}">
<input type="password" placeholder="Enter your Password" id="password" class="form-control" th:field="*{password}">
<input type="submit" class="btn btn-primary btn-block btn-login">
</form>
On form submit with a value for username
and password
everything works, but if I omit either of those, like the username
I get the following WARN
log statement:
Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 4 errors
Field error in object 'member' on field 'username': rejected value []; codes [NotEmpty.member.username,NotEmpty.username,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [member.username,username]; arguments []; default message [username]]; default message [must not be empty]
But then the server returns a 400 error and renders the error.html
page instead of returning to the form, and equally important is my registerMember()
method in the controller isn't called at all. It by passes my logic entirely so I can't even manually inspect the errors using BindingResult
.
If I omit the @Valid
annotation on my model like @ModelAttribute Member member
then my method is invoked but none of the javax.validation
is used and the BindingResult.getErrorCount()
is 0
, thus defeating the entire point of using it (I'd have to validate myself manually).
Any idea how I can let @Valid
do its job and return back to the /register
endpoint to display the field-level errors?
You can solve this problem by following ways.
public ModelAndView registerMember(HttpServletRequest request, HttpServletResponse response,@ModelAttribute @Valid Member member, ModelMap model, BindingResult result)
to this
public ModelAndView registerMember(HttpServletRequest request, HttpServletResponse response,@ModelAttribute @Valid Member member, BindingResult result, ModelMap model)
Because binding result should be immediate to @Valid object.
After this you can errors and send it desired form as below
if (bindingResult.hasErrors()) {
return "register";
}
@RestControllerAdvice @EnableWebMvc
public class ExceptionAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Response handleInvaldException (MethodArgumentNotValidException ex, Response response) {
Map < String, String > errors = new HashMap < > ();
ex.getBindingResult().getAllErrors().forEach((err) - > {
String fieldName = ((FieldError) err).getField();
String errorMessage = err.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
response.setData(errors);
retr
}
}