You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Thymeleaf中BindingResult未校验输入问题求助

Fix: BindingResult Validation Errors Not Showing in Thymeleaf for firstName/lastName

I've been stuck on this for hours: My Spring Boot controller has @GetMapping and @PostMapping methods, and I use @ControllerAdvice to handle database validation errors, but that's not the issue here. I expect BindingResult to flag validation errors for the firstName and lastName fields, but the error messages aren't being passed to the Thymeleaf template. Relevant code is below:

@GetMapping Method

@GetMapping("/register")
public String showRegistrationForm(Model model, @RequestParam(required = false) boolean registrationError) {
    UserRegistrationForm registrationForm = new UserRegistrationForm();
    model.addAttribute("registrationForm", registrationForm);
    model.addAttribute("registerError", registrationError);

    return "registration";
}

@PostMapping Method

@PostMapping("/register")
public String handleRegistration(@ModelAttribute @Valid UserRegistrationForm userRegistrationForm, BindingResult result, Model model) {

        if(result.hasErrors()){
            model.addAttribute("registrationForm", userRegistrationForm);
            return "registration";
        }

    UserDTO userDTO = new UserDTO();
    userDTO.setEmail(userRegistrationForm.getEmail());
    userDTO.setPassword(passwordEncoder.encode(userRegistrationForm.getPassword()));
    userDTO.setFirstName(userRegistrationForm.getFirstName());
    userDTO.setLastName(userRegistrationForm.getLastName());
    userDTO.setRoleName(Set.of("ROLE_USER"));
    userDTO.setUsername(userRegistrationForm.getUsername());

    User users = new User();
    users.setUsername(userDTO.getUsername());
    users.setEmail(userDTO.getEmail());
    users.setPassword(userDTO.getPassword());
    users.setFirstName(userDTO.getFirstName());
    users.setLastName(userDTO.getLastName());
    users.setRoleName(userDTO.getRoleName());
    try {
        userService.saveUser(users);
        System.out.println(repository.findByEmail(users.getEmail()));
    } catch (Exception e) {
        if (repository.findByUsername(users.getUsername()).isPresent()
                && repository.findByEmail(users.getEmail()).isPresent()){
            logger.warn(String.format("User already exists with the username: %s and the email: %s",
                    users.getUsername(), users.getEmail()));
            throw new UserException(String.format("User already exists with the username: %s and the email: %s",
                    users.getUsername(), users.getEmail()));
        } else if (repository.findByUsername(users.getUsername()).isPresent()) {
            logger.warn(String.format("User already exists with the username: %s", users.getUsername()));
            throw new UserException(String.format("User already exists with the username: %s", users.getUsername()));
        } else if (repository.findByEmail(users.getEmail()).isPresent()) {
            logger.warn(String.format("User already exists with the email: %s", users.getEmail()));
            throw new UserException(String.format("User already exists with the email: %s", users.getEmail()));
        }
    }
    return "redirect:/login?registrationSuccess=true";
}

Thymeleaf Template Snippet

<div class="d-flex flex-row align-items-center mb-4">
    <i class="fas fa-user fa-lg me-3 fa-fw"></i>
    <div class="form-outline flex-fill mb-0">
        <label class="form-label" for="firstName">Name</label>
        <input type="text" id="firstName" th:field="*{firstName}" class="form-control" placeholder="First name"/>
    </div>
    <div th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" class="alert alert-warning"></div>
</div>
<div class="d-flex flex-row align-items-center mb-4">
    <i class="fas fa-user fa-lg me-3 fa-fw"></i>
    <div class="form-outline flex-fill mb-0">
        <label class="form-label" for="lastName">Last name</label>
        <input type="text" id="lastName" th:field="*{lastName}" class="form-control" placeholder="Last name"/>
    </div>
    <div th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" class="alert alert-warning"></div>
</div>

UserRegistrationForm Class with Validation Constraints

@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table
public class UserRegistrationForm {

    @Id
    @SequenceGenerator(name = "user_Reg_Form_Gen",
            allocationSize = 1, sequenceName = "user_Reg_Form_Gen")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_Reg_Form_Gen")
    Long registerFormID;

    @NotNull(message = "Username is required")
    @Size(min = 3, max = 16, message = "Username must be between 3 and 16 characters")
    private String username;
    @NotNull(message = "Please enter a password")
    private String password;
    private String matchPassword;
    @NotNull(message = "First name is required")
    private String firstName;
    @NotNull(message = "Last name is required")
    private String lastName;
    @NotNull(message = "Email is required")
    private String email;
}

Solutions

1. Remove JPA Annotations from UserRegistrationForm

The root issue is that you've marked UserRegistrationForm as a JPA entity with @Entity and @Table, plus ID generation annotations. This turns it into a database entity instead of a simple form DTO, which breaks Spring's validation and model binding flow. These annotations belong on your User entity, not the form object.

Updated UserRegistrationForm:

@AllArgsConstructor
@NoArgsConstructor
@Data
public class UserRegistrationForm {

    @NotNull(message = "Username is required")
    @Size(min = 3, max = 16, message = "Username must be between 3 and 16 characters")
    private String username;
    @NotNull(message = "Please enter a password")
    private String password;
    private String matchPassword;
    @NotNull(message = "First name is required")
    private String firstName;
    @NotNull(message = "Last name is required")
    private String lastName;
    @NotNull(message = "Email is required")
    private String email;
}

2. Simplify Model Handling in @PostMapping

When validation fails, you don't need to manually re-add userRegistrationForm to the model. Spring automatically retains the validated object with its BindingResult errors when you return the view name. Clean up your method:

@PostMapping("/register")
public String handleRegistration(@ModelAttribute @Valid UserRegistrationForm userRegistrationForm, BindingResult result) {
    if (result.hasErrors()) {
        return "registration";
    }
    // Rest of your registration logic
}

3. Confirm Thymeleaf Form Binding

Ensure your template's <form> tag uses th:object="${registrationForm}" to bind to the form object. This is mandatory for *{firstName} and #fields.hasErrors('firstName') to work correctly:

<form th:action="@{/register}" th:object="${registrationForm}" method="post">
    <!-- Your existing form fields go here -->
</form>

4. Validate Dependencies

Make sure your project includes the Spring Boot Validation starter—this provides the underlying engine for @Valid to function:

Maven (pom.xml):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Gradle (build.gradle):

implementation 'org.springframework.boot:spring-boot-starter-validation'

Content sourced from Stack Exchange, question author Jschwery

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 08:03:01