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

REST接口使用LocalDate接收参数遇400错误,如何完善日期校验逻辑?

问题根因

你遇到的400错误发生在Jackson反序列化请求体的阶段,早于Bean Validation的校验流程,所以你定义的@NotNull注解还没生效,请求就被Spring MVC默认逻辑拦截返回了固定格式的400响应。具体两类非法输入的触发逻辑:

  • 传入空字符串、不符合yyyy-MM-dd格式的日期值、2月30日这类逻辑上不存在的日期时,Jackson内置的LocalDate反序列化器会直接抛出DateTimeParseException,被包装为HttpMessageNotReadableException后直接返回400
  • 如果传入JSON null值正常会走到校验逻辑触发@NotNull报错,如果你连这个场景也返回默认400,说明你没有配置全局异常处理器接管校验抛出的异常
解决方案

步骤1:自定义LocalDate反序列化器

统一处理空字符串、格式错误、逻辑无效日期的场景,你可以选择反序列化失败时返回null走后续校验,或者直接抛出带自定义错误信息的异常:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

public class CustomLocalDateDeserializer extends StdDeserializer<LocalDate> {
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;

    public CustomLocalDateDeserializer() {
        super(LocalDate.class);
    }

    @Override
    public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        String dateStr = parser.getText().trim();
        // 空字符串直接返回null,后续触发@NotNull校验
        if (dateStr.isBlank()) {
            return null;
        }
        try {
            return LocalDate.parse(dateStr, DATE_FORMATTER);
        } catch (DateTimeParseException e) {
            // 格式错误/无效日期抛自定义异常,后续全局异常处理器捕获
            throw new IllegalArgumentException("日期格式非法,必须为yyyy-MM-dd格式的有效日期");
        }
    }
}

注册反序列化器

你可以选择单个字段标注生效,或者全局配置所有LocalDate字段生效:

  • 单个字段生效:给PSRequest的字段加@JsonDeserialize注解
public class PSRequest {
    @JsonDeserialize(using = CustomLocalDateDeserializer.class)
    @NotNull(message = "fromDate cannot be null")
    protected LocalDate fromDate;

    @JsonDeserialize(using = CustomLocalDateDeserializer.class)
    @NotNull(message = "toDate cannot be null")
    protected LocalDate toDate;
}
  • 全局生效:配置Jackson自定义ObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper customObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        // 注册自定义LocalDate反序列化器
        javaTimeModule.addDeserializer(LocalDate.class, new CustomLocalDateDeserializer());
        objectMapper.registerModule(javaTimeModule);
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return objectMapper;
    }
}

步骤2:配置全局异常处理器

接管反序列化、校验两个阶段的所有异常,返回自定义结构的错误响应:

import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.http.converter.HttpMessageNotReadableException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理JSR校验触发的参数错误
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationError(MethodArgumentNotValidException e) {
        Map<String, String> errorMap = new HashMap<>();
        e.getBindingResult().getAllErrors().forEach(error -> {
            String field = ((FieldError) error).getField();
            String message = error.getDefaultMessage();
            errorMap.put(field, message);
        });
        return ResponseEntity.badRequest().body(errorMap);
    }

    // 处理Jackson反序列化触发的参数错误
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<Map<String, String>> handleDeserializationError(HttpMessageNotReadableException e) {
        Map<String, String> errorMap = new HashMap<>();
        Throwable rootCause = e.getRootCause();
        if (rootCause instanceof IllegalArgumentException) {
            errorMap.put("error", rootCause.getMessage());
        } else {
            errorMap.put("error", "请求参数格式非法");
        }
        return ResponseEntity.badRequest().body(errorMap);
    }

    // 处理单个参数校验触发的约束错误
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<Map<String, String>> handleConstraintError(ConstraintViolationException e) {
        Map<String, String> errorMap = new HashMap<>();
        for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
            errorMap.put(violation.getPropertyPath().toString(), violation.getMessage());
        }
        return ResponseEntity.badRequest().body(errorMap);
    }
}

配置完成后,你提到的三类非法输入都会返回你自定义的错误信息,不会再出现Spring默认的400响应结构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 03:09:01