使用@NotNull、@NotBlank和@Valid时仍触发必填校验错误的问题
我在UserDTO类中使用@NotBlank和@NotNull注解校验firstName、lastName、userRole字段,同时通过@Valid注解嵌套校验CrewDTO。在UserController的signup接口中,通过@RequestBody @Valid接收UserDTO参数,且已通过Postman传入包含对应字段有效值的请求体,但仍收到错误提示:First Name is mandatory, Last Name is mandatory and Role is mandatory,请帮忙排查问题原因。
相关代码
UserDTO
package com.pocketxtra.eventmanager.controller.dto; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.pocketxtra.eventmanager.service.domain.Gender; import com.pocketxtra.eventmanager.service.domain.Role; import com.pocketxtra.eventmanager.service.domain.UserType; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class UserDTO { String id; private String contact; @NotBlank(message = "First name is mandatory") private String firstName; @NotBlank(message = "Last name is mandatory") private String lastName; @NotNull(message = "Role is mandatory") private Role userRole; private UserType userType; @Valid @JsonProperty("crew") private CrewDTO crewDTO; }
CrewDTO
package com.pocketxtra.eventmanager.controller.dto; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.pocketxtra.eventmanager.service.domain.WeekDay; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class CrewDTO extends UserDTO { @NotNull(message = "Date of Birth is mandatory") private Date dob; @Valid @NotNull(message = "Education details is mandatory") @JsonProperty("education") private EducationDTO educationDTO; @NotNull(message = "Primary location is mandatory") private String primaryLocation; }
UserController.signup接口
@PostMapping("/signup") public ResponseEntity<String> signup(@RequestBody @Valid UserDTO userDTO) { // 业务逻辑代码 }
Postman请求体
{ "contact": "3245345451", "firstName": "Aman", "lastName": "Kumar", "userRole": "CREW", "crew": { "primaryLocation": "abcdncandkjcn", "dob": "1999-09-16T08:33:59.309Z", "education": { "college": "ABC", "course": "BCD", "city": "KANPUR" } } }
问题原因分析
核心问题在于CrewDTO继承了UserDTO,而UserDTO中使用@Valid注解嵌套校验CrewDTO。当校验触发时,会对CrewDTO的所有字段(包括从UserDTO继承来的firstName、lastName、userRole)进行校验,但请求体中crew对象里并没有这些字段——这些字段是在UserDTO的根层级传入的。因此CrewDTO实例中的继承字段值为null/空,触发了@NotBlank和@NotNull的校验规则,导致返回错误提示。
另外需要额外确认:请求体中的userRole值"CREW"是否和Role枚举类中的枚举值完全匹配(包括大小写),如果枚举值定义为Crew而不是CREW,也会导致反序列化失败,进而触发@NotNull校验失败。
解决方案
方案1:移除CrewDTO对UserDTO的继承(推荐)
CrewDTO作为UserDTO的嵌套字段,不应该继承UserDTO,这会导致字段重复和校验逻辑冲突。修改CrewDTO为独立类:
package com.pocketxtra.eventmanager.controller.dto; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class CrewDTO { @NotNull(message = "Date of Birth is mandatory") private Date dob; @Valid @NotNull(message = "Education details is mandatory") @JsonProperty("education") private EducationDTO educationDTO; @NotNull(message = "Primary location is mandatory") private String primaryLocation; }
方案2:调整校验策略(保留继承时的妥协方案)
如果必须保留继承关系,可以在CrewDTO中覆盖父类的校验字段,添加@Null注解允许这些字段为空:
public class CrewDTO extends UserDTO { // 覆盖父类字段,允许为空 @Null private String firstName; @Null private String lastName; @Null private Role userRole; // 其他原有字段... }
方案3:确认枚举反序列化正确性
检查Role枚举类的定义,确保CREW是有效的枚举值:
public enum Role { CREW, ADMIN; // 其他枚举值... }
如果枚举值是小写或其他格式,可添加@JsonCreator注解支持灵活反序列化:
public enum Role { CREW, ADMIN; @JsonCreator public static Role fromValue(String value) { return Role.valueOf(value.toUpperCase()); } }
内容的提问来源于stack exchange,提问作者Sagar Kumar

