Spring Boot 3.0迁3.3时Jackson FAIL_ON_TRAILING_TOKENS异常问题
将Spring Boot应用从3.0迁移至3.3版本后,自定义MultiDateDeserializer抛出FAIL_ON_TRAILING_TOKENS异常,尽管已显式配置ObjectMapper禁用该特性,且调试确认使用的是同一个ObjectMapper实例。
配置代码
@Bean public Module dateSerializationModule() { return new SimpleModule("dateFormat") .addDeserializer(Date.class, new MultiDateDeserializer()).addSerializer(Date.class, new DateSerializer()); } @Bean @Primary public ObjectMapper mapper() { return new ObjectMapper() .findAndRegisterModules().registerModule(attributesModule()).registerModule(dateSerializationModule()) .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, false) .configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); }
异常信息
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Trailing token (of type FIELD_NAME) found after value (bound as
com.fasterxml.jackson.databind.JsonNode): not allowed as perDeserializationFeature.FAIL_ON_TRAILING_TOKENSCaused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Trailing token (of type FIELD_NAME) found after value (bound as
com.fasterxml.jackson.databind.JsonNode): not allowed as perDeserializationFeature.FAIL_ON_TRAILING_TOKENSat [Source: REDACTED (StreamReadFeature.INCLUDE_SOURCE_IN_LOCATIONdisabled); line: 1, column: 11847] (through reference chain: my.dto.MyDTO["aDateField"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63) ~[jackson-databind-2.17.1.jar:2.17.1]
at com.fasterxml.jackson.databind.DeserializationContext.reportTrailingTokens(DeserializationContext.java:1840) ~[jackson-databind-2.17.1.jar:2.17.1]
at com.fasterxml.jackson.databind.ObjectMapper._verifyNoTrailingTokens(ObjectMapper.java:5013) ~[jackson-databind-2.17.1.jar:2.17.1]
at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:4886) ~[jackson-databind-2.17.1.jar:2.17.1]
at com.fasterxml.jackson.databind.ObjectMapper.readTree(ObjectMapper.java:3145) ~[jackson-databind-2.17.1.jar:2.17.1]
at my.app.MultiDateDeserializer.deserialize(MultiDateDeserializer.java:41) ~[main/:na]
自定义反序列化器代码
public class MultiDateDeserializer extends StdDeserializer<Date> { public MultiDateDeserializer() { this(null); } public MultiDateDeserializer(Class<?> vc) { super(vc); } @Override public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { JsonNode node = jp.getCodec().readTree(jp); // 异常抛出位置 // ... 日期解析逻辑 } }
对应JSON字段
aDateField: "2022-03-09T00:00:00.000"
核心原因
Jackson 2.17(Spring Boot 3.3默认依赖版本)对readTree方法的尾随token检查逻辑进行了强化:在字段级反序列化器中调用readTree(jp)时,该方法会读取当前字段的value节点,随后检查整个JSON流是否还有未处理的token(比如DTO的其他字段),此时会判定为“尾随token”并触发异常。而Jackson 2.14版本中无此严格检查。
解决方案
修改自定义反序列化器,直接读取当前字段的value,无需通过readTree转换为JsonNode:
@Override public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { // 直接读取日期字符串 String dateStr = jp.getValueAsString(); // 执行你的日期解析逻辑(比如兼容多种格式) if (dateStr == null || dateStr.isEmpty()) { return null; } // 示例:使用DateTimeFormatter解析ISO格式日期 DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; LocalDateTime localDateTime = LocalDateTime.parse(dateStr, formatter); return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); }
备选方案(不推荐)
若必须保留readTree的使用方式,可通过DeserializationContext临时禁用该检查,但会破坏全局配置一致性:
@Override public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { // 临时禁用尾随token检查 DeserializationContext modifiedContext = ctxt.with(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, false); JsonNode node = modifiedContext.readValue(jp, JsonNode.class); // ... 后续日期解析逻辑 }
内容的提问来源于stack exchange,提问作者alvinmeimoun

