SpringBoot返回List<UUID>时触发HttpMessageNotWritableException问题
核心原因:Java泛型类型擦除
你遇到的问题本质是泛型类型擦除导致的。Java泛型是编译期特性,运行时所有泛型参数会被擦除为其上限类型(通常是Object)。你的UUIDListConvertor继承自泛型JsonConverter<List<UUID>>,但运行时JsonConverter中的泛型参数T会被擦除,无法保留List<UUID>的具体类型信息。
当Spring的Jackson消息转换器在序列化响应中的List<UUID>时,由于无法识别列表元素的实际类型是UUID,会默认按String类型处理,进而触发String无法转换为UUID的类型转换异常。
而手动创建List<UUID>返回时,方法的返回类型或DTO字段的泛型信息在编译期已明确,Jackson可以通过反射获取到List<UUID>的具体类型,因此能正确序列化。
为何手动指定TypeReference<List<UUID>>能解决问题
TypeReference是Jackson提供的工具类,它通过匿名内部类的方式保留了泛型类型信息(JVM会在匿名内部类的字节码中记录泛型参数)。当你手动指定TypeReference<List<UUID>>时,Jackson可以明确知道要处理的是List<UUID>类型,从而正确序列化/反序列化每个UUID元素。
可行解决方案
方案1:改进自定义转换器,明确指定TypeReference
修改UUIDListConvertor,直接在子类中定义具体的TypeReference,绕过泛型擦除的限制:
public class UUIDListConvertor extends JsonConverter<List<UUID>> { // 明确指定List<UUID>的TypeReference private static final TypeReference<List<UUID>> UUID_LIST_TYPE = new TypeReference<>() {}; private final ObjectMapper objectMapper = new ObjectMapper(); @Override public String convertToDatabaseColumn(List<UUID> attribute) { try { return objectMapper.writeValueAsString(attribute); } catch (JsonProcessingException e) { throw new RuntimeException("Failed to serialize UUID list", e); } } @Override public List<UUID> convertToEntityAttribute(String dbData) { try { return objectMapper.readValue(dbData, UUID_LIST_TYPE); } catch (JsonProcessingException e) { throw new RuntimeException("Failed to deserialize UUID list", e); } } }
方案2:在DTO字段上明确指定序列化器
如果不想修改转换器,可以在DTO的List<UUID>字段上添加Jackson注解,指定元素的序列化/反序列化器:
public class OrderRequestDTO { @JsonSerialize(contentUsing = UUIDSerializer.class) @JsonDeserialize(contentUsing = UUIDDeserializer.class) private List<UUID> items; // getter/setter }
Jackson自带UUIDSerializer和UUIDDeserializer,可以直接使用,确保元素按UUID类型处理。
方案3:为泛型转换器添加类型信息获取逻辑
如果需要保留泛型JsonConverter的通用性,可以在父类中通过反射获取子类的泛型参数类型:
public abstract class JsonConverter<T> implements AttributeConverter<T, String> { protected final ObjectMapper objectMapper = new ObjectMapper(); protected final TypeReference<T> typeReference; @SuppressWarnings("unchecked") public JsonConverter() { // 通过反射获取子类的泛型参数类型 Type superType = getClass().getGenericSuperclass(); if (superType instanceof ParameterizedType parameterizedType) { Type actualType = parameterizedType.getActualTypeArguments()[0]; this.typeReference = new TypeReference<T>() { @Override public Type getType() { return actualType; } }; } else { throw new IllegalArgumentException("Generic supertype required"); } } @Override public String convertToDatabaseColumn(T attribute) { try { return objectMapper.writeValueAsString(attribute); } catch (JsonProcessingException e) { throw new RuntimeException("Serialize failed", e); } } @Override public T convertToEntityAttribute(String dbData) { try { return objectMapper.readValue(dbData, typeReference); } catch (JsonProcessingException e) { throw new RuntimeException("Deserialize failed", e); } } }
这样UUIDListConvertor继承后,父类就能通过反射获取到List<UUID>的类型信息,无需手动指定TypeReference。
内容的提问来源于stack exchange,提问作者XProtocol

