javaspring-bootspring-mvcthymeleafspring-thymeleaf

Validating Form Input in SpringBoot - thymeleaf


I have controller:

@Controller
@Slf4j
@RequestMapping("/natalchart")
public class NatalChartDataController {

    @GetMapping({"/data"})
    public String data(Model model, NatalChartDataPayload data) {
        model.addAttribute("months", Month.values());
        return "natalChartData";
    }

    @ModelAttribute("data")
    public NatalChartDataPayload natalChartDataPayload(){
        return NatalChartDataPayload.builder().build();
    }

    @PostMapping("/create")
    public String createNatalChart(
        @Valid @ModelAttribute NatalChartDataPayload data,
        BindingResult result
    ) {
        if (result.hasErrors()) {
            return "natalChartData";
        }
    // rest omitted
}

This is the template:

 <div class="container" data-aos="fade-up">
          <form method="post" th:action="@{/natalchart/create}" th:object="${data}">
          <div class="row">
            <div class="col-lg-8">
                <h4>Enter your birth details to create your natal chart</h4>

              <p>&nbsp;</p>
              <p>&nbsp;</p>

              <div th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Email Error</div>

and the error:

enter image description here

However, I don't see the error message in the template.


Solution

  • First of all, I am very sorry, because I am afraid my first answer was incomplete, and you needed to ask a related question a second time.

    I think the problem is related to the following method:

    @PostMapping("/create")
        public String createNatalChart(
            @Valid @ModelAttribute NatalChartDataPayload data,
            BindingResult result
        ) {
            if (result.hasErrors()) {
                return "natalChartData";
            }
        // rest omitted
    

    In Spring, if you define a model attribute without defining its name explicitly it will fallback to the uncapitalized object type. Thus, when you define this:

    @Valid @ModelAttribute NatalChartDataPayload data,
    

    Spring will generate a bean with the name natalChartDataPayload. As you presented in your screenshot, that is the bean that is being evaluated for errors in your debug window.

    But, in your page you are looking for a bean with name data, and it turns out that there is one, implicitly included by Spring in every request, because you defined it with this method:

    @ModelAttribute("data")
        public NatalChartDataPayload natalChartDataPayload(){
            return NatalChartDataPayload.builder().build();
        }
    

    To solve the issue, you can use something like the following:

    @Controller
    @Slf4j
    @RequestMapping("/natalchart")
    public class NatalChartDataController {
    
        @GetMapping({"/data"})
        public String data(Model model, NatalChartDataPayload data) {
            model.addAttribute("months", Month.values());
            model.addAttribute("data", NatalChartDataPayload.builder().build());
            return "natalChartData";
        }
    
        @PostMapping("/create")
        public String createNatalChart(
            @Valid @ModelAttribute("data") NatalChartDataPayload data,
            BindingResult result
        ) {
            if (result.hasErrors()) {
                // you can inject model and include the data object as an attribute
                // as well, although it is redundant after defining @ModelAttribute("data")
                return "natalChartData";
            }
        // rest omitted
    }
    

    Please, note that I named your ModelAttribute as data:

    @Valid @ModelAttribute("data") NatalChartDataPayload data,
    

    and now everything matches the data name.

    Probably it will be enough to solve the issue, although in the presented code I removed the natalChartDataPayload method as well, I think it may cause confusion, and I added the data attribute in your data method instead.

    Similar problems and solutions have been provided here in SO too: see for example this one, or this other.

    There is a related Github issue that could be of help as well.