Skip to content

Commit 1af1d1c

Browse files
rvlasveldclaude
andcommitted
[kotlin-spring] Add sealed class polymorphism support via useSealedDiscriminatorClasses option
Add opt-in `useSealedDiscriminatorClasses` configuration option (default `false`) that enables proper polymorphism support for the kotlin-spring generator using sealed classes instead of interfaces. When enabled, discriminator parent models are generated as Kotlin sealed classes with proper Jackson `@JsonTypeInfo`/`@JsonSubTypes` annotations. Child classes extend the parent via constructor inheritance with `override` properties. This approach mirrors the polymorphism support added to the kotlin-server generator in OpenAPITools#22610. The motivation for this change is that the current interface-based polymorphism has several long-standing issues: parent interfaces incorrectly contain all children's properties, child classes don't properly implement the parent interface, the `override` keyword is missing on inherited properties, and multi-level inheritance produces invalid data class hierarchies that don't compile. These issues have been tracked across multiple GitHub issues over several years. The implementation adds a `postProcessAllModels()` method (ported from `KotlinServerCodegen`) that builds parent-child relationships for discriminator models in three passes: first collecting discriminator mappings, then processing child models to mark inherited properties and generate parent constructor arguments, and finally configuring vendor extensions that control the template rendering. Both oneOf (type union) and allOf (inheritance) discriminator patterns are supported. For oneOf, the parent sealed class contains only the discriminator property; children get the discriminator as an overridden property with a default value. For allOf, the parent sealed class contains all its declared properties; children override inherited properties and pass them via the parent constructor call. A companion `fixJacksonJsonTypeInfoInheritance` option (default `true`, only applies when sealed classes are enabled) controls whether Jackson's `visible` flag is set to `true` on `@JsonTypeInfo` and whether discriminator properties are automatically added to child models with appropriate default values. The default behavior (`useSealedDiscriminatorClasses=false`) is completely unchanged — existing users continue to get interface-based generation with zero sample file changes. This makes the change non-breaking and suitable for a minor release. New template `dataClassSealedVar.mustache` renders sealed class constructor properties with `open`/`override` modifiers and `@get:Schema`/`@get:JsonProperty` annotations. The existing `dataClass.mustache` and `typeInfoAnnotation.mustache` templates are updated to conditionally render either interface or sealed class based on the flag. Six new test methods cover oneOf with discriminator, allOf with discriminator, polymorphism without discriminator, and the `fixJacksonJsonTypeInfoInheritance` toggle. All 149 existing tests continue to pass. Fixes OpenAPITools#18167, OpenAPITools#18206, OpenAPITools#11347, OpenAPITools#8060 Related: OpenAPITools#8366, OpenAPITools#8059 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 03c13fb commit 1af1d1c

6 files changed

Lines changed: 501 additions & 7 deletions

File tree

docs/generators/kotlin-spring.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
3434
|documentationProvider|Select the OpenAPI documentation provider.|<dl><dt>**none**</dt><dd>Do not publish an OpenAPI specification.</dd><dt>**source**</dt><dd>Publish the original input OpenAPI specification.</dd><dt>**springdoc**</dt><dd>Generate an OpenAPI 3 specification using SpringDoc.</dd></dl>|springdoc|
3535
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
3636
|exceptionHandler|generate default global exception handlers (not compatible with reactive. enabling reactive will disable exceptionHandler )| |true|
37+
|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|
3738
|gradleBuildFile|generate a gradle build file using the Kotlin DSL| |true|
3839
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
3940
|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
6162
|useFlowForArrayReturnType|Whether to use Flow for array/collection return types when reactive is enabled. If false, will use List instead.| |true|
6263
|useJackson3|Use Jackson 3 dependencies (tools.jackson package). Only available with `useSpringBoot4`. Defaults to true when `useSpringBoot4` is enabled. Incompatible with `openApiNullable`.| |false|
6364
|useResponseEntity|Whether (when false) to return actual type (e.g. List&lt;Fruit&gt;) and handle non-happy path responses via exceptions flow or (when true) return entire ResponseEntity (e.g. ResponseEntity&lt;List&lt;Fruit&gt;&gt;). If disabled, method are annotated using a @ResponseStatus annotation, which has the status of the first response declared in the Api definition| |true|
65+
|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|
6466
|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|
6567
|useSpringBoot3|Generate code and provide dependencies for use with Spring Boot &ge; 3 (use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false|
6668
|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
307309
|Composite|✓|OAS2,OAS3
308310
|Polymorphism|✓|OAS2,OAS3
309311
|Union|✗|OAS3
310-
|allOf||OAS2,OAS3
312+
|allOf||OAS2,OAS3
311313
|anyOf|✗|OAS3
312-
|oneOf||OAS3
314+
|oneOf||OAS3
313315
|not|✗|OAS3
314316

315317
### Security Feature

0 commit comments

Comments
 (0)