From 3ae776357005f117cab32253d13b6ba5e2a32fce Mon Sep 17 00:00:00 2001
From: Sahil Kumar <45749746+sahil-ramagiri@users.noreply.github.com>
Date: Fri, 17 Jan 2025 22:39:37 +0000
Subject: [PATCH] Do not require JsonSubType annotation for sealed classes.

---
 .../configuration/SpringDocConfiguration.java |   2 +-
 .../SpringDocSealedClassModule.java           |  64 +++++
 .../api/v30/app238/AbstractParent.java        |  66 +++++
 .../api/v30/app238/ConcreteParent.java        |  66 +++++
 .../springdoc/api/v30/app238/Controller.java  |  68 ++++++
 .../api/v30/app238/SpringDocApp238Test.java   |  36 +++
 .../api/v30/app239/HelloController.java       |  51 ++++
 .../api/v30/app239/SpringDocApp239Test.java   |  35 +++
 .../test/resources/results/3.0.1/app238.json  | 227 ++++++++++++++++++
 .../test/resources/results/3.0.1/app239.json  | 132 ++++++++++
 10 files changed, 746 insertions(+), 1 deletion(-)
 create mode 100644 springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSealedClassModule.java
 create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/AbstractParent.java
 create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/ConcreteParent.java
 create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/Controller.java
 create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/SpringDocApp238Test.java
 create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app239/HelloController.java
 create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app239/SpringDocApp239Test.java
 create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app238.json
 create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app239.json

diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java
index 4c39763c9..7e6bc4889 100644
--- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java
+++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java
@@ -423,7 +423,7 @@ SpringDocProviders springDocProviders(Optional<ActuatorProvider> actuatorProvide
 			Optional<RepositoryRestResourceProvider> repositoryRestResourceProvider, Optional<RouterFunctionProvider> routerFunctionProvider,
 			Optional<SpringWebProvider> springWebProvider,
 			ObjectMapperProvider objectMapperProvider) {
-		objectMapperProvider.jsonMapper().registerModule(new SpringDocRequiredModule());
+		objectMapperProvider.jsonMapper().registerModules(new SpringDocRequiredModule(), new SpringDocSealedClassModule());
 		return new SpringDocProviders(actuatorProvider, springCloudFunctionProvider, springSecurityOAuth2Provider, repositoryRestResourceProvider, routerFunctionProvider, springWebProvider, objectMapperProvider);
 	}
 
diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSealedClassModule.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSealedClassModule.java
new file mode 100644
index 000000000..37b7a7290
--- /dev/null
+++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSealedClassModule.java
@@ -0,0 +1,64 @@
+/*
+ *
+ *  *
+ *  *  *
+ *  *  *  * Copyright 2025 the original author or authors.
+ *  *  *  *
+ *  *  *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  *  *  * you may not use this file except in compliance with the License.
+ *  *  *  * You may obtain a copy of the License at
+ *  *  *  *
+ *  *  *  *      https://www.apache.org/licenses/LICENSE-2.0
+ *  *  *  *
+ *  *  *  * Unless required by applicable law or agreed to in writing, software
+ *  *  *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  *  *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  *  *  * See the License for the specific language governing permissions and
+ *  *  *  * limitations under the License.
+ *  *  *
+ *  *
+ *
+ */
+
+package org.springdoc.core.configuration;
+
+import com.fasterxml.jackson.databind.introspect.Annotated;
+import com.fasterxml.jackson.databind.jsontype.NamedType;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import io.swagger.v3.core.jackson.SwaggerAnnotationIntrospector;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The type Spring doc sealed class module.
+ *
+ * @author sahil-ramagiri
+ */
+public class SpringDocSealedClassModule extends SimpleModule {
+
+	@Override
+	public void setupModule(SetupContext context) {
+		context.insertAnnotationIntrospector(new RespectSealedClassAnnotationIntrospector());
+	}
+
+	/**
+	 * The type sealed class annotation introspector.
+	 */
+	private static class RespectSealedClassAnnotationIntrospector extends SwaggerAnnotationIntrospector {
+
+		@Override
+		public List<NamedType> findSubtypes(Annotated annotated) {
+			ArrayList<NamedType> subTypes = new ArrayList<>();
+
+			if (annotated.getAnnotated() instanceof Class<?> clazz && clazz.isSealed()) {
+				Class<?>[] permittedSubClasses = clazz.getPermittedSubclasses();
+				if (permittedSubClasses.length > 0) {
+					Arrays.stream(permittedSubClasses).map(NamedType::new).forEach(subTypes::add);
+				}
+			}
+
+			return subTypes;
+		}
+	}
+}
\ No newline at end of file
diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/AbstractParent.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/AbstractParent.java
new file mode 100644
index 000000000..5ef6af397
--- /dev/null
+++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/AbstractParent.java
@@ -0,0 +1,66 @@
+/*
+ *
+ *  *
+ *  *  *
+ *  *  *  *
+ *  *  *  *  * Copyright 2025 the original author or authors.
+ *  *  *  *  *
+ *  *  *  *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  *  *  *  * you may not use this file except in compliance with the License.
+ *  *  *  *  * You may obtain a copy of the License at
+ *  *  *  *  *
+ *  *  *  *  *      https://www.apache.org/licenses/LICENSE-2.0
+ *  *  *  *  *
+ *  *  *  *  * Unless required by applicable law or agreed to in writing, software
+ *  *  *  *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  *  *  *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  *  *  *  * See the License for the specific language governing permissions and
+ *  *  *  *  * limitations under the License.
+ *  *  *  *
+ *  *  *
+ *  *
+ *
+ */
+
+package test.org.springdoc.api.v30.app238;
+
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+
+@JsonTypeInfo(use = Id.NAME, property = "type")
+public abstract sealed class AbstractParent {
+	private int id;
+
+	public int getId() {
+		return id;
+	}
+
+	public void setId(int id) {
+		this.id = id;
+	}
+}
+
+final class ChildOfAbstract1 extends AbstractParent {
+	private String abstrachChild1Param;
+
+	public String getAbstrachChild1Param() {
+		return abstrachChild1Param;
+	}
+
+	public void setAbstrachChild1Param(String abstrachChild1Param) {
+		this.abstrachChild1Param = abstrachChild1Param;
+	}
+}
+
+final class ChildOfAbstract2 extends AbstractParent {
+	private String abstractChild2Param;
+
+	public String getAbstractChild2Param() {
+		return abstractChild2Param;
+	}
+
+	public void setAbstractChild2Param(String abstractChild2Param) {
+		this.abstractChild2Param = abstractChild2Param;
+	}
+}
diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/ConcreteParent.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/ConcreteParent.java
new file mode 100644
index 000000000..de49b5ab3
--- /dev/null
+++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/ConcreteParent.java
@@ -0,0 +1,66 @@
+/*
+ *
+ *  *
+ *  *  *
+ *  *  *  *
+ *  *  *  *  * Copyright 2025 the original author or authors.
+ *  *  *  *  *
+ *  *  *  *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  *  *  *  * you may not use this file except in compliance with the License.
+ *  *  *  *  * You may obtain a copy of the License at
+ *  *  *  *  *
+ *  *  *  *  *      https://www.apache.org/licenses/LICENSE-2.0
+ *  *  *  *  *
+ *  *  *  *  * Unless required by applicable law or agreed to in writing, software
+ *  *  *  *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  *  *  *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  *  *  *  * See the License for the specific language governing permissions and
+ *  *  *  *  * limitations under the License.
+ *  *  *  *
+ *  *  *
+ *  *
+ *
+ */
+
+package test.org.springdoc.api.v30.app238;
+
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+
+@JsonTypeInfo(use = Id.NAME, property = "type")
+public sealed class ConcreteParent {
+	private int id;
+
+	public int getId() {
+		return id;
+	}
+
+	public void setId(int id) {
+		this.id = id;
+	}
+}
+
+final class ChildOfConcrete1 extends ConcreteParent {
+	private String concreteChild1Param;
+
+	public String getConcreteChild1Param() {
+		return concreteChild1Param;
+	}
+
+	public void setConcreteChild1Param(String concreteChild1Param) {
+		this.concreteChild1Param = concreteChild1Param;
+	}
+}
+
+final class ChildOfConcrete2 extends ConcreteParent {
+	private String concreteChild2Param;
+
+	public String getConcreteChild2Param() {
+		return concreteChild2Param;
+	}
+
+	public void setConcreteChild2Param(String concreteChild2Param) {
+		this.concreteChild2Param = concreteChild2Param;
+	}
+}
diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/Controller.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/Controller.java
new file mode 100644
index 000000000..7fa6c0302
--- /dev/null
+++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/Controller.java
@@ -0,0 +1,68 @@
+/*
+ *
+ *  *
+ *  *  *
+ *  *  *  *
+ *  *  *  *  * Copyright 2025 the original author or authors.
+ *  *  *  *  *
+ *  *  *  *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  *  *  *  * you may not use this file except in compliance with the License.
+ *  *  *  *  * You may obtain a copy of the License at
+ *  *  *  *  *
+ *  *  *  *  *      https://www.apache.org/licenses/LICENSE-2.0
+ *  *  *  *  *
+ *  *  *  *  * Unless required by applicable law or agreed to in writing, software
+ *  *  *  *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  *  *  *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  *  *  *  * See the License for the specific language governing permissions and
+ *  *  *  *  * limitations under the License.
+ *  *  *  *
+ *  *  *
+ *  *
+ *
+ */
+
+package test.org.springdoc.api.v30.app238;
+
+import java.util.List;
+
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("class-hierarchy")
+public class Controller {
+	@PostMapping("abstract-parent")
+	public Response abstractParent(@RequestBody AbstractParent payload) {
+		return null;
+	}
+
+	@PostMapping("concrete-parent")
+	public Response concreteParent(@RequestBody ConcreteParent payload) {
+		return null;
+	}
+}
+
+class Response {
+	AbstractParent abstractParent;
+
+	List<ConcreteParent> concreteParents;
+
+	public AbstractParent getAbstractParent() {
+		return abstractParent;
+	}
+
+	public void setAbstractParent(AbstractParent abstractParent) {
+		this.abstractParent = abstractParent;
+	}
+
+	public List<ConcreteParent> getConcreteParents() {
+		return concreteParents;
+	}
+
+	public void setConcreteParents(List<ConcreteParent> concreteParents) {
+		this.concreteParents = concreteParents;
+	}
+}
diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/SpringDocApp238Test.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/SpringDocApp238Test.java
new file mode 100644
index 000000000..396c2c68b
--- /dev/null
+++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app238/SpringDocApp238Test.java
@@ -0,0 +1,36 @@
+/*
+ *
+ *  *
+ *  *  *
+ *  *  *  *
+ *  *  *  *  * Copyright 2025 the original author or authors.
+ *  *  *  *  *
+ *  *  *  *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  *  *  *  * you may not use this file except in compliance with the License.
+ *  *  *  *  * You may obtain a copy of the License at
+ *  *  *  *  *
+ *  *  *  *  *      https://www.apache.org/licenses/LICENSE-2.0
+ *  *  *  *  *
+ *  *  *  *  * Unless required by applicable law or agreed to in writing, software
+ *  *  *  *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  *  *  *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  *  *  *  * See the License for the specific language governing permissions and
+ *  *  *  *  * limitations under the License.
+ *  *  *  *
+ *  *  *
+ *  *
+ *
+ */
+
+package test.org.springdoc.api.v30.app238;
+
+import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+
+public class SpringDocApp238Test extends AbstractSpringDocV30Test {
+
+	@SpringBootApplication
+	static class SpringDocTestApp {}
+}
diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app239/HelloController.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app239/HelloController.java
new file mode 100644
index 000000000..8f74c1227
--- /dev/null
+++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app239/HelloController.java
@@ -0,0 +1,51 @@
+package test.org.springdoc.api.v30.app239;
+
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class HelloController {
+	
+    @PostMapping("/parent")
+    public void parentEndpoint(@RequestBody Superclass parent) {
+        
+    }
+	
+}
+
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "@type")
+sealed class Superclass permits IntermediateClass {
+
+	public Superclass() {}
+}
+
+@Schema(name = IntermediateClass.SCHEMA_NAME)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "@type")
+sealed class IntermediateClass extends Superclass permits FirstChildClass, SecondChildClass {
+
+	public static final String SCHEMA_NAME = "IntermediateClass";
+}
+
+@Schema(name = FirstChildClass.SCHEMA_NAME)
+final class FirstChildClass extends IntermediateClass {
+
+	public static final String SCHEMA_NAME = "Image";
+}
+
+@Schema(name = SecondChildClass.SCHEMA_NAME)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "@type")
+sealed class SecondChildClass extends IntermediateClass {
+
+	public static final String SCHEMA_NAME = "Mail";
+}
+
+@Schema(name = ThirdChildClass.SCHEMA_NAME)
+final class ThirdChildClass extends SecondChildClass {
+
+	public static final String SCHEMA_NAME = "Home";
+}
\ No newline at end of file
diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app239/SpringDocApp239Test.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app239/SpringDocApp239Test.java
new file mode 100644
index 000000000..d80d2caa0
--- /dev/null
+++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app239/SpringDocApp239Test.java
@@ -0,0 +1,35 @@
+/*
+ *
+ *  *
+ *  *  *
+ *  *  *  *
+ *  *  *  *  * Copyright 2025 the original author or authors.
+ *  *  *  *  *
+ *  *  *  *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  *  *  *  * you may not use this file except in compliance with the License.
+ *  *  *  *  * You may obtain a copy of the License at
+ *  *  *  *  *
+ *  *  *  *  *      https://www.apache.org/licenses/LICENSE-2.0
+ *  *  *  *  *
+ *  *  *  *  * Unless required by applicable law or agreed to in writing, software
+ *  *  *  *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  *  *  *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  *  *  *  * See the License for the specific language governing permissions and
+ *  *  *  *  * limitations under the License.
+ *  *  *  *
+ *  *  *
+ *  *
+ *
+ */
+
+package test.org.springdoc.api.v30.app239;
+
+import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+public class SpringDocApp239Test extends AbstractSpringDocV30Test {
+
+	@SpringBootApplication
+	static class SpringDocTestApp {}
+}
diff --git a/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app238.json b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app238.json
new file mode 100644
index 000000000..1f5dfa891
--- /dev/null
+++ b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app238.json
@@ -0,0 +1,227 @@
+{
+  "openapi": "3.0.1",
+  "info": {
+    "title": "OpenAPI definition",
+    "version": "v0"
+  },
+  "servers": [
+    {
+      "url": "http://localhost",
+      "description": "Generated server url"
+    }
+  ],
+  "paths": {
+    "/class-hierarchy/concrete-parent": {
+      "post": {
+        "tags": [
+          "controller"
+        ],
+        "operationId": "concreteParent",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "oneOf": [
+                  {
+                    "$ref": "#/components/schemas/ConcreteParent"
+                  },
+                  {
+                    "$ref": "#/components/schemas/ChildOfConcrete1"
+                  },
+                  {
+                    "$ref": "#/components/schemas/ChildOfConcrete2"
+                  }
+                ]
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Response"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/class-hierarchy/abstract-parent": {
+      "post": {
+        "tags": [
+          "controller"
+        ],
+        "operationId": "abstractParent",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "oneOf": [
+                  {
+                    "$ref": "#/components/schemas/ChildOfAbstract1"
+                  },
+                  {
+                    "$ref": "#/components/schemas/ChildOfAbstract2"
+                  }
+                ]
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/Response"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "ChildOfConcrete1": {
+        "type": "object",
+        "allOf": [
+          {
+            "$ref": "#/components/schemas/ConcreteParent"
+          },
+          {
+            "type": "object",
+            "properties": {
+              "concreteChild1Param": {
+                "type": "string"
+              }
+            }
+          }
+        ]
+      },
+      "ChildOfConcrete2": {
+        "type": "object",
+        "allOf": [
+          {
+            "$ref": "#/components/schemas/ConcreteParent"
+          },
+          {
+            "type": "object",
+            "properties": {
+              "concreteChild2Param": {
+                "type": "string"
+              }
+            }
+          }
+        ]
+      },
+      "ConcreteParent": {
+        "required": [
+          "type"
+        ],
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "type": {
+            "type": "string"
+          }
+        },
+        "discriminator": {
+          "propertyName": "type"
+        }
+      },
+      "AbstractParent": {
+        "required": [
+          "type"
+        ],
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "type": {
+            "type": "string"
+          }
+        },
+        "discriminator": {
+          "propertyName": "type"
+        }
+      },
+      "ChildOfAbstract1": {
+        "type": "object",
+        "allOf": [
+          {
+            "$ref": "#/components/schemas/AbstractParent"
+          },
+          {
+            "type": "object",
+            "properties": {
+              "abstrachChild1Param": {
+                "type": "string"
+              }
+            }
+          }
+        ]
+      },
+      "ChildOfAbstract2": {
+        "type": "object",
+        "allOf": [
+          {
+            "$ref": "#/components/schemas/AbstractParent"
+          },
+          {
+            "type": "object",
+            "properties": {
+              "abstractChild2Param": {
+                "type": "string"
+              }
+            }
+          }
+        ]
+      },
+      "Response": {
+        "type": "object",
+        "properties": {
+          "abstractParent": {
+            "oneOf": [
+              {
+                "$ref": "#/components/schemas/ChildOfAbstract1"
+              },
+              {
+                "$ref": "#/components/schemas/ChildOfAbstract2"
+              }
+            ]
+          },
+          "concreteParents": {
+            "type": "array",
+            "items": {
+              "oneOf": [
+                {
+                  "$ref": "#/components/schemas/ConcreteParent"
+                },
+                {
+                  "$ref": "#/components/schemas/ChildOfConcrete1"
+                },
+                {
+                  "$ref": "#/components/schemas/ChildOfConcrete2"
+                }
+              ]
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app239.json b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app239.json
new file mode 100644
index 000000000..de2244501
--- /dev/null
+++ b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app239.json
@@ -0,0 +1,132 @@
+{
+  "openapi": "3.0.1",
+  "info": {
+    "title": "OpenAPI definition",
+    "version": "v0"
+  },
+  "servers": [
+    {
+      "url": "http://localhost",
+      "description": "Generated server url"
+    }
+  ],
+  "paths": {
+    "/parent": {
+      "post": {
+        "tags": [
+          "hello-controller"
+        ],
+        "operationId": "parentEndpoint",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "oneOf": [
+                  {
+                    "$ref": "#/components/schemas/Superclass"
+                  },
+                  {
+                    "$ref": "#/components/schemas/IntermediateClass"
+                  },
+                  {
+                    "$ref": "#/components/schemas/Image"
+                  },
+                  {
+                    "$ref": "#/components/schemas/Mail"
+                  },
+                  {
+                    "$ref": "#/components/schemas/Home"
+                  }
+                ]
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK"
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "Home": {
+        "type": "object",
+        "allOf": [
+          {
+            "$ref": "#/components/schemas/Mail"
+          }
+        ]
+      },
+      "Image": {
+        "type": "object",
+        "allOf": [
+          {
+            "$ref": "#/components/schemas/IntermediateClass"
+          }
+        ]
+      },
+      "IntermediateClass": {
+        "required": [
+          "@type"
+        ],
+        "type": "object",
+        "discriminator": {
+          "propertyName": "@type"
+        },
+        "allOf": [
+          {
+            "$ref": "#/components/schemas/Superclass"
+          },
+          {
+            "type": "object",
+            "properties": {
+              "@type": {
+                "type": "string"
+              }
+            }
+          }
+        ]
+      },
+      "Mail": {
+        "required": [
+          "@type"
+        ],
+        "type": "object",
+        "discriminator": {
+          "propertyName": "@type"
+        },
+        "allOf": [
+          {
+            "$ref": "#/components/schemas/IntermediateClass"
+          },
+          {
+            "type": "object",
+            "properties": {
+              "@type": {
+                "type": "string"
+              }
+            }
+          }
+        ]
+      },
+      "Superclass": {
+        "required": [
+          "@type"
+        ],
+        "type": "object",
+        "properties": {
+          "@type": {
+            "type": "string"
+          }
+        },
+        "discriminator": {
+          "propertyName": "@type"
+        }
+      }
+    }
+  }
+}