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

Spring Boot+Thymeleaf项目表单验证失效问题求助

Spring Boot + Thymeleaf表单验证失效问题排查

问题现象

提交空表单时,@NotEmpty、@Pattern等验证注解未生效,直接触发NullPointerException,日志显示绑定结果无错误。

依赖配置(pom.xml)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.5.Final</version>
</dependency>

DTO类代码

package com.example.randomtext.web;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

@Getter
@Setter
@ToString
public class MainPageForm {
    @NotEmpty(message = "choose the source file")
    private SourceFile sourceFile;
    private String sourceText;
    private boolean sourceTextSet;
    private String generatedText;
    @Pattern(regexp = "[0-9]+", message = "input the whole number")
    private String lengthOfOutput;

    @RequiredArgsConstructor
    @Getter
    public enum SourceFile {
        ALICE("alice.txt"),
        CONFUCIUS("confucius.txt"),
        private static final SourceFile[] ALL_VALUES = SourceFile.values();
        private final String fileName;
    }
}

Thymeleaf表单(main.html)

<html>
<head><title>RandomText</title></head>
<body>
<h3>Random text generator</h3>
<pre th:if="${!mainPageForm.sourceTextSet}" th:style="'color:red;'">
    source text not set. Have you chosen the source file?</pre>
<form action="#" th:action="@{/random_text}" th:object="${mainPageForm}"
      method="post">
    <label>source file</label><br>
    <div th:each="sourceFile : ${T(com.example.randomtext.web.MainPageForm.SourceFile).values()}">
        <input type="radio" th:field="*{sourceFile}" th:value="${sourceFile}">
        <label th:text="${sourceFile.getFileName()}">source file</label>
        <pre th:style="'color:red;'"
            th:if="${#fields.hasErrors('sourceFile')}"
        th:errors="*{sourceFile}"></pre>
        <br>
    </div>
    <label>length of string to be generated<br>
        <input type="text" width="200" th:field="*{lengthOfOutput}">
        <pre th:style="'color:red;'"
           th:if="${#fields.hasErrors('lengthOfOutput')}"
           th:errors="*{lengthOfOutput}"></pre>
    </label><br><br>
    <br><input type="submit" value="Submit"/><input type="reset" value="Reset"/>
</form>
<pre th:text="${mainPageForm.sourceText}" th:style="'color:black;'">
    source text</pre>
</body>
</html>

控制器代码

package com.example.randomtext.web;

import com.example.randomtext.RandomTextService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import javax.validation.Valid;
import java.net.URISyntaxException;

import static com.example.randomtext.Constants.BASE_PATH;
import static com.example.randomtext.Constants.MAIN_TEMPLATE;

@Controller
@RequiredArgsConstructor
@Slf4j
public class MainPageController {
    private final RandomTextService service;

    @GetMapping(BASE_PATH)
    public String doGet(MainPageForm form) {
        return MAIN_TEMPLATE;
    }

    @PostMapping(BASE_PATH)
    public String doPost(@Valid MainPageForm mainPageForm, BindingResult bindingResult) throws URISyntaxException {
        log.info("got form data {}", mainPageForm.toString());
        if (!bindingResult.hasErrors()) {
            log.info("see no errors :E");
            log.info(bindingResult.toString());
            log.info("sourceFile {}", mainPageForm.getSourceFile().getFileName());
            mainPageForm.setSourceText(service.readSourceString(mainPageForm.getSourceFile().getFileName()));
        } else {
            log.info("got errors");
            bindingResult.getFieldErrors()
                         .forEach(e -> log.error("got validation error {} for field {}",
                                                 e.getDefaultMessage(),
                                                 e.getField()));
        }
        return MAIN_TEMPLATE;
    }
}

提交空表单后的日志

2023-01-30T20:22:49.481+03:00  INFO 5464 --- [p-nio-80-exec-6] c.e.randomtext.web.MainPageController    : got form data MainPageForm(sourceFile=null, sourceText=null, sourceTextSet=false, generatedText=null, lengthOfOutput=)
2023-01-30T20:22:49.481+03:00  INFO 5464 --- [p-nio-80-exec-6] c.e.randomtext.web.MainPageController    : see no errors :E
2023-01-30T20:22:49.481+03:00  INFO 5464 --- [p-nio-80-exec-6] c.e.randomtext.web.MainPageController    : org.springframework.validation.BeanPropertyBindingResult: 0 errors
2023-01-30T20:22:49.483+03:00 ERROR 5464 --- [p-nio-80-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "com.example.randomtext.web.MainPageForm$SourceFile.getFileName()" because the return value of "com.example.randomtext.web.MainPageForm.getSourceFile()" is null] with root cause

已尝试操作

  • 调整javax.validation和hibernate-validator依赖版本
  • 执行maven clean命令

解决方案

1. 修正枚举类型的验证注解

@NotEmpty仅适用于字符串、集合、数组等类型,枚举类型的非空验证需使用@NotNull:

@NotNull(message = "choose the source file")
private SourceFile sourceFile;

2. 移除冗余依赖

spring-boot-starter-validation已包含validation-api和hibernate-validator的适配版本,手动引入易导致版本冲突,删除pom.xml中以下内容:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.5.Final</version>
</dependency>

3. 完善长度字段验证

@Pattern仅验证格式,空字符串不会触发该规则,需添加@NotEmpty确保输入不为空:

@NotEmpty(message = "length cannot be empty")
@Pattern(regexp = "[0-9]+", message = "input the whole number")
private String lengthOfOutput;

4. 确保枚举值绑定正确

Thymeleaf中枚举值绑定需传递枚举名称,修改radio的value属性:

<input type="radio" th:field="*{sourceFile}" th:value="${sourceFile.name()}">

完成以上调整后,重启项目即可触发正常的表单验证,避免空指针异常。


内容的提问来源于stack exchange,提问作者Ignat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 00:41:14