4

The problem: A feign client, making an API call to a Spring boot Rest API that returns a Page<T> can't deserialize the sort property of that page.

  • Spring Boot: 2.3.3.Release
  • Spring Cloud Feign: 2.2.5.RELEASE

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of org.springframework.data.domain.Sort (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (BufferedReader); line: 1, column: 238] (through reference chain: org.springframework.cloud.openfeign.support.PageJacksonModule$SimplePageImpl["sort"])

Not sure why the registered PageJacksonModule doesn't seem to support that.

Given a manually configured Feign client:

public class TelematicsConfig {

  private String host;

  ObjectMapper provideObjectMapper() {
    return new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .setPropertyNamingStrategy(SnakeCaseStrategy.SNAKE_CASE)
        .registerModule(new PageJacksonModule());
  }

  @Bean
  TelematicsClient provideTelematicsClient() {
    return Feign.builder()
        .client(new OkHttpClient())
        .encoder(new JacksonEncoder(provideObjectMapper()))
        .decoder(new JacksonDecoder(provideObjectMapper()))
        .logger(new Slf4jLogger(TelematicsClient.class))
        .logLevel(Logger.Level.FULL)
        .target(TelematicsClient.class, host);
  }

}

The client itself:

public interface TelematicsClient {

  @RequestLine("GET /api/v1/telematics/devices")
  Page<TelematicsDevice> getDevices();

}

When invoking this I get:

2020-09-16 12:38:49.707 ERROR 96244 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException: Cannot construct instance of `org.springframework.data.domain.Sort` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (BufferedReader); line: 1, column: 238] (through reference chain: org.springframework.cloud.openfeign.support.PageJacksonModule$SimplePageImpl["sort"]) reading GET http://localhost:8081/api/v1/telematics/devices] with root cause

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Sort` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (BufferedReader); line: 1, column: 238] (through reference chain: org.springframework.cloud.openfeign.support.PageJacksonModule$SimplePageImpl["sort"])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1611)
    at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1077)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1320)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:535)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:419)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1310)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3463)
    at feign.jackson.JacksonDecoder.decode(JacksonDecoder.java:61)

Any insights into why this isn't working would be greatly appreciated.

Edit: The following class seems to hint at support for sorting, no?

https://github.com/spring-cloud/spring-cloud-openfeign/blob/master/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageJacksonModule.java#L69

Erik
  • 997
  • 4
  • 14
  • 24

4 Answers4

18

If you use autoconfigured Feign client you can follow Spring Cloud OpenFeign docs by turning on corresponding configuration property:

You may consider enabling Jackson Modules for the support org.springframework.data.domain.Page and org.springframework.data.domain.Sort decoding.

feign.autoconfiguration.jackson.enabled=true
Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74
  • Oh nice! I missed this in the docs. This seems like it should be the default setting, if it's not already. – Erik Feb 11 '21 at 14:14
10

Found the answer.

https://github.com/spring-cloud/spring-cloud-openfeign/blob/master/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule.java

    return new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .setPropertyNamingStrategy(SnakeCaseStrategy.SNAKE_CASE)
        .registerModule(new PageJacksonModule())
        .registerModule(new SortJacksonModule()); // <-- This.  duh.
Erik
  • 997
  • 4
  • 14
  • 24
  • PageJacksonModule not working with Snake_case because they hardcoded totalElements property to be always in camel_case using @JsonProperty("TotalElements"). – roma2341 Dec 27 '22 at 08:25
  • It even can break existing code, because my code works fine , returns Page from my controller with total_elements property, but feign client can' return Page. But when i add PageJacksonModule feign client works fine, but property TotalElements become in pascalCase both at feign and my controllers( – roma2341 Dec 27 '22 at 08:26
0

try to add :

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-openfeign-core</artifactId>
        <version>3.1.3</version>
        <scope>compile</scope>
    </dependency>
Mohamed.Abdo
  • 2,054
  • 1
  • 19
  • 12
0

You can get these modules by adding the openfeign-core dependency:

implementation "org.springframework.cloud:spring-cloud-openfeign-core"

But this doesn't work in Spring Boot 3 for the SortJacksonModule anymore.

For example:

Code (Kotlin)
private val mapper = jacksonObjectMapper().registerModules(
    PageJacksonModule(),
    SortJacksonModule(),
    JavaTimeModule(),
)
Config:

Config Not Found

Throws this exception:

java.lang.NoClassDefFoundError: feign/codec/EncodeException at org.springframework.cloud.openfeign.support.SortJacksonModule.setupModule(SortJacksonModule.java:47)

Resolution

The OpenFeign spring team are waiting for votes in order to spend the time to fix this issue. Make sure to vote on the issue here:

https://github.com/spring-cloud/spring-cloud-openfeign/issues/675

TheJeff
  • 3,665
  • 34
  • 52