Search code examples
javaspringspring-bootvalidationthymeleaf

Spring Boot and Thymeleaf are returning a 400 error on field errors


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?


Solution

  • You can solve this problem by following ways.

    1. You need to change the method signature from

    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";
    }
    
    1. You can controller advice for handling exceptions

    @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
    }
    

    }