There was a tricky problem with the Spring Boot application that I had been trying to solve for a while, and I hope someone can help me. I deleted all other parts of the project and tried to make it as simple as possible. If you go to localhost: 8080, a form will appear with two text fields for entering two names and the "Submit" button. The first name will be stored in the Nominee object, the second in the Submitter object. When you click Submit, it will check in the fields to make sure that none of them are empty. I will post the code below and explain my problem at the end.
Application.java
package nomination; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.jms.annotation.EnableJms; import org.springframework.jms.config.JmsListenerContainerFactory; import org.springframework.jms.config.SimpleJmsListenerContainerFactory; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @SpringBootApplication @EnableJms @EnableWebMvc public class Application { public static void main(String[] args) throws Exception { // Launch the application SpringApplication.run(Application.class, args); } }
Webcontroller.java
package nomination; import javax.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ui.Model; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Controller public class WebController extends WebMvcConfigurerAdapter { protected static final Logger LOG = LoggerFactory.getLogger(WebController.class); @InitBinder("nominee") protected void initNomineeBinder(WebDataBinder binder) { binder.setValidator(new NomineeValidator()); } @InitBinder("submitter") protected void initSubmitterBinder(WebDataBinder binder) { binder.setValidator(new SubmitterValidator()); } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/success").setViewName("success"); } @RequestMapping(value="/", method=RequestMethod.GET) public String showForm(Model model) { model.addAttribute("nominee", new Nominee()); model.addAttribute("submitter", new Submitter()); return "form"; } @RequestMapping(value="/", method=RequestMethod.POST) public String checkPersonInfo(@ModelAttribute(value="nominee") @Valid Nominee nominee, @ModelAttribute(value="submitter") @Valid Submitter submitter, BindingResult bindingResult, @Valid Model model) { LOG.info("Nominee to string: " + nominee.toString()); LOG.info("Submitter to string: " + submitter.toString()); LOG.info("bindingResult to string: " + bindingResult.toString()); if (bindingResult.hasErrors()) { return "form"; } return "redirect:/success"; } }
Nominee.java
package nomination; import lombok.Data; @Data public class Nominee { private String name; }
NomineeValidatior.java
package nomination; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; public class NomineeValidator implements Validator { public boolean supports(Class clazz) { return Nominee.class.equals(clazz); } public void validate(Object object, Errors errors) { ValidationUtils.rejectIfEmpty(errors, "name", "name", "This field is empty."); } }
Submitter.java
package nomination; import lombok.Data; @Data public class Submitter { private String sname; }
SubmitterValidator.java
package nomination; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; public class SubmitterValidator implements Validator { public boolean supports(Class clazz) { return Submitter.class.equals(clazz); } public void validate(Object object, Errors errors) { ValidationUtils.rejectIfEmpty(errors, "sname", "sname", "This field is empty."); } }
form.html
<html><body> <form role="form" th:action="@{/}" method="post"> <h2>Nominee details</h2> <table> <tr> <td>Name:</td> <td> <input type="text" th:field="${nominee.name}" /> </td> <td><p th:if="${#fields.hasErrors('nominee.name')}" th:errors="${nominee.name}">Empty field</p></td> </tr> </table> <h2>Your details</h2> <table> <tr> <td>Your name:</td> <td> <input type="text" th:field="${submitter.sname}" /> </td> <td><p th:if="${#fields.hasErrors('submitter.sname')}" th:errors="${submitter.sname}">Empty field</p></td> </tr> </table> <div> <button type="submit">Submit nomination</button> </div> </form> </body></html>
success.html
<html><body>Form successfully submitted.</body></html>
If I leave the first text box empty (and to fill or not to fill the second text box), an error message appears on the screen:
Whitelabel Error Page
There is no explicit mapping for / error in this application, so you see this as a fallback. Tue May 12 13:10:17 AEST 2015 There was an unexpected error (type = Bad Request, status = 400). Failed to validate object = "" nominee. Number of errors: 1
I do not know how to fix this, so leaving the first empty text box will not cause the whitelabel error page. If I leave the second text box empty, but fill out the first, it works completely fine, so I'm not sure why it causes an error if I try differently. Any help correcting this would be greatly appreciated.
In addition, you may have noticed that I had to use βnameβ and βsnakesβ as my variables in Nomine and Sender, if I set them as βnameβ, then this does not work properly. If there is a way to edit it so that both of them can use the "name", I would be interested to know how to do it.
Edit: solution found. In WebController, checkPersonInfo needs a separate BindingResult for each object being checked. BindingResult must be in the method parameters immediately after each @Valid object.
So, in WebController.java, this is:
@RequestMapping(value="/", method=RequestMethod.POST) public String checkPersonInfo(@ModelAttribute(value="nominee") @Valid Nominee nominee, @ModelAttribute(value="submitter") @Valid Submitter submitter, BindingResult bindingResult, @Valid Model model) { LOG.info("Nominee to string: " + nominee.toString()); LOG.info("Submitter to string: " + submitter.toString()); LOG.info("bindingResult to string: " + bindingResult.toString()); if (bindingResult.hasErrors()) { return "form"; } return "redirect:/success"; }
You need to become the following:
@RequestMapping(value="/", method=RequestMethod.POST) public String checkPersonInfo(@ModelAttribute(value="nominee") @Valid Nominee nominee, BindingResult bindingResultNominee, @ModelAttribute(value="submitter") @Valid Submitter submitter, BindingResult bindingResultSubmitter) { LOG.info("Nominee to string: " + nominee.toString()); LOG.info("Submitter to string: " + submitter.toString()); if (bindingResultNominee.hasErrors() || bindingResultSubmitter.hasErrors()) { return "form"; } return "redirect:/success"; }
(The model object was deleted since it was never used anywhere, if you need to check it with @Valid then you will add a third BindingResult object.)