-
Notifications
You must be signed in to change notification settings - Fork 701
Description
Description
After upgrading from Spring Boot 3.x.x to Spring Boot 4.0.0,
AlpsJacksonJsonHttpMessageConverter starts intercepting all application/json requests,
even though:
- in Boot 3.x
AlpsJsonHttpMessageConverter#canRead()always returned false - in Boot 4.x
AlpsJacksonJsonHttpMessageConverter#canRead()incorrectly returns true via base class logic
As a result, Spring Data REST no longer resolves URI links such as:
{
"author": "http://localhost:8080/api/v1/users/1"
}and deserialization fails with:
InvalidFormatException: Cannot deserialize value of type `long`
from String "http://localhost:8080/api/v1/users/1"
This breaks entity relationship binding via URI.
Root Cause Analysis
In Spring Boot 4:
AlpsJacksonJsonHttpMessageConverter inherits from
AbstractSmartHttpMessageConverter.
Spring now calls:
AbstractSmartHttpMessageConverter#canRead(...)
instead of the converter's overridden method.
That method falls back to:
supports(clazz) && supportedMediaTypes.contains(application/json)
And the converter registers media types:
application/alps+json
application/json
*/*
Therefore, canRead incorrectly returns true for application/json.
In Boot 3.x the same converter always returned false for JSON, so
the correct Jackson-based PersistentEntityJacksonModule converter handled the payload and resolved URI links to Entity.
Reproduction
I have created minimal app to reproduce this bug.
Follow the next project instruction to reproduce the bug.
Expected behavior
Spring Data REST should:
- detect
"author": "http://.../users/1" - resolve entity via
UriToEntityConverter - bind it correctly to
Task.author
Exactly as it worked in Spring Boot 3.x.
Actual behavior in Spring Boot 4
- ALPS converter intercepts JSON
- no URI resolution happens
- deserialization fails before hitting Data REST
Workaround
Remove application/json from ALPS supported types
@Bean
public HttpMessageConverter<Object> patchAlps(AlpsJacksonJsonHttpMessageConverter converter) {
converter.setSupportedMediaTypes(
converter.getSupportedMediaTypes().stream()
.filter(mt -> !mt.includes(MediaType.APPLICATION_JSON))
.toList()
);
return converter;
}