@Pattern注解无法校验LocalDate格式,如何返回自定义错误信息?
问题描述
我定义了一个接收项目名称和起始日期的ProjectDTO:
@Data @AllArgsConstructor @NoArgsConstructor public class ProjectDTO { @NotBlank(message = "project name is required") private String projectName; @NotNull(message = "project start date is required") @Pattern(regexp = "^\\d{4}\\-(0[1-9]|1[012])\\-(0[1-9]|[12][0-9]|3[01])$", message = "date format should be yyyy-MM-dd") private LocalDate startDate; }
同时实现了全局异常处理器:
@RestControllerAdvice public class ApplicationExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) private ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) { List<String> errors = ex.getBindingResult() .getFieldErrors() .stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.toList()); ApiException apiException = ApiException.builder() .errors(errors) .httpStatus(HttpStatus.BAD_REQUEST) .timestamp(ZonedDateTime.now(ZoneId.of("Z"))) .build(); return new ResponseEntity<>(apiException, HttpStatus.BAD_REQUEST); } }
当项目名称传入null时,校验正常生效,返回自定义错误响应:
{ "errors": [ "project name is required" ], "httpStatus": "BAD_REQUEST", "timestamp": "2023-02-17T05:06:08.9362836Z" }
但传入错误格式的起始日期(例如2023/12-17)时,校验未触发,返回Spring默认的400错误:
{ "timestamp": "2023-02-17T05:21:07.004+00:00", "status": 400, "error": "Bad Request", "path": "/api/projects" }
控制台报错显示反序列化失败:
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.time.LocalDate` from String "2023/02-17": Failed to deserialize java.time.LocalDate: (java.time.format.DateTimeParseException) Text '2023/02-17' could not be parsed at index 4; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.LocalDate` from String "2023/02-17": Failed to deserialize java.time.LocalDate: (java.time.format.DateTimeParseException) Text '2023/02-17' could not be parsed at index 4<LF> at [Source: (PushbackInputStream); line: 5, column: 25] (through reference chain: com.theravado.donationservice.dto.ProjectRequest["projects"]->java.util.ArrayList[0]->com.theravado.donationservice.dto.ProjectDTO["startDate"])]
需要让日期格式错误时返回指定的date format should be yyyy-MM-dd提示,而非默认反序列化错误。
解决方案
问题核心是:Jackson反序列化阶段先失败,根本没走到JSR-380的校验环节。@Pattern是字符串校验注解,而字段类型为LocalDate,Jackson尝试将错误格式的字符串转为LocalDate失败,直接抛出HttpMessageNotReadableException,跳过了后续校验。
以下是两种可行的解决方式:
方式一:修改字段类型为String,先校验再转换
将startDate改为String类型,通过@Pattern完成格式校验后,再在业务逻辑中转为LocalDate:
@Data @AllArgsConstructor @NoArgsConstructor public class ProjectDTO { @NotBlank(message = "project name is required") private String projectName; @NotNull(message = "project start date is required") @Pattern(regexp = "^\\d{4}\\-(0[1-9]|1[012])\\-(0[1-9]|[12][0-9]|3[01])$", message = "date format should be yyyy-MM-dd") private String startDate; // 可选:提供转换方法 public LocalDate getStartDateAsLocalDate() { return LocalDate.parse(startDate); } }
这种方式确保格式校验优先执行,失败时触发MethodArgumentNotValidException,进入自定义异常处理器返回指定提示。
方式二:配置Jackson日期规则+捕获反序列化异常
步骤1:配置Jackson强制使用指定日期格式
在application.properties中添加:
spring.jackson.date-format=yyyy-MM-dd spring.jackson.serialization.write-dates-as-timestamps=false spring.jackson.deserialization.accept-date-formats=yyyy-MM-dd
或通过配置类自定义ObjectMapper:
@Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); JavaTimeModule module = new JavaTimeModule(); module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); mapper.registerModule(module); return mapper; } }
步骤2:扩展全局异常处理器,捕获HttpMessageNotReadableException
修改异常处理器,判断是否为日期反序列化错误,返回自定义提示:
@RestControllerAdvice public class ApplicationExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) private ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) { List<String> errors = ex.getBindingResult() .getFieldErrors() .stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.toList()); ApiException apiException = ApiException.builder() .errors(errors) .httpStatus(HttpStatus.BAD_REQUEST) .timestamp(ZonedDateTime.now(ZoneId.of("Z"))) .build(); return new ResponseEntity<>(apiException, HttpStatus.BAD_REQUEST); } @ExceptionHandler(HttpMessageNotReadableException.class) private ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex) { List<String> errors = new ArrayList<>(); // 判定是否为LocalDate反序列化失败 if (ex.getCause() instanceof InvalidFormatException) { InvalidFormatException invalidFormatEx = (InvalidFormatException) ex.getCause(); if (LocalDate.class.equals(invalidFormatEx.getTargetType())) { errors.add("date format should be yyyy-MM-dd"); } else { errors.add("请求体格式错误"); } } else { errors.add("请求体解析失败"); } ApiException apiException = ApiException.builder() .errors(errors) .httpStatus(HttpStatus.BAD_REQUEST) .timestamp(ZonedDateTime.now(ZoneId.of("Z"))) .build(); return new ResponseEntity<>(apiException, HttpStatus.BAD_REQUEST); } }
这种方式保留LocalDate字段类型,通过Jackson配置强制格式,同时捕获反序列化异常返回自定义提示,更贴合原有代码结构。
内容的提问来源于stack exchange,提问作者Shanka Somasiri
相关产品推荐
相关产品推荐

