Spring Boot 4.0.0: InvalidDefinitionException when trying to deserialise an interface with TestRestTemplate
I'm trying to migrate a project from Boot 3.5.8 to 4.0.0, but I'm having hard time making Boot to deserialise interfaces.
Please see the attached project, this is the tutorial (the complete/ project), modified to reproduce the use case at issue.
To summarise:
EntityControllerreturns anEntityfor a GET method.Entityis an Interface, the controller instantiates an implementing concrete class and returns it- Entity has a
Metadataproperty attached. its getter returns this interface,EntityImpl.getMetadata()returnsMetadataImpl EntityControllerTestusesTestRestTemplateto call the GET URL and get anEntity, or anEntityImpl(I've tried both, with similar results)
What it gets instead is (when I use EntityImpl in restTemplate.exchange()):
[ERROR] com.example.restservice.EntityControllerTest.testBasics -- Time elapsed: 0.376 s <<< ERROR!
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.example.restservice.Metadata]
at org.springframework.http.converter.AbstractJacksonHttpMessageConverter.readJavaType(AbstractJacksonHttpMessageConverter.java:363)
at org.springframework.http.converter.AbstractJacksonHttpMessageConverter.read(AbstractJacksonHttpMessageConverter.java:322)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:112)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1035)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1019)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:757)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:677)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:561)
at org.springframework.boot.resttestclient.TestRestTemplate.exchange(TestRestTemplate.java:725)
at com.example.restservice.EntityControllerTest.testBasics(EntityControllerTest.java:32)
Caused by: tools.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.restservice.Metadata` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #83] (through reference chain: com.example.restservice.EntityImpl["metadata"])
at tools.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:70)
at tools.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1958)
at tools.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:448)
at tools.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1488)
at tools.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:254)
at tools.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:552)
at tools.jackson.databind.deser.bean.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:746)
at tools.jackson.databind.deser.bean.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:642)
at tools.jackson.databind.deser.bean.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1417)
at tools.jackson.databind.deser.bean.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:480)
at tools.jackson.databind.deser.bean.BeanDeserializer.deserialize(BeanDeserializer.java:200)
at tools.jackson.databind.deser.DeserializationContextExt.readRootValue(DeserializationContextExt.java:265)
at tools.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1646)
at tools.jackson.databind.ObjectReader.readValue(ObjectReader.java:1171)
at org.springframework.http.converter.AbstractJacksonHttpMessageConverter.readJavaType(AbstractJacksonHttpMessageConverter.java:355)
... 9 more
And a similar error when I use Entity.class in restTemplate.exchange().
This happens despite @JsonCreator in both the constructors of EntityImpl and MetadataImpl, plus @JsonDeserialize ( contentAs = MetadataImpl.class ) in EntityImpl.getMetadata() or in the metadata parameter for EntityImpl's constructor (or in both).
This used to work well before Spring Boot 4. From the error message, I understand I should provide a mapping from abstract to concrete types, but so far, the mentioned annotations were the way to provide such a mapping. Is there some other mechanism now? Do I need to tell the TestRestTemplate to use the same annotations that the server is using? How?
Note that I don't want to place annotations like JsonDeserialize at the interface level, since this violates the Dependency Inversion Principle and would not work well when switching to an alternative implementation of the application data model.
Finally, I've tried the same with the rest test client and got the same error.
OK, after struggling with CoPilot, I think I found a way:
// @JsonDeserialize no longer necessary
@Configuration
public class JacksonConfig
{
@Bean
public JacksonModule jacksonMappingsModule ()
{
SimpleModule module = new SimpleModule();
module.addAbstractTypeMapping ( Metadata.class, MetadataImpl.class );
return module;
}
}
Which fixes the way Jackson 3.0.2 works. But it's ugly, maybe there is a way to re-enable the old behaviour?
This used to work well before Spring Boot 4.
I am failing to see how you think this is a Spring Boot problem. From the description you're aware of the move to Jackson 3 and the impacts this may have. I've tried to illustrate that by adding the deprecated Jackson 2 module and configuring HTTP to use Jackson2 via spring.http.converters.preferred-json-mapper=jackson2 and the sample failed with:
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.example.restservice.Metadata]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:405)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:356)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:103)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1035)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1019)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:757)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:677)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:561)
at org.springframework.boot.resttestclient.TestRestTemplate.exchange(TestRestTemplate.java:725)
at com.example.restservice.EntityControllerTest.testBasics(EntityControllerTest.java:32)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.restservice.Metadata` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 84] (through reference chain: com.example.restservice.EntityImpl["metadata"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1943)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:415)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1430)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:273)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:553)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:597)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:490)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1499)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:340)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2146)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1504)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:397)
... 9 more
I then downgraded your sample to Spring Boot 3.5.8 and it fails the same way.
The sample isn't actionable. If you think something in Spring Boot has to change, then please provide a sample that fails with 4.0 but does not with 3.5.
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.