diff --git a/docs/generators/kotlin-spring.md b/docs/generators/kotlin-spring.md
index 6c43046fbfb9..d9a28aaf1391 100644
--- a/docs/generators/kotlin-spring.md
+++ b/docs/generators/kotlin-spring.md
@@ -34,6 +34,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|documentationProvider|Select the OpenAPI documentation provider.|
**none**
Do not publish an OpenAPI specification.
**source**
Publish the original input OpenAPI specification.
**springdoc**
Generate an OpenAPI 3 specification using SpringDoc.
|springdoc|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|exceptionHandler|generate default global exception handlers (not compatible with reactive. enabling reactive will disable exceptionHandler )| |true|
+|fixJacksonJsonTypeInfoInheritance|Only applies when useSealedDiscriminatorClasses is true. When true (default), ensures Jackson polymorphism works correctly by: (1) always setting visible=true on @JsonTypeInfo, and (2) adding the discriminator property to child models with appropriate default values. When false, visible is only set to true if all children already define the discriminator property.| |true|
|gradleBuildFile|generate a gradle build file using the Kotlin DSL| |true|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|includeHttpRequestContext|Whether to include HttpServletRequest (blocking) or ServerWebExchange (reactive) as additional parameter in generated methods.| |false|
@@ -61,6 +62,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|useFlowForArrayReturnType|Whether to use Flow for array/collection return types when reactive is enabled. If false, will use List instead.| |true|
|useJackson3|Use Jackson 3 dependencies (tools.jackson package). Only available with `useSpringBoot4`. Defaults to true when `useSpringBoot4` is enabled. Incompatible with `openApiNullable`.| |false|
|useResponseEntity|Whether (when false) to return actual type (e.g. List<Fruit>) and handle non-happy path responses via exceptions flow or (when true) return entire ResponseEntity (e.g. ResponseEntity<List<Fruit>>). If disabled, method are annotated using a @ResponseStatus annotation, which has the status of the first response declared in the Api definition| |true|
+|useSealedDiscriminatorClasses|Generate sealed classes instead of interfaces for discriminator models. When true, discriminator parent models are generated as sealed classes with proper Jackson @JsonTypeInfo/@JsonSubTypes annotations and child classes extend the parent with constructor inheritance. When false (default), the legacy interface-based polymorphism is used.| |false|
|useSealedResponseInterfaces|Generate sealed interfaces for endpoint responses that all possible response types implement. Allows controllers to return any valid response type in a type-safe manner (e.g., sealed interface CreateUserResponse implemented by User, ConflictResponse, ErrorResponse)| |false|
|useSpringBoot3|Generate code and provide dependencies for use with Spring Boot ≥ 3 (use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false|
|useSpringBoot4|Generate code and provide dependencies for use with Spring Boot 4.x. Enabling this option will also enable `useJakartaEe`.| |false|
@@ -307,9 +309,9 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|Composite|✓|OAS2,OAS3
|Polymorphism|✓|OAS2,OAS3
|Union|✗|OAS3
-|allOf|✗|OAS2,OAS3
+|allOf|✓|OAS2,OAS3
|anyOf|✗|OAS3
-|oneOf|✗|OAS3
+|oneOf|✓|OAS3
|not|✗|OAS3
### Security Feature
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java
index 6730dd636649..282d7f63142b 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java
@@ -166,6 +166,25 @@ public String getDescription() {
@Setter private boolean autoXSpringPaginated = false;
@Setter private boolean useSealedResponseInterfaces = false;
@Setter private boolean companionObject = false;
+ @Getter @Setter
+ private boolean useSealedDiscriminatorClasses = false;
+ @Getter @Setter
+ private boolean fixJacksonJsonTypeInfoInheritance = true;
+
+ public static final String USE_SEALED_DISCRIMINATOR_CLASSES = "useSealedDiscriminatorClasses";
+ public static final String USE_SEALED_DISCRIMINATOR_CLASSES_DESC =
+ "Generate sealed classes instead of interfaces for discriminator models. " +
+ "When true, discriminator parent models are generated as sealed classes with proper " +
+ "Jackson @JsonTypeInfo/@JsonSubTypes annotations and child classes extend the parent " +
+ "with constructor inheritance. When false (default), the legacy interface-based " +
+ "polymorphism is used.";
+ public static final String FIX_JACKSON_JSON_TYPE_INFO_INHERITANCE = "fixJacksonJsonTypeInfoInheritance";
+ public static final String FIX_JACKSON_JSON_TYPE_INFO_INHERITANCE_DESC =
+ "Only applies when useSealedDiscriminatorClasses is true. " +
+ "When true (default), ensures Jackson polymorphism works correctly by: " +
+ "(1) always setting visible=true on @JsonTypeInfo, and (2) adding the discriminator property " +
+ "to child models with appropriate default values. When false, visible is only set to true if " +
+ "all children already define the discriminator property.";
@Getter @Setter
protected boolean useSpringBoot3 = false;
@@ -201,6 +220,8 @@ public KotlinSpringServerCodegen() {
GlobalFeature.ParameterStyling
)
.includeSchemaSupportFeatures(
+ SchemaSupportFeature.allOf,
+ SchemaSupportFeature.oneOf,
SchemaSupportFeature.Polymorphism
)
.includeParameterFeatures(
@@ -273,6 +294,8 @@ public KotlinSpringServerCodegen() {
addOption(SCHEMA_IMPLEMENTS_FIELDS, "A map of single field or a list of fields per schema name that should be prepended with `override` (serves similar purpose as `x-kotlin-implements-fields`, but is fully decoupled from the api spec). Example: yaml `schemaImplementsFields: {Pet: id, Category: [name, id], Dog: [bark, breed]}` marks fields to be prepended with `override` in schemas `Pet` (field `id`), `Category` (fields `name`, `id`) and `Dog` (fields `bark`, `breed`)", "empty map");
addSwitch(AUTO_X_SPRING_PAGINATED, "Automatically add x-spring-paginated to operations that have 'page', 'size', and 'sort' query parameters. When enabled, operations with all three parameters will have Pageable support automatically applied. Operations with x-spring-paginated explicitly set to false will not be auto-detected.", autoXSpringPaginated);
addSwitch(COMPANION_OBJECT, "Whether to generate companion objects in data classes, enabling companion extensions.", companionObject);
+ addSwitch(USE_SEALED_DISCRIMINATOR_CLASSES, USE_SEALED_DISCRIMINATOR_CLASSES_DESC, useSealedDiscriminatorClasses);
+ addSwitch(FIX_JACKSON_JSON_TYPE_INFO_INHERITANCE, FIX_JACKSON_JSON_TYPE_INFO_INHERITANCE_DESC, fixJacksonJsonTypeInfoInheritance);
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
@@ -534,6 +557,23 @@ public void processOpts() {
additionalProperties.put(COMPANION_OBJECT, companionObject);
}
+ if (additionalProperties.containsKey(USE_SEALED_DISCRIMINATOR_CLASSES)) {
+ this.setUseSealedDiscriminatorClasses(
+ Boolean.parseBoolean(additionalProperties.get(USE_SEALED_DISCRIMINATOR_CLASSES).toString()));
+ }
+ additionalProperties.put(USE_SEALED_DISCRIMINATOR_CLASSES, useSealedDiscriminatorClasses);
+
+ if (useSealedDiscriminatorClasses) {
+ // Enable proper oneOf/anyOf discriminator handling for sealed class polymorphism
+ legacyDiscriminatorBehavior = false;
+ }
+
+ if (additionalProperties.containsKey(FIX_JACKSON_JSON_TYPE_INFO_INHERITANCE)) {
+ this.setFixJacksonJsonTypeInfoInheritance(
+ Boolean.parseBoolean(additionalProperties.get(FIX_JACKSON_JSON_TYPE_INFO_INHERITANCE).toString()));
+ }
+ additionalProperties.put(FIX_JACKSON_JSON_TYPE_INFO_INHERITANCE, fixJacksonJsonTypeInfoInheritance);
+
additionalProperties.put("springHttpStatus", new SpringHttpStatusLambda());
// Set basePackage from invokerPackage
@@ -1123,6 +1163,260 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
// TODO: Handle tags
}
+ @Override
+ public Map postProcessAllModels(Map objs) {
+ objs = super.postProcessAllModels(objs);
+
+ if (!useSealedDiscriminatorClasses) {
+ return objs;
+ }
+
+ // Build a map of model name -> model for easy lookup
+ Map allModelsMap = new HashMap<>();
+ for (ModelsMap modelsMap : objs.values()) {
+ for (ModelMap modelMap : modelsMap.getModels()) {
+ CodegenModel model = modelMap.getModel();
+ allModelsMap.put(model.getClassname(), model);
+ }
+ }
+
+ // First pass: collect all discriminator parent -> children mappings
+ // Also identify the "true" discriminator owners (not inherited via allOf)
+ Map childToParentMap = new HashMap<>();
+ Set trueDiscriminatorOwners = new HashSet<>();
+
+ for (ModelsMap modelsMap : objs.values()) {
+ for (ModelMap modelMap : modelsMap.getModels()) {
+ CodegenModel model = modelMap.getModel();
+ if (model.getDiscriminator() != null && model.getDiscriminator().getMappedModels() != null
+ && !model.getDiscriminator().getMappedModels().isEmpty()) {
+ String discriminatorPropBaseName = model.getDiscriminator().getPropertyBaseName();
+
+ for (CodegenDiscriminator.MappedModel mappedModel : model.getDiscriminator().getMappedModels()) {
+ childToParentMap.put(mappedModel.getModelName(), model.getClassname());
+
+ // If the mapping name equals the model name, check if we can derive
+ // a better mapping name from the child's discriminator property enum value
+ if (mappedModel.getMappingName().equals(mappedModel.getModelName())) {
+ CodegenModel childModel = allModelsMap.get(mappedModel.getModelName());
+ if (childModel != null) {
+ for (CodegenProperty prop : childModel.getAllVars()) {
+ if (prop.getBaseName().equals(discriminatorPropBaseName) && prop.isEnum) {
+ Map allowableValues = prop.getAllowableValues();
+ if (allowableValues != null && allowableValues.containsKey("values")) {
+ @SuppressWarnings("unchecked")
+ List