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

@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 03:36:30