Skip to content

StackOverflowError with Kotlin Sealed Class Hierarchy and @JsonSubTypes after upgrading to 2.8.5 #2918

Closed
@machinecode

Description

@machinecode

We are experiencing a StackOverflowError during OpenAPI schema generation after upgrading springdoc-openapi-starter-common and springdoc-openapi-starter-webmvc-ui from version 2.3.0 to 2.8.5. This upgrade was part of a larger update, including Spring Boot from 3.3.7 to 3.4.1. We are using Kotlin and Gradle.

The issue appears to be related to the processing of a sealed class hierarchy with @JsonSubTypes annotation. The structure is similar to the following:

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "@type")
@JsonSubTypes(
    JsonSubTypes.Type(value = ConcreteClassA::class, nams = "TYPE_A"),
    JsonSubTypes.Type(value = ConcreteClassB::class, name = "TYPE_B"),
    // ... more subtypes ...
    JsonSubTypes.Type(value = ConcreteClassWithGrandchild::class, name = "TYPE_WITH_GRANDCHILD"),
    // ... more subtypes ...

)
sealed class AbstractBaseClass {
    abstract fun someAbstractFunction(): String?

    open fun anotherFunction() = someAbstractFunction() ?: ""
}

// Example of a simple subclass
data class ConcreteClassA(
    val someProperty: String?
) : AbstractBaseClass() {
    override fun someAbstractFunction(): String? { /* implementation */ }
}

// Intermediate abstract class
sealed class IntermediateAbstractClass : AbstractBaseClass() {
    abstract val commonProperty1: String?
    abstract val commonProperty2: String?
}
// Example of class extends to another sealed class
data class ConcreteClassWithGrandchild(
    @JsonAlias("alias_prop1")
    @JsonInclude(JsonInclude.Include.NON_NULL)
    override val commonProperty1: String? = null,

    @JsonAlias("alias_prop2")
    @JsonInclude(JsonInclude.Include.NON_NULL)
    val specificProperty: String?,

     override val commonProperty2: String?,

) : IntermediateAbstractClass() {
    override fun someAbstractFunction() = "someValue"
}

The key points are:

  • A sealed class (AbstractBaseClass) is used as the base.
  • @JsonTypeInfo and @JsonSubTypes are used for polymorphic deserialization.
  • There are multiple concrete subclasses, and, importantly, at least one subclass (ConcreteClassWithGrandchild) extends to another sealed class (IntermediateAbstractClass).
  • Some properties use @JsonAlias and @JsonInclude.

The exception occurs during the openApiGenerate Gradle task. The stack trace indicates a stack overflow within org.openapitools.codegen.DefaultCodegen.getAllOfDescendants, suggesting a problem with recursive processing of the class hierarchy.

org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':openApiGenerate'.
  ...
Caused by: org.gradle.api.GradleException: Code generation failed.
  ...
Caused by: java.lang.RuntimeException: Could not process model 'AbstractBaseClass'.Please make sure that your schema is correct!
  ...
Caused by: java.lang.RuntimeException: Stack overflow hit when looking for AbstractBaseClass an infinite loop starting and ending at ConcreteClassWithGrandchild was seen
    at org.openapitools.codegen.DefaultCodegen.getAllOfDescendants(DefaultCodegen.java:3491)
    at org.openapitools.codegen.DefaultCodegen.createDiscriminator(DefaultCodegen.java:3573)
    at org.openapitools.codegen.DefaultCodegen.fromModel(DefaultCodegen.java:3035)
    at org.openapitools.codegen.DefaultGenerator.processModels(DefaultGenerator.java:1765)
    at org.openapitools.codegen.DefaultGenerator.generateModels(DefaultGenerator.java:523)
  ...

This issue seems related to the previously reported and fixed issue: #2603. However, that fix doesn't seem to resolve the problem in our case, potentially because of the grandchild sealed class relationship, or some interaction with the other annotations.

Expected Behavior:
The OpenAPI schema should be generated successfully without a StackOverflowError.

Actual Behavior:
A StackOverflowError occurs during schema generation, preventing the build from completing.

Steps to Reproduce:
While I can't share the exact code, the provided class structure above outlines a simplified version. Creating a similar setup with:

  • Kotlin sealed class.
  • Multiple subtypes.
  • Grandchild in sealed class hierarchy.
  • @JsonTypeInfo, @JsonSubTypes, @JsonAlias, and @JsonInclude annotations.
  • Spring Boot 3.4.1 and springdoc-openapi 2.8.5

should reproduce it.

Environment:

  • Spring Boot: 3.4.1
  • springdoc-openapi-starter-common: 2.8.5
  • springdoc-openapi-starter-webmvc-ui: 2.8.5
  • Kotlin
  • Gradle

Could you please investigate this issue? It's breaking our doc generation completely. Thank you!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions