Im very new to the SpringBoot framework, currently i'm trying to create a working page for CRUD operations required for my project. The problem is in my controller BindingResult wont catch any errors when i submit a form with empty inputs. Here's my DTO class:
package com.estate.controllers;
import com.estate.model.Property;
import jakarta.persistence.Column;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import jakarta.validation.constraints.*;
import java.util.Date;
import org.springframework.web.multipart.MultipartFile;
public class PropertyDTO {
@NotEmpty(message = "The name of the property is required")
private String title;
@Size(min = 10, message = "The description should be at least 10 characters long")
@Size(max = 2000, message = "The description can not exceed 2000 characters")
private String description;
@NotEmpty(message = "The price is required")
@Size(min = 0)
private Double price;
@NotEmpty(message = "The number of bedrooms is required")
@Size(min = 0)
private Integer bedrooms;
@NotEmpty(message = "The number of bathrooms is required")
@Size(min = 1,message = "The number of bathrooms must be at least 1")
private Integer bathrooms;
@NotEmpty(message = "The area is required")
@Size(min = 0,message = "The area must be greater than 0")
private Double area;
@NotEmpty(message = "The name of the city is required")
private String city;
@NotEmpty(message = "The address is required")
private String address;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at")
private Date createdAt;
@PrePersist
protected void onCreate() {
createdAt = new Date();
}
@Column(name = "image_url")
private MultipartFile imageFile;
}
My controller where i even doubled the if statement for errors:
package com.estate.Admin;
import com.estate.controllers.PropertyDTO;
import com.estate.model.Property;
import com.estate.repository.PropertyRepository;
import com.estate.service.PropertyService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("/properties")
@Validated
public class AdminPropertyController {
private final PropertyService propertyService;
@Autowired
private PropertyRepository repo;
public AdminPropertyController(PropertyService propertyService) {
this.propertyService = propertyService;
}
@GetMapping({"","/"})
public String showPropertyList(Model model){
List<Property> properties = repo.findAll();
model.addAttribute("properties", properties);
return "propertyTemplate/index.html";
}
@GetMapping("/create")
public String showCreatePage(@ModelAttribute("propertyDTO") PropertyDTO propertyDTO, Model model) {
model.addAttribute("propertyDTO", propertyDTO);
return "propertyTemplate/createProperty";
}
@PostMapping("/create")
public String CreateProperty(@ModelAttribute("propertyDTO") @Valid PropertyDTO propertyDto, @Valid BindingResult result, Model model){
if(result.hasErrors()){
model.addAttribute("errors", result.getAllErrors());
return "propertyTemplate/createProperty";
}
if(result.hasErrors()){
return "propertyTemplate/createProperty";
}
return "redirect:/properties";
}
}
And my html file, i use thymeleaf for displaying errors:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Online catalog</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
<div class ="container">
<div class="row">
<div class="col-md-8 mx-auto rounded border p-4 m-4">
<h2 class="text-center mb-5">New Property</h2>
<form :form method ="POST" enctype="multipart/form-data" th:object="${propertyDTO}">
<div class="row mb-3">
<label class="col-sm-4 col-form-label">Name</label>
<div class="col-sm-8">
<input class="form-control" placeholder="Введите название" th:field="${propertyDTO.title}">
<p th:if="${#fields.hasErrors('title')}" th:errorclass="text-danger"
th:errors="${propertyDTO.title}"></p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-4 col-form-label">Description</label>
<div class="col-sm-8">
<textarea class="form-control" placeholder="Введите описание"
th:field="${propertyDTO.description}"></textarea>
<p th:if="${#fields.hasErrors('description')}" th:errorclass="text-danger"
th:errors="${propertyDTO.description}"></p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-4 col-form-label">Price</label>
<div class="col-sm-8">
<input class="form-control" type="number" step="0.01" min="0" value="0" placeholder="Введите цену"
th:field="${propertyDTO.price}">
<p th:if="${#fields.hasErrors('price')}" th:errorclass="text-danger"
th:errors="${propertyDTO.price}"></p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-4 col-form-label">Bedrooms</label>
<div class="col-sm-8">
<input class="form-control" type="number" step="1" min="0" value="0" placeholder="Введите количество комнат"
th:field="${propertyDTO.bedrooms}">
<p th:if="${#fields.hasErrors('bedrooms')}" th:errorclass="text-danger"
th:errors="${propertyDTO.bedrooms}"></p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-4 col-form-label">Bathrooms</label>
<div class="col-sm-8">
<input class="form-control" type="number" step="1" min="0" value="0" placeholder="Введите количество комнат"
th:field="${propertyDTO.bathrooms}">
<p th:if="${#fields.hasErrors('bathrooms')}" th:errorclass="text-danger"
th:errors="${propertyDTO.bathrooms}"></p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-4 col-form-label">Area</label>
<div class="col-sm-8">
<input class="form-control" type="number" step="0.01" min="0" placeholder="Введите площадь"
th:field="${propertyDTO.area}">
<p th:if="${#fields.hasErrors('area')}" th:errorclass="text-danger"
th:errors="${propertyDTO.area}"></p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-4 col-form-label">City</label>
<div class="col-sm-8">
<input class="form-control" placeholder="Введите город"
th:field="${propertyDTO.city}">
<p th:if="${#fields.hasErrors('city')}" th:errorclass="text-danger"
th:errors="${propertyDTO.city}"></p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-4 col-form-label">Address</label>
<div class="col-sm-8">
<input class="form-control" placeholder="Введите адресс"
th:field="${propertyDTO.address}">
<p th:if="${#fields.hasErrors('address')}" th:errorclass="text-danger"
th:errors="${propertyDTO.address}"></p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-4 col-form-label">Image</label>
<div class="col-sm-8">
<input type="file" class="form-control"
th:field="${propertyDTO.imageFile}">
<p th:if="${#fields.hasErrors('imageFile')}" th:errorclass="text-danger"
th:errors="${propertyDTO.imageFile}"></p>
</div>
</div>
<div class="row">
<div class="offset-sm-4 col-sm-4 d-grid">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
<div class="col-sm-4 d-grid">
<a class="btn btn-outline-primary" href="/properties" role="button">Cancel</a>
</div>
</div>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>
I tried changing names of the model attribute cause at first in project logs i was seing an issue with it, but now im hard stuck at this propblem. I would appreciate any pointers for the problems or incorrections beyond the validation problem.
th:field="${propertyDTO.title}"should beth:field="*{title}"in your form. Applies to your other form fields as well ofcourse. Additionally make sure you addedspring-boot-starter-validationas a dependency and didn't mess around with individualvalidation-apidependencies etc.th:errorssyntax should be the same as for theth:fieldsoth:errors="*{description}"instead ofth:errors="${description}. I would strongly suggest a read of the documentation instead of trail and error.