diff --git a/bin/configs/kotlin-enum-default-value.yaml b/bin/configs/kotlin-enum-default-value.yaml
index b27ccfd73afb..502bdcf07eb5 100644
--- a/bin/configs/kotlin-enum-default-value.yaml
+++ b/bin/configs/kotlin-enum-default-value.yaml
@@ -7,5 +7,6 @@ additionalProperties:
serializableModel: "true"
dateLibrary: java8
enumUnknownDefaultCase: true
+ enumPropertyNaming: bestEffortBacktick
enumNameMappings:
CHRISTMAS_DAY: XMAS_DAY
diff --git a/docs/generators/kotlin-misk.md b/docs/generators/kotlin-misk.md
index b0aa29064a10..3832b9120655 100644
--- a/docs/generators/kotlin-misk.md
+++ b/docs/generators/kotlin-misk.md
@@ -29,7 +29,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|apiSuffix|suffix for api classes| |Api|
|artifactId|Generated artifact id (name of jar).| |null|
|artifactVersion|Generated artifact's package version.| |1.0.0|
-|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
+|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|generateStubImplClasses|Generate Stub Impl Classes| |false|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|implicitHeaders|Skip header parameters in the generated API methods.| |false|
diff --git a/docs/generators/kotlin-server.md b/docs/generators/kotlin-server.md
index 31f042e5128f..da36df26ba13 100644
--- a/docs/generators/kotlin-server.md
+++ b/docs/generators/kotlin-server.md
@@ -22,7 +22,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|apiSuffix|suffix for api classes| |Api|
|artifactId|Generated artifact id (name of jar).| |kotlin-server|
|artifactVersion|Generated artifact's package version.| |1.0.0|
-|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
+|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|featureAutoHead|Automatically provide responses to HEAD requests for existing routes that have the GET verb defined.| |true|
|featureCORS|Ktor by default provides an interceptor for implementing proper support for Cross-Origin Resource Sharing (CORS). See enable-cors.org.| |false|
|featureCompression|Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response.| |true|
diff --git a/docs/generators/kotlin-spring.md b/docs/generators/kotlin-spring.md
index 529456315dda..bf4393ae41f2 100644
--- a/docs/generators/kotlin-spring.md
+++ b/docs/generators/kotlin-spring.md
@@ -32,7 +32,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|declarativeInterfaceReactiveMode|What type of reactive style to use in Spring Http declarative interface|
- **coroutines**
- Use kotlin-idiomatic 'suspend' functions
- **reactor**
- Use reactor return wrappers 'Mono' and 'Flux'
|coroutines|
|delegatePattern|Whether to generate the server files using the delegate pattern| |false|
|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|
+|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|exceptionHandler|generate default global exception handlers (not compatible with reactive. enabling reactive will disable exceptionHandler )| |true|
|generatePageableConstraintValidation|Generate a @ValidPageable annotation and PageableConstraintValidator class, and apply @ValidPageable to the injected Pageable parameter of operations whose 'page' or 'size' parameter specifies a maximum constraint. The annotation enforces those constraints on the Pageable object that replaces the individual page/size query parameters. Requires useBeanValidation=true and library=spring-boot.| |false|
|generateSortValidation|Generate a @ValidSort annotation and SortValidator class, and apply @ValidSort to the injected Pageable parameter of operations whose 'sort' parameter has enum values. The annotation validates that sort values in the Pageable object match the allowed enum values from the spec. Requires useBeanValidation=true and library=spring-boot.| |false|
diff --git a/docs/generators/kotlin-vertx.md b/docs/generators/kotlin-vertx.md
index 279e7330f4e4..6cdc16bed8c1 100644
--- a/docs/generators/kotlin-vertx.md
+++ b/docs/generators/kotlin-vertx.md
@@ -22,7 +22,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|apiSuffix|suffix for api classes| |Api|
|artifactId|Generated artifact id (name of jar).| |null|
|artifactVersion|Generated artifact's package version.| |1.0.0|
-|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
+|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|implicitHeaders|Skip header parameters in the generated API methods.| |false|
|modelMutable|Create mutable models| |false|
diff --git a/docs/generators/kotlin-wiremock.md b/docs/generators/kotlin-wiremock.md
index 8bd595b2e4e5..b02b15fb3596 100644
--- a/docs/generators/kotlin-wiremock.md
+++ b/docs/generators/kotlin-wiremock.md
@@ -22,7 +22,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|apiSuffix|suffix for api classes| |Api|
|artifactId|Generated artifact id (name of jar).| |null|
|artifactVersion|Generated artifact's package version.| |1.0.0|
-|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
+|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|implicitHeaders|Skip header parameters in the generated API methods.| |false|
|modelMutable|Create mutable models| |false|
diff --git a/docs/generators/kotlin.md b/docs/generators/kotlin.md
index 7e79ba98bfc8..981ed1e016bb 100644
--- a/docs/generators/kotlin.md
+++ b/docs/generators/kotlin.md
@@ -25,7 +25,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|collectionType|Option. Collection type to use|- **array**
- kotlin.Array
- **list**
- kotlin.collections.List
|list|
|companionObject|Whether to generate companion objects in data classes, enabling companion extensions.| |false|
|dateLibrary|Option. Date library to use|- **threetenbp-localdatetime**
- Threetenbp - Backport of JSR310 (jvm only, for legacy app only)
- **kotlinx-datetime**
- kotlinx-datetime (preferred for multiplatform)
- **string**
- String
- **java8-localdatetime**
- Java 8 native JSR310 (jvm only, for legacy app only)
- **java8**
- Java 8 native JSR310 (jvm only, preferred for jdk 1.8+)
- **threetenbp**
- Threetenbp - Backport of JSR310 (jvm only, preferred for jdk < 1.8)
|java8|
-|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
+|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|explicitApi|Generates code with explicit access modifiers to comply with Kotlin Explicit API Mode.| |false|
|failOnUnknownProperties|Fail Jackson de-serialization on unknown properties| |false|
|generateOneOfAnyOfWrappers|Generate oneOf, anyOf schemas as wrappers. Only `jvm-retrofit2`(library) with `gson` or `kotlinx_serialization`(serializationLibrary) support this option.| |false|
diff --git a/docs/generators/ktorm-schema.md b/docs/generators/ktorm-schema.md
index 179d7c7647c0..4b905a59a220 100644
--- a/docs/generators/ktorm-schema.md
+++ b/docs/generators/ktorm-schema.md
@@ -23,7 +23,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|artifactId|Generated artifact id (name of jar).| |ktorm|
|artifactVersion|Generated artifact's package version.| |1.0.0|
|defaultDatabaseName|Default database name for all queries| |sqlite.db|
-|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
+|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|identifierNamingConvention|Naming convention of Ktorm identifiers(table names and column names). This is not related to database name which is defined by defaultDatabaseName option|- **original**
- Do not transform original names
- **snake_case**
- Use snake_case names
|original|
|implicitHeaders|Skip header parameters in the generated API methods.| |false|
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java
index cd130c766530..e49181d74df7 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java
@@ -91,7 +91,34 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegen implements Co
@Setter protected boolean nonPublicApi = false;
- @Getter protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.original;
+ /**
+ * Naming convention options for Kotlin enum properties. Extends the shared
+ * {@link org.openapitools.codegen.CodegenConstants.ENUM_PROPERTY_NAMING_TYPE} with
+ * Kotlin-specific options so that Kotlin generators are not forced to add language-specific
+ * values to the shared enum (which cannot be extended in Java).
+ */
+ public enum KotlinEnumNamingType {
+ camelCase, PascalCase, snake_case, original, UPPERCASE,
+ /**
+ * Like {@code original}, but uses Kotlin's backtick-escaped identifier syntax to preserve
+ * more values without falling back to sanitization. Where {@code original} would silently
+ * replace characters (e.g. {@code in-progress} → {@code inMinusProgress}), this option
+ * wraps the value in backticks instead (e.g. {@code `in-progress`}).
+ *
+ * Particularly useful for sort/order enums whose values contain commas or other punctuation,
+ * e.g. {@code name,asc}, {@code name,desc}, {@code id,asc}, {@code id,desc} — these are
+ * preserved as `name,asc` etc. rather than being mangled into {@code nameCommaAsc}.
+ *
+ *
+ * - Already a valid plain Kotlin identifier and not reserved → used as-is
+ * - Contains no backtick / newline / CR / NUL → wrapped in backticks
+ * - Otherwise → falls back to the standard sanitization (same as {@code original})
+ *
+ */
+ bestEffortBacktick
+ }
+
+ @Getter protected KotlinEnumNamingType enumPropertyNaming = KotlinEnumNamingType.original;
// model classes cannot use the same property names defined in HashMap
// ref: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-hash-map/
@@ -287,7 +314,11 @@ public AbstractKotlinCodegen() {
addOption(CodegenConstants.ARTIFACT_ID, "Generated artifact id (name of jar).", artifactId);
addOption(CodegenConstants.ARTIFACT_VERSION, "Generated artifact's package version.", artifactVersion);
- CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_DESC);
+ CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING,
+ "Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case'," +
+ " 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original'" +
+ " but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays" +
+ " `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)");
cliOptions.add(enumPropertyNamingOpt.defaultValue(enumPropertyNaming.name()));
cliOptions.add(new CliOption(CodegenConstants.PARCELIZE_MODELS, CodegenConstants.PARCELIZE_MODELS_DESC));
@@ -341,14 +372,14 @@ public String escapeUnsafeCharacters(String input) {
/**
* Sets the naming convention for Kotlin enum properties
*
- * @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link org.openapitools.codegen.CodegenConstants.ENUM_PROPERTY_NAMING_TYPE}
+ * @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link KotlinEnumNamingType}
*/
public void setEnumPropertyNaming(final String enumPropertyNamingType) {
try {
- this.enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.valueOf(enumPropertyNamingType);
+ this.enumPropertyNaming = KotlinEnumNamingType.valueOf(enumPropertyNamingType);
} catch (IllegalArgumentException ex) {
StringBuilder sb = new StringBuilder(enumPropertyNamingType + " is an invalid enum property naming option. Please choose from:");
- for (CodegenConstants.ENUM_PROPERTY_NAMING_TYPE t : CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.values()) {
+ for (KotlinEnumNamingType t : KotlinEnumNamingType.values()) {
sb.append("\n ").append(t.name());
}
throw new RuntimeException(sb.toString());
@@ -682,6 +713,17 @@ public String toEnumVarName(String value, String datatype) {
case UPPERCASE:
modified = underscore(modified).toUpperCase(Locale.ROOT);
break;
+ case bestEffortBacktick:
+ // Use the original value as a plain identifier if already valid and not reserved.
+ if (!reservedWords.contains(value) && value.matches("[a-zA-Z_$][a-zA-Z0-9_$]*")) {
+ return value;
+ }
+ // Wrap in backticks when the value contains no character that is illegal inside them.
+ if (!value.contains("`") && !value.contains("\n") && !value.contains("\r") && !value.contains("\0")) {
+ return "`" + value + "`";
+ }
+ // Fall back: use the already-sanitized modified (pre-switch value).
+ break;
}
if (reservedWords.contains(modified)) {
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/AbstractKotlinCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/AbstractKotlinCodegenTest.java
index fe04c75e18d7..a5e8b44c9483 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/AbstractKotlinCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/AbstractKotlinCodegenTest.java
@@ -18,6 +18,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -25,6 +26,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;
import static org.openapitools.codegen.CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.*;
+import static org.openapitools.codegen.languages.AbstractKotlinCodegen.KotlinEnumNamingType.bestEffortBacktick;
import static org.openapitools.codegen.TestUtils.createCodegenModelWrapper;
import static org.testng.Assert.*;
@@ -80,6 +82,81 @@ public void originalEnumConverter() {
assertEquals(codegen.toEnumVarName("data/*", null), "dataSlashStar");
}
+ @Test
+ public void bestEffortBacktickEnumConverter() {
+ codegen.setEnumPropertyNaming(bestEffortBacktick.name());
+
+ // Already a valid plain Kotlin identifier — no backticks needed
+ assertEquals(codegen.toEnumVarName("validName", null), "validName");
+ assertEquals(codegen.toEnumVarName("snake_case", null), "snake_case");
+
+ // Contains characters invalid in a plain identifier — wrap in backticks
+ assertEquals(codegen.toEnumVarName("long Name", null), "`long Name`");
+ assertEquals(codegen.toEnumVarName("long-Name", null), "`long-Name`");
+ assertEquals(codegen.toEnumVarName("not1long Name", null), "`not1long Name`");
+ assertEquals(codegen.toEnumVarName("data/*", null), "`data/*`");
+
+ // Starts with a digit — not a valid plain identifier, wrap in backticks
+ assertEquals(codegen.toEnumVarName("1long Name", null), "`1long Name`");
+
+ // Kotlin reserved word — wrap in backticks to make it valid
+ assertEquals(codegen.toEnumVarName("fun", null), "`fun`");
+ assertEquals(codegen.toEnumVarName("class", null), "`class`");
+
+ // Emoji — Unicode Symbol category, not a letter, so invalid as plain identifier; valid inside backticks
+ assertEquals(codegen.toEnumVarName("🎉", null), "`🎉`");
+
+ // Dollar sign is valid in plain Kotlin identifiers — no backticks needed
+ assertEquals(codegen.toEnumVarName("$price", null), "$price");
+
+ // Contains a literal backtick — cannot use backtick escaping, fall back to sanitization
+ assertEquals(codegen.toEnumVarName("foo`bar", null), "fooBacktickBar");
+ }
+
+ @Test(description = "bestEffortBacktick preserves original values as backtick identifiers in ComplexEnum")
+ public void testComplexEnumFromSpecWithBestEffortBacktick() {
+ codegen.setEnumPropertyNaming(bestEffortBacktick.name());
+ final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/kotlin/issue10591-enum-defaultValue.yaml");
+ codegen.setOpenAPI(openAPI);
+
+ Schema complexEnumSchema = openAPI.getComponents().getSchemas().get("ComplexEnum");
+ CodegenModel cm = codegen.fromModel("ComplexEnum", complexEnumSchema);
+ codegen.postProcessModels(createCodegenModelWrapper(cm));
+
+ @SuppressWarnings("unchecked")
+ List