Search code examples
springspring-bootspring-mvcthymeleafspring-validator

Validating input in spring-boot using @Valid andThymeleaf


I try to validate 2 fields from an entity class which has 2 fields: name and price, yet both strings and i try to print the message from each @NotBlank(message = "...") into the form under the input field. But i get these errors:

Field error in object 'foodDto' on field 'name': rejected value []; codes [NotBlank.foodDto.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [foodDto.name,name]; arguments []; default message [name]]; default message [Name required] Field error in object 'foodDto' on field 'price': rejected value []; codes [NotBlank.foodDto.price,NotBlank.price,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [foodDto.price,price]; arguments []; default message [price]]; default message [Price required]]

package com.assignment2.project.dto;


import javax.validation.constraints.NotBlank;

public class FoodDto {

@NotBlank(message = "Name required")
public String name;
@NotBlank(message = "Price required")
public String price;

public FoodDto(String name, String price) {
    this.name = name;
    this.price = price;
}

public FoodDto() {
}

@Override
public String toString() {
    return "FoodDto{" +
            "name='" + name + '\'' +
            ", price='" + price + '\'' +
            '}';
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getPrice() {
    return price;
}

public void setPrice(String price) {
    this.price = price;
}
}
 /////////////////////////////////////////////////
@Controller
@RequestMapping("food")
public class FoodController{

@Autowired
FoodService foodService;

@Autowired
ReportPDFService reportPDFService;

@GetMapping("create")
public String createFood(Model model){
    model.addAttribute("title","Create Food");
    String type = reportPDFService.readFile();
    model.addAttribute("user",new User(Integer.parseInt(type)));
    model.addAttribute("food", new FoodDto());
    return "food/create";
}

@PostMapping("create")
public String renderCreatedFood(@ModelAttribute @Valid FoodDto newFood, Model model, BindingResult errors){
    if(errors.hasErrors()){
        System.out.println("heree?");
        model.addAttribute("title","Create Food");
        return "food/create";
    }
    foodService.create(newFood);
    return "redirect:create";
}
////////////////////
<form method="post">
<div class="form-group">
    <label>Name
        <input th:field="${food.name}" class="form-control">
    </label>
    <p class="error" th:errors="${food.name}"></p>
</div>

<div class="form-group">
    <label>Price
        <input th:field="${food.price}" class="form-control">
    </label>
    <p class="error" th:errors="${food.price}"></p>
</div>

<div class="form-group">
    <input type="submit" value="Create" class="btn btn-success">
</div>

</form>

instead of giving me the error under the field in the html file it gives me an error in the app and on the web:

enter image description here


Solution

  • In your code, I think BindingResult should come right after newFood.

    renderCreatedFood(Model model, @Valid FoodDto newFood, BindingResult errors)
    

    In your controller handler method, try moving the BindingResult argument so it is immediately after the command argument. Spring looks for command object parameters and BindingResult parameters to be paired up in handler method signatures.

    I created a similar example with reference here and I think you want this!

    Here, if the name or price field contains an empty value; your message appears next to the input.

    public class FoodDTO {
    
        @NotBlank(message = "Name required!")
        public String name;
    
        @NotBlank(message = "Price required!")
        public String price;
    
        public FoodDTO() {
        }
    
        // getter/setter ..
    }
    
    @Controller
    @RequestMapping("food")
    public class FoodController {
    
        @GetMapping("/create")
        public String crete(FoodDTO foodDTO) {
            return "food-form";
        }
    
        @PostMapping("/insert")
        public String insert(@Valid FoodDTO foodDTO, BindingResult bindingResult) {
    
            if (bindingResult.hasErrors()) {
                return "food-form";
            }
    
            return "redirect:/food/result";
        }
    
        @GetMapping("/result")
        public String result() {
            return "result";
        }
    }
    
    <!DOCTYPE HTML>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <body>
        <form action="/food/insert" method="post" th:object="${foodDTO}">
            <table>
                <tr>
                    <td>Name:</td>
                    <td><input type="text" th:field="*{name}" /></td>
                    <td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</td>
                </tr>
                <tr>
                    <td>Price:</td>
                    <td><input type="text" th:field="*{price}" /></td>
                    <td th:if="${#fields.hasErrors('price')}" th:errors="*{price}">Price Error</td>
                </tr>
                <tr>
                    <td><button type="submit">Submit</button></td>
                </tr>
            </table>
        </form>
    </body>
    </html>