Skip to content

/v3/api-docs returns 500 with spring-data-rest: NPE in SpringDocDataRestUtils.updateResponseSchemaEmbedded #3328

Description

@khippe

Describe the bug

With spring-boot-starter-data-rest on the classpath, every call to /v3/api-docs fails with HTTP 500 and the Swagger UI stays empty. A single JPA entity and a JpaRepository are enough - no custom annotations, no security, no @RestResource configuration.

The failure happens in SpringDocDataRestUtils.updateResponseSchemaEmbedded, where the schema behind the _embedded property is read without a null check:

// SpringDocDataRestUtils.java, line 293 ff.
if (openapi31) {
    JsonSchema jsonSchema = (JsonSchema) entry.getValue().getProperties().get(entityClassName);   // line 297
    ...
} else {
    ArraySchema arraySchema = (ArraySchema) entry.getValue().getProperties().get(entityClassName); // line 302
    ...
}

entry.getValue().getProperties() returns null, so both branches throw.

To Reproduce

Minimal project - four files, nothing else:

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.0</version>
</parent>

<properties>
    <java.version>25</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>3.1.0</version>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Item.java

@Entity
public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // getters and setters
}

ItemRepository.java

public interface ItemRepository extends JpaRepository<Item, Long> {
}

application.properties

spring.data.rest.base-path=/api
spring.datasource.url=jdbc:h2:mem:repro
spring.jpa.hibernate.ddl-auto=create-drop

Then:

mvn spring-boot:run
curl -i http://localhost:8080/v3/api-docs   # => HTTP/1.1 500

Expected behavior

/v3/api-docs returns 200 and describes the Spring Data REST resource.

Additional context

I tested four combinations. The NPE shows up in all of them:

springdoc springdoc.api-docs.version Json Processing Exception warnings Result
3.1.0 default (openapi_3_1) 124 500, NPE at line 297
3.1.0 openapi_3_0 0 500, NPE at line 302
3.0.3 default (openapi_3_1) 0 500, NPE at line 297

Two things follow from this, and I think both are worth pointing out:

  1. This is not a 3.1.0 regression. 3.0.3 fails the same way, so the missing null check has been
    there for a while. Only the warnings in the first row are new.
  2. This looks independent of SpringDocUtils logs Json Processing Exception occurred warning for every @Min-constrained Integer query parameter (regression since 3.1.0) #3314 / fix: Add a type deserializer to allow for JSON cloning of a JsonSchema object #3315. My first guess was that the failed JsonSchema clone
    from SpringDocUtils logs Json Processing Exception occurred warning for every @Min-constrained Integer query parameter (regression since 3.1.0) #3314 was leaving the schema empty, but the run with openapi_3_0 produces zero
    Json Processing Exception warnings and still throws — and so does 3.0.3, which predates the
    change fix: Add a type deserializer to allow for JSON cloning of a JsonSchema object #3315 addresses. So I would not expect fix: Add a type deserializer to allow for JSON cloning of a JsonSchema object #3315 to fix this on its own. Happy to be corrected
    if you read it differently.

I did not dig into why getProperties() is null for the _embedded schema - that part I'll leave to you, the reproducer above should make it easy to see.

Stacktrace (springdoc 3.1.0, default spec version, taken from the reproducer)

java.lang.NullPointerException: Cannot invoke "java.util.Map.get(Object)" because the return value of "io.swagger.v3.oas.models.media.Schema.getProperties()" is null
	at org.springdoc.core.utils.SpringDocDataRestUtils.updateResponseSchemaEmbedded(SpringDocDataRestUtils.java:297)
	at org.springdoc.core.utils.SpringDocDataRestUtils.updateResponseSchema(SpringDocDataRestUtils.java:278)
	at org.springdoc.core.utils.SpringDocDataRestUtils.lambda$updateApiResponse$3(SpringDocDataRestUtils.java:173)
	at java.base/java.util.HashMap$KeySet.forEach(HashMap.java:1017)
	at org.springdoc.core.utils.SpringDocDataRestUtils.lambda$updateApiResponse$4(SpringDocDataRestUtils.java:171)
	at java.base/java.util.LinkedHashMap$LinkedValues.forEach(LinkedHashMap.java:834)
	at org.springdoc.core.utils.SpringDocDataRestUtils.updateApiResponse(SpringDocDataRestUtils.java:166)
	at org.springdoc.core.utils.SpringDocDataRestUtils.lambda$customise$0(SpringDocDataRestUtils.java:150)
	at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:987)
	at org.springdoc.core.utils.SpringDocDataRestUtils.lambda$customise$1(SpringDocDataRestUtils.java:150)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
	at org.springdoc.core.utils.SpringDocDataRestUtils.lambda$customise$2(SpringDocDataRestUtils.java:145)
	at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:803)
	at org.springdoc.core.utils.SpringDocDataRestUtils.customise(SpringDocDataRestUtils.java:143)
	at org.springdoc.core.providers.SpringRepositoryRestResourceProvider.customize(SpringRepositoryRestResourceProvider.java:292)
	at org.springdoc.webmvc.api.OpenApiResource.lambda$getPaths$1(OpenApiResource.java:161)
	at java.base/java.util.Optional.ifPresent(Optional.java:178)
	at org.springdoc.webmvc.api.OpenApiResource.lambda$getPaths$4(OpenApiResource.java:158)
	at java.base/java.util.Optional.ifPresent(Optional.java:178)
	at org.springdoc.webmvc.api.OpenApiResource.getPaths(OpenApiResource.java:154)
	at org.springdoc.api.AbstractOpenApiResource.getOpenApi(AbstractOpenApiResource.java:404)
	at org.springdoc.webmvc.api.OpenApiResource.openapiJson(OpenApiResource.java:129)
	at org.springdoc.webmvc.api.OpenApiWebMvcResource.openapiJson(OpenApiWebMvcResource.java:117)

Environment

  • springdoc-openapi 3.1.0 (also reproduced with 3.0.3)
  • Spring Boot 4.1.0
  • Java 25 (Temurin 25.0.3)
  • spring-boot-starter-data-rest, spring-boot-starter-data-jpa, H2
  • spring.data.rest.base-path=/api
  • Workaround in use: springdoc.enable-data-rest=false - brings /v3/api-docs back to 200, but drops the Spring Data REST resource from the documentation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions