Springdoc-openapi: ClassNotFoundException: DefaultedPageable

Created on 13 Jun 2020  路  10Comments  路  Source: springdoc/springdoc-openapi

Hi, I have recently migrated my springboot (version 2.3.1) app from springfox to springdoc openapi (version 1.4.1).

The app uses spring-boot-starter-data-elasticsearch and has some endpoints that receive Pageable as parameters and return a Page.

I've added those 2 dependencies

<!-- SpringDoc OpenApi -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.4.1</version>
</dependency>
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-data-rest</artifactId>
    <version>1.4.1</version>
</dependency>

I am getting an exception whose stacktrace is shown below. It's complaining that org.springframework.data.rest.webmvc.support.DefaultedPageable is missing.

2020-06-13 20:14:32.508 ERROR [publisher-api,,,] 33952 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalStateException: Unable to load cache item
        at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:79) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.context.annotation.ConfigurationClassEnhancer.createClass(ConfigurationClassEnhancer.java:137) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.context.annotation.ConfigurationClassEnhancer.enhance(ConfigurationClassEnhancer.java:109) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:423) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurationClassPostProcessor.java:257) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:291) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:131) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:707) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.3.1.RELEASE.jar:2.3.1.RELEASE]
        at com.mycompany.publisherapi.PublisherApiApplication.main(PublisherApiApplication.java:12) ~[classes/:na]
Caused by: java.lang.NoClassDefFoundError: org/springframework/data/rest/webmvc/support/DefaultedPageable
        at org.springdoc.data.rest.SpringDocDataRestConfiguration.<clinit>(SpringDocDataRestConfiguration.java:74) ~[springdoc-openapi-data-rest-1.4.1.jar:1.4.1]
        at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
        at java.base/java.lang.Class.forName(Class.java:398) ~[na:na]
        at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:571) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
        at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        ... 21 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.springframework.data.rest.webmvc.support.DefaultedPageable
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581) ~[na:na]
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
        ... 32 common frames omitted

Thanks in advance!

Most helpful comment

@OskarsPakers,

From your description, you don't need to load springdoc-openapi-data-rest. (You will load unecessary beans related to spring-data-rest)

If you just need to enable the support of Pageable, you can just add the following line:

SpringDocUtils.getConfig().replaceWithClass(org.springframework.data.domain.Pageable.class, Pageable.class);

This is explained here: https://springdoc.org/
Section [Spring Data Rest support]

Also, a fix will be added to handle this case, for projects relying on springdoc-openapi-data-rest for Pageable Support.

All 10 comments

Find class SpringDocDataRestConfiguration and overide it.
In my case it occurs that I haven't got mvc dependency, when I added it many wrong things happend :)
So I created in my code (Yes I know that this is workaround)

package org.springdoc.data.rest;

import com.querydsl.core.types.Predicate;
import org.springdoc.core.converters.models.Pageable;
import org.springdoc.data.rest.customisers.QuerydslPredicateOperationCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;

import java.util.Optional;

import static org.springdoc.core.Constants.SPRINGDOC_ENABLED;
import static org.springdoc.core.SpringDocUtils.getConfig;

@Configuration
@ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true)
public class SpringDocDataRestConfiguration {

static {
    getConfig().replaceWithClass(org.springframework.data.domain.Pageable.class, Pageable.class)
            .replaceWithClass(org.springframework.data.domain.PageRequest.class, Pageable.class);
}

@Bean
@ConditionalOnMissingBean
@Primary
@Lazy(false)
DataRestHalProvider halProvider(Optional<RepositoryRestConfiguration> repositoryRestConfiguration) {
    return new DataRestHalProvider(repositoryRestConfiguration);
}


@ConditionalOnClass(value = {QuerydslBindingsFactory.class})
class QuerydslProvider {

    @Bean
    @ConditionalOnMissingBean
    @Lazy(false)
    QuerydslPredicateOperationCustomizer queryDslQuerydslPredicateOperationCustomizer(Optional<QuerydslBindingsFactory> querydslBindingsFactory) {
        if (querydslBindingsFactory.isPresent()) {
            getConfig().addRequestWrapperToIgnore(Predicate.class);
            return new QuerydslPredicateOperationCustomizer(querydslBindingsFactory.get());
        }
        return null;
    }
}

}

Adding dependency spring-boot-starter-data-rest seem to resolve the error.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

Would be nice if springdoc would work without this dependency too.

@OskarsPakers,

From your description, you don't need to load springdoc-openapi-data-rest. (You will load unecessary beans related to spring-data-rest)

If you just need to enable the support of Pageable, you can just add the following line:

SpringDocUtils.getConfig().replaceWithClass(org.springframework.data.domain.Pageable.class, Pageable.class);

This is explained here: https://springdoc.org/
Section [Spring Data Rest support]

Also, a fix will be added to handle this case, for projects relying on springdoc-openapi-data-rest for Pageable Support.

Hi @bnasslahsen , thanks for solving the issue!

What I did was, basically, remove the springdoc-openapi-data-rest dependency and add the following to my SpringDocOpenApiConfig file:

@Configuration
public class SpringDocOpenApiConfig {

    ...

    static {
        SpringDocUtils.getConfig().replaceWithClass(org.springframework.data.domain.Pageable.class, Pageable.class);
    }

}

Besides, in the controller endpoint that uses Pageable, I hidded the pageable parameter (because I didn't want it as request body) and added the @PageableAsQueryParam annotation

@GetMapping
@PageableAsQueryParam
public Page<News> getNews(@Parameter(hidden = true) Pageable pageable) {
    return newsService.listAllNewsByPage(pageable);
}

So far so good!

But, just one question: in the documentation, it stated that

"Since, v1.3.1 you can use as well @ParameterObject instead of @PageableAsQueryParam for HTTP GET methods."

Do you have an example that uses @ParameterObject. I was not able to set it.

Thanks!

Hey @bnasslahsen ,

After checking your example, I was able to use @ParameterObject to handle the Pageable.

However, in order to make it works, I needed to add back the dependency org.springdoc:springdoc-openapi-data-rest:1.4.1. And by adding this dependency, in order to not have the ClassNotFoundException exceptions that I described in the issue, I need also to add org.springframework.boot:spring-boot-starter-data-rest as suggested by @OskarsPakers

P.S. I still have SpringDocUtils.getConfig().replaceWithClass(org.springframework.data.domain.Pageable.class, Pageable.class);

But, I can stick to @PageableAsQueryParam, no problem. Thanks

@ivangfr,

@ParameterObject is completly independent from org.springdoc:springdoc-openapi-data-rest:1.4.1.

Why did you need back org.springdoc:springdoc-openapi-data-rest ?

Here is my project: https://github.com/ivangfr/spring-cloud-stream-elasticsearch/tree/master/publisher-api

In the pom.xml, I have

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.4.1</version>
</dependency>

My StringDocOpenApiConfig.java

@Configuration
public class SpringDocOpenApiConfig {

    ...

    static {
        SpringDocUtils.getConfig().replaceWithClass(org.springframework.data.domain.Pageable.class, Pageable.class);
    }

}

My enpoint

@Operation(summary = "Get News")
@PageableAsQueryParam
@GetMapping
public Page<News> getNews(@Parameter(hidden = true) Pageable pageable) {
    return newsService.listAllNewsByPage(pageable);
}

/v3/api-docs

{
    ...
    "paths": {
        "/api/news": {
            "get": {
                "operationId": "getNews",
                "parameters": [
                    {
                        "description": "Zero-based page index (0..N)",
                        "in": "query",
                        "name": "page",
                        "schema": {
                            "default": "0",
                            "type": "integer"
                        }
                    },
                    {
                        "description": "The size of the page to be returned",
                        "in": "query",
                        "name": "size",
                        "schema": {
                            "default": "20",
                            "type": "integer"
                        }
                    },
                    {
                        "description": "Sorting criteria in the format: property(,asc|desc). Default sort order is ascending. Multiple sort criteria are supported.",
                        "in": "query",
                        "name": "sort",
                        "schema": {
                            "items": {
                                "type": "string"
                            },
                            "type": "array"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "content": {
                            "*/*": {
                                "schema": {
                                    "$ref": "#/components/schemas/PageNews"
                                }
                            }
                        },
                        "description": "OK"
                    }
                },
                "summary": "Get News",
                "tags": [
                    "news-controller"
                ]
            }
        }
    },
    ...
}

It doesn't work if I change the endpoint configuration to

import io.swagger.v3.oas.annotations.Operation;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import javax.validation.constraints.NotNull;
...

@Operation(summary = "Get News")
@GetMapping
public Page<News> getNews(@NotNull @ParameterObject Pageable pageable) {
    return newsService.listAllNewsByPage(pageable);
}

/v3/api-docs

{
    ...
    "paths": {
        "/api/news": {
            "get": {
                "operationId": "getNews",
                "responses": {
                    "200": {
                        "content": {
                            "*/*": {
                                "schema": {
                                    "$ref": "#/components/schemas/PageNews"
                                }
                            }
                        },
                        "description": "OK"
                    }
                },
                "summary": "Get News",
                "tags": [
                    "news-controller"
                ]
            }
        }
    },
    ...
}

@ivangfr,

It's working on your sample as well, using @ParameterObject!
I am quite sure you didn't import the right Pageable type.
For less confusion, here is the right working code:

    static {
        SpringDocUtils.getConfig().replaceWithClass(org.springframework.data.domain.Pageable.class, org.springdoc.core.converters.models.Pageable.class);
    }

Let me know, if you made it work, so we can update the documentation accordingly.

Hey @bnasslahsen ! Exactly, that was the problem!

I've imported from org.springframework.data.domain.Pageable instead of org.springdoc.core.converters.models.

By setting the Pageable absolute class name in the 2nd argument of replaceWithClass method will avoid some confusion.

Best

Was this page helpful?
0 / 5 - 0 ratings

Related issues

debargharoy picture debargharoy  路  3Comments

bfs-natewallman picture bfs-natewallman  路  4Comments

kfirfer picture kfirfer  路  5Comments

Orxcle-xx picture Orxcle-xx  路  4Comments

SSK-code picture SSK-code  路  4Comments