quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.77k stars 2.68k forks source link

Spring-data throw JsonMappingException when using PageImpl #41292

Open jedla97 opened 4 months ago

jedla97 commented 4 months ago

Describe the bug

Returnning PageImpl to the endpoint causing throw com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.UnsupportedOperationException) (through reference chain: org.springframework.data.domain.PageImpl["pageable"]->org.springframework.data.domain.Unpaged["pageSize"])

This is probably caused by bump of spring in https://github.com/quarkusio/quarkus/pull/40344.

The attached reproduces working with 3.11.2 and failing with 999-SNAPSHOT

Also using quarkus-rest-jackson the endpoint return 500 and using quarkus-resteasy-jackson return 400 but that's probably difference in implamentation of these extensions.

Using quarkus-resteasy-jackson returning Not able to deserialize data provided. and error is shown only in log.

Expected behavior

Expecting to get result from endpoint based on passed parameters.

For example on http://localhost:8080/book/paged?size=1&page=0 return should be like this

{"content":[{"bid":1,"name":"Sapiens","publicationYear":2011,"isbn":9789295055025}],"pageable":"INSTANCE","last":true,"totalPages":1,"totalElements":1,"first":true,"numberOfElements":1,"size":0,"number":0,"sort":{"sorted":false,"unsorted":true,"empty":true},"empty":false}

Actual behavior

Error:

java.lang.UnsupportedOperationException
    at org.springframework.data.domain.Unpaged.getPageSize(Unpaged.java:65)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:688)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:183)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:183)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:502)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:341)
    at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1574)
    at com.fasterxml.jackson.databind.ObjectWriter._writeValueAndClose(ObjectWriter.java:1275)
    at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1098)
    at io.quarkus.resteasy.reactive.jackson.runtime.serialisers.BasicServerJacksonMessageBodyWriter.writeResponse(BasicServerJacksonMessageBodyWriter.java:39)
    at org.jboss.resteasy.reactive.server.core.ServerSerialisers.invokeWriter(ServerSerialisers.java:217)
    at org.jboss.resteasy.reactive.server.core.serialization.DynamicEntityWriter.write(DynamicEntityWriter.java:113)
    at org.jboss.resteasy.reactive.server.handlers.ResponseWriterHandler.handle(ResponseWriterHandler.java:32)
    at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:147)
    at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
    at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:599)
    at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2516)
    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2495)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1521)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:1583)
Resulted in: com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.UnsupportedOperationException) (through reference chain: org.springframework.data.domain.PageImpl["pageable"]->org.springframework.data.domain.Unpaged["pageSize"])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:402)
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:361)
    at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:323)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:778)
    ... 23 more

How to Reproduce?

  1. git clone -b spring-paged-rest git@github.com:jedla97/quarkus-reproducers.git
  2. cd quarkus-reproducers
  3. mvn clean verify
  4. To pass test use mvn clean verify -Dquarkus.platform.version=3.11.2

In addition you can run it in dev mode and access it on http://localhost:8080/book/paged?size=1&page=0

Also you can try resteasy by change branche to spring-paged-resteasy

Output of uname -a or ver

Fedora 40, Ubuntu 22 (Github runner)

Output of java -version

OpenJDK 21

Quarkus version or git rev

main

Build tool (ie. output of mvnw --version or gradlew --version)

Apache Maven 3.9.3

Additional information

No response

quarkus-bot[bot] commented 4 months ago

/cc @geoand (spring)

jedla97 commented 4 months ago

cc aureamunoz as this is probably related to bump of the spring

gsmet commented 4 months ago

Let's make sure we add some tests when we fix this. Thanks!

michalvavrik commented 1 month ago

hey @jedla97 @aureamunoz, I am looking at the Quarkus QE test coverage for Spring and wondering about this one. Is this fixed, to do, or not a bug?

jedla97 commented 1 month ago

I try the reproducer and it's still failing, so it's not fixed/explained.

From my POV it's a bug, as this worked fine before. Maybe it's only error in implementation as something changes but I don't see anything in our migration guide.

aureamunoz commented 1 month ago

Oops, I completely missed this one, I need to take a look closer. I will work on it next week

michalvavrik commented 1 month ago

Thanks @jedla97 @aureamunoz

aureamunoz commented 1 month ago

I investigated this issue, and it turns out the problem isn't on our side. Starting from Spring Boot 3.2, Unpaged instances are no longer Jackson-serializable by default. As mentioned in the linked issue, the problem isn't that Unpaged instances now are non-serializable. Previously, Unpaged was represented as an Enum, which always serialized to a String. However, this has changed—Unpaged is now represented by an object, which alters the serialization behavior.

To resolve this, you should use the PageImpl constructor that takes a List of content, pagination details, and the total item count, rather than just passing the collection. In your reproducer code, passing only the collection results in the creation of an Unpaged object making it non-serializable.

To fix the issue, I recommend updating the BookStore#findPaged method as follows:

public Page<Book> findPaged(Pageable pageable) {
        List<Book> list = this.findAll().list();
        return new PageImpl<>(list,pageable,list.size());
    }

cc @jedla97 @michalvavrik

michalvavrik commented 1 month ago

thanks for investigation @aureamunoz I'll let @jedla97 look into it when he is back next week