Skip to content

Spring Boot 4.0.0: InvalidDefinitionException when trying to deserialise an interface with TestRestTemplate #48340

@marco-brandizi

Description

@marco-brandizi

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:

  • EntityController returns an Entity for a GET method. Entity is an Interface, the controller instantiates an implementing concrete class and returns it
  • Entity has a Metadata property attached. its getter returns this interface, EntityImpl.getMetadata() returns MetadataImpl
  • EntityControllerTest uses TestRestTemplate to call the GET URL and get an Entity, or an EntityImpl (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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: feedback-reminderWe've sent a reminder that we need additional information before we can continuestatus: waiting-for-feedbackWe need additional information before we can continuestatus: waiting-for-triageAn issue we've not yet triaged

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions