Repository: cjbooms/fabrikt
Pull request #180 (2023-03-28 13:46:05)

Support jakarta.validation for model classes
Added CLI option `--validation-library` with default value JAVAX_VALIDATION and optional value JAKARTA_VALIDATION to allow generated model classes to support either

cjbooms: Nice work. Given the default behaviour remains unchanged, I'll release this a minor version

Patch:
diff --git a/README.md b/README.md
index 5f4b914..6c9014f 100644
--- a/README.md
+++ b/README.md
@@ -159,6 +159,10 @@ This section documents the available CLI parameters for controlling what gets ge
 |                              | CHOOSE ANY OF: |
 |                              |   `DATETIME_AS_INSTANT` - Use `Instant` as the datetime type. Defaults to `OffsetDateTime` |
 |                              |   `DATETIME_AS_LOCALDATETIME` - Use `LocalDateTime` as the datetime type. Defaults to `OffsetDateTime` |
+|   `--validation-library`     | Specify which validation library to use for annotations in generated model classes. Default: JAVAX_VALIDATION |
+|                              | CHOOSE ONE OF: |
+|                              |   `JAVAX_VALIDATION` - Use `javax.validation` annotations in generated model classes (default) |
+|                              |   `JAKARTA_VALIDATION` - Use `jakarta.validation` annotations in generated model classes |
 
 ### Command Line
 Fabrikt is packaged as an executable jar, allowing it to be integrated into any build tool. The CLI can be invoked as follows:
diff --git a/build.gradle.kts b/build.gradle.kts
index 4000c37..9443ba7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -55,6 +55,7 @@ dependencies {
     testImplementation("org.assertj:assertj-core:3.14.0")
     // Below dependencies are solely present so code examples in the test resources dir compile
     testImplementation("javax.validation:validation-api:2.0.1.Final")
+    testImplementation("jakarta.validation:jakarta.validation-api:3.0.2")
     testImplementation("org.springframework:spring-webmvc:5.1.9.RELEASE")
     testImplementation("io.micronaut:micronaut-core:3.8.4")
     testImplementation("io.micronaut:micronaut-http:3.8.4")
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt
index cdd50a7..36bde80 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt
@@ -31,7 +31,8 @@ object CodeGen {
             codeGenArgs.clientOptions,
             codeGenArgs.typeOverrides,
             codeGenArgs.srcPath,
-            codeGenArgs.resourcesPath
+            codeGenArgs.resourcesPath,
+            codeGenArgs.validationLibrary
         )
     }
 
@@ -48,8 +49,9 @@ object CodeGen {
         typeOverrides: Set<CodeGenTypeOverride>,
         srcPath: Path,
         resourcesPath: Path,
+        validationLibrary: ValidationLibrary
     ) {
-        MutableSettings.updateSettings(codeGenTypes, controllerOptions, controllerTarget, modelOptions, clientOptions, typeOverrides)
+        MutableSettings.updateSettings(codeGenTypes, controllerOptions, controllerTarget, modelOptions, clientOptions, typeOverrides, validationLibrary)
 
         val suppliedApi = pathToApi.toFile().readText()
         val baseDir = pathToApi.parent
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt
index 281adab..8648045 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt
@@ -126,6 +126,13 @@ class CodeGenArgs {
         converter = TypeCodeGenOptionsConverter::class
     )
     var typeOverrides: Set<CodeGenTypeOverride> = emptySet()
+
+    @Parameter(
+        names = ["--validation-library"],
+        description = "Specify which validation library to use for annotations in generated model classes. Default: JAVAX_VALIDATION",
+        converter = ValidationLibraryOptionConverter::class
+    )
+    var validationLibrary: ValidationLibrary = ValidationLibrary.JAVAX_VALIDATION
 }
 
 class CodeGenerationTypesConverter : IStringConverter<CodeGenerationType> {
@@ -150,6 +157,10 @@ class ClientCodeGenOptionConverter : IStringConverter<ClientCodeGenOptionType> {
         convertToEnumValue(value)
 }
 
+class ValidationLibraryOptionConverter : IStringConverter<ValidationLibrary> {
+    override fun convert(value: String): ValidationLibrary = convertToEnumValue(value)
+}
+
 class TypeCodeGenOptionsConverter: IStringConverter<CodeGenTypeOverride> {
     override fun convert(value: String): CodeGenTypeOverride = convertToEnumValue(value)
 }
@@ -176,4 +187,4 @@ inline fun <reified T : Enum<T>> convertToEnumValue(value: String): T {
     } catch (e: IllegalArgumentException) {
         throw ParameterException("$value is not a valid option. Please choose from ${enumValues<T>().joinToString()}")
     }
-}
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt
index 3f93d3d..a769078 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt
@@ -1,5 +1,9 @@
 package com.cjbooms.fabrikt.cli
 
+import com.cjbooms.fabrikt.generators.JakartaAnnotations
+import com.cjbooms.fabrikt.generators.JavaxValidationAnnotations
+import com.cjbooms.fabrikt.generators.ValidationAnnotations
+
 enum class CodeGenerationType(val description: String) {
     HTTP_MODELS(
         "Jackson annotated data classes to represent the schema objects defined in the input."
@@ -51,4 +55,10 @@ enum class CodeGenTypeOverride(val description: String) {
     DATETIME_AS_INSTANT("Use `Instant` as the datetime type. Defaults to `OffsetDateTime`"),
     DATETIME_AS_LOCALDATETIME("Use `LocalDateTime` as the datetime type. Defaults to `OffsetDateTime`");
     override fun toString() = "`${super.toString()}` - $description"
+}
+
+enum class ValidationLibrary(val description: String, val annotations: ValidationAnnotations) {
+    JAVAX_VALIDATION("Use `javax.validation` annotations in generated model classes (default)", JavaxValidationAnnotations),
+    JAKARTA_VALIDATION("Use `jakarta.validation` annotations in generated model classes", JakartaAnnotations);
+    override fun toString() = "`${super.toString()}` - $description"
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt
index c274e12..5186e29 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt
@@ -56,7 +56,7 @@ class CodeGenerator(
     private fun resourceSet(resFiles: Collection<ResourceFile>) = setOf(ResourceSourceSet(resFiles, resourcesPath))
 
     private fun models(): Models =
-        JacksonModelGenerator(packages, sourceApi, MutableSettings.modelOptions()).generate()
+        JacksonModelGenerator(packages, sourceApi, MutableSettings.modelOptions(), MutableSettings.validationLibrary().annotations).generate()
 
     private fun resources(models: Models): List<ResourceFile> =
         listOfNotNull(QuarkusReflectionModelGenerator(models, MutableSettings.generationTypes()).generate())
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/ValidationAnnotations.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/JavaxValidationAnnotations.kt
similarity index 66%
rename from src/main/kotlin/com/cjbooms/fabrikt/generators/ValidationAnnotations.kt
rename to src/main/kotlin/com/cjbooms/fabrikt/generators/JavaxValidationAnnotations.kt
index 80a4f97..0c65a1a 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/ValidationAnnotations.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/JavaxValidationAnnotations.kt
@@ -3,17 +3,17 @@ package com.cjbooms.fabrikt.generators
 import com.squareup.kotlinpoet.AnnotationSpec
 import com.squareup.kotlinpoet.ClassName
 
-object ValidationAnnotations {
-    private val minClass = ClassName("javax.validation.constraints", "Min")
-    private val maxClass = ClassName("javax.validation.constraints", "Max")
-    private val validClass = ClassName("javax.validation", "Valid")
-    private val notNullClass = ClassName("javax.validation.constraints", "NotNull")
-    private val patternClass = ClassName("javax.validation.constraints", "Pattern")
-    private val sizeClass = ClassName("javax.validation.constraints", "Size")
-    private val decimalMinClass = ClassName("javax.validation.constraints", "DecimalMin")
-    private val decimalMaxClass = ClassName("javax.validation.constraints", "DecimalMax")
+abstract class ValidationAnnotations(packageName: String) {
+    private val minClass = ClassName("$packageName.constraints", "Min")
+    private val maxClass = ClassName("$packageName.constraints", "Max")
+    private val validClass = ClassName(packageName, "Valid")
+    private val notNullClass = ClassName("$packageName.constraints", "NotNull")
+    private val patternClass = ClassName("$packageName.constraints", "Pattern")
+    private val sizeClass = ClassName("$packageName.constraints", "Size")
+    private val decimalMinClass = ClassName("$packageName.constraints", "DecimalMin")
+    private val decimalMaxClass = ClassName("$packageName.constraints", "DecimalMax")
 
-    val NON_NULL_ANNOTATION = AnnotationSpec
+    val nonNullAnnotation = AnnotationSpec
         .builder(notNullClass)
         .useSiteTarget(AnnotationSpec.UseSiteTarget.GET)
         .build()
@@ -62,3 +62,7 @@ object ValidationAnnotations {
         .useSiteTarget(AnnotationSpec.UseSiteTarget.GET)
         .build()
 }
+
+object JavaxValidationAnnotations: ValidationAnnotations("javax.validation")
+
+object JakartaAnnotations: ValidationAnnotations("jakarta.validation")
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt
index bddc213..1d253a3 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt
@@ -1,11 +1,6 @@
 package com.cjbooms.fabrikt.generators
 
-import com.cjbooms.fabrikt.cli.CodeGenerationType
-import com.cjbooms.fabrikt.cli.ControllerCodeGenOptionType
-import com.cjbooms.fabrikt.cli.ControllerCodeGenTargetType
-import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType
-import com.cjbooms.fabrikt.cli.ClientCodeGenOptionType
-import com.cjbooms.fabrikt.cli.CodeGenTypeOverride
+import com.cjbooms.fabrikt.cli.*
 
 object MutableSettings {
     private lateinit var generationTypes: MutableSet<CodeGenerationType>
@@ -14,6 +9,7 @@ object MutableSettings {
     private lateinit var modelOptions: MutableSet<ModelCodeGenOptionType>
     private lateinit var clientOptions: MutableSet<ClientCodeGenOptionType>
     private lateinit var typeOverrides: MutableSet<CodeGenTypeOverride>
+    private lateinit var validationLibrary: ValidationLibrary
 
     fun updateSettings(
         genTypes: Set<CodeGenerationType> = emptySet(),
@@ -21,7 +17,8 @@ object MutableSettings {
         controllerTarget: ControllerCodeGenTargetType = ControllerCodeGenTargetType.SPRING,
         modelOptions: Set<ModelCodeGenOptionType> = emptySet(),
         clientOptions: Set<ClientCodeGenOptionType> = emptySet(),
-        typeOverrides: Set<CodeGenTypeOverride> = emptySet()
+        typeOverrides: Set<CodeGenTypeOverride> = emptySet(),
+        validationLibrary: ValidationLibrary = ValidationLibrary.JAVAX_VALIDATION
     ) {
         this.generationTypes = genTypes.toMutableSet()
         this.controllerOptions = controllerOptions.toMutableSet()
@@ -29,6 +26,7 @@ object MutableSettings {
         this.modelOptions = modelOptions.toMutableSet()
         this.clientOptions = clientOptions.toMutableSet()
         this.typeOverrides = typeOverrides.toMutableSet()
+        this.validationLibrary = validationLibrary
     }
 
     fun addOption(option: ModelCodeGenOptionType) = modelOptions.add(option)
@@ -40,4 +38,5 @@ object MutableSettings {
     fun modelOptions() = this.modelOptions.toSet()
     fun clientOptions() = this.clientOptions.toSet()
     fun typeOverrides() = this.typeOverrides.toSet()
+    fun validationLibrary() = this.validationLibrary
 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
index c8846ec..6ec6ec3 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
@@ -1,6 +1,6 @@
 package com.cjbooms.fabrikt.generators
 
-import com.cjbooms.fabrikt.generators.ValidationAnnotations.fieldValid
+import com.cjbooms.fabrikt.generators.JavaxValidationAnnotations.fieldValid
 import com.cjbooms.fabrikt.generators.model.JacksonMetadata
 import com.cjbooms.fabrikt.model.KotlinTypeInfo
 import com.cjbooms.fabrikt.model.PropertyInfo
@@ -27,7 +27,8 @@ object PropertyUtils {
         parameterizedType: TypeName,
         classBuilder: TypeSpec.Builder,
         constructorBuilder: FunSpec.Builder,
-        classType: ClassType = ClassType.VANILLA_MODEL
+        classType: ClassType = ClassType.VANILLA_MODEL,
+        validationAnnotations: ValidationAnnotations = JavaxValidationAnnotations
     ) {
         val property = PropertySpec.builder(name, type)
 
@@ -86,13 +87,13 @@ object PropertyUtils {
                         property.addAnnotation(JacksonMetadata.jacksonParameterAnnotation(oasKey))
                     }
                     property.addAnnotation(JacksonMetadata.jacksonPropertyAnnotation(oasKey))
-                    property.addValidationAnnotations(this)
+                    property.addValidationAnnotations(this, validationAnnotations)
                 }
 
                 ClassType.VANILLA_MODEL -> {
                     property.addAnnotation(JacksonMetadata.jacksonParameterAnnotation(oasKey))
                     property.addAnnotation(JacksonMetadata.jacksonPropertyAnnotation(oasKey))
-                    property.addValidationAnnotations(this)
+                    property.addValidationAnnotations(this, validationAnnotations)
                 }
             }
 
@@ -148,28 +149,28 @@ object PropertyUtils {
      *   minProperties          - Not Supported. No equivalent javax validation. We could add our own as a
      *   enum                   - Not currently supported. Possible to do as a regex maybe.
      */
-    private fun PropertySpec.Builder.addValidationAnnotations(info: PropertyInfo) {
-        if (!info.isNullable()) addAnnotation(ValidationAnnotations.NON_NULL_ANNOTATION)
+    private fun PropertySpec.Builder.addValidationAnnotations(info: PropertyInfo, validationAnnotations: ValidationAnnotations) {
+        if (!info.isNullable()) addAnnotation(validationAnnotations.nonNullAnnotation)
         when (info) {
             is PropertyInfo.Field -> {
                 // Regex validation pattern to validate string input
-                info.pattern?.let { addAnnotation(ValidationAnnotations.regexPattern(it)) }
+                info.pattern?.let { addAnnotation(validationAnnotations.regexPattern(it)) }
 
                 // Size Restrictions for Strings
                 val (min, max) = Pair(info.minLength, info.maxLength)
                 if (min != null || max != null) addAnnotation(
-                    ValidationAnnotations.lengthRestriction(min, max)
+                    validationAnnotations.lengthRestriction(min, max)
                 )
 
                 // Numeric value validation
                 info.minimum?.let {
                     addAnnotation(
-                        ValidationAnnotations.minRestriction(it, info.exclusiveMinimum ?: false)
+                        validationAnnotations.minRestriction(it, info.exclusiveMinimum ?: false)
                     )
                 }
                 info.maximum?.let {
                     addAnnotation(
-                        ValidationAnnotations.maxRestriction(it, info.exclusiveMaximum ?: false)
+                        validationAnnotations.maxRestriction(it, info.exclusiveMaximum ?: false)
                     )
                 }
             }
@@ -178,7 +179,7 @@ object PropertyUtils {
                 // Size Restrictions for collections
                 val (minCollLen, maxCollLen) = Pair(info.minItems, info.maxItems)
                 if (minCollLen != null || maxCollLen != null) addAnnotation(
-                    ValidationAnnotations.lengthRestriction(
+                    validationAnnotations.lengthRestriction(
                         minCollLen,
                         maxCollLen
                     )
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
index 5fe4fca..b788d55 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
@@ -11,6 +11,8 @@ import com.cjbooms.fabrikt.generators.TypeFactory.createMapOfMapsStringToStringA
 import com.cjbooms.fabrikt.generators.TypeFactory.createMapOfStringToType
 import com.cjbooms.fabrikt.generators.TypeFactory.createMutableMapOfMapsStringToStringType
 import com.cjbooms.fabrikt.generators.TypeFactory.createMutableMapOfStringToType
+import com.cjbooms.fabrikt.generators.JavaxValidationAnnotations
+import com.cjbooms.fabrikt.generators.ValidationAnnotations
 import com.cjbooms.fabrikt.generators.model.JacksonMetadata.JSON_VALUE
 import com.cjbooms.fabrikt.generators.model.JacksonMetadata.basePolymorphicType
 import com.cjbooms.fabrikt.generators.model.JacksonMetadata.polymorphicSubTypes
@@ -60,7 +62,8 @@ import java.net.URL
 class JacksonModelGenerator(
     private val packages: Packages,
     private val sourceApi: SourceApi,
-    private val options: Set<ModelCodeGenOptionType> = emptySet()
+    private val options: Set<ModelCodeGenOptionType> = emptySet(),
+    private val validationAnnotations: ValidationAnnotations = JavaxValidationAnnotations
 ) {
     companion object {
         fun toModelType(basePackage: String, typeInfo: KotlinTypeInfo, isNullable: Boolean = false): TypeName {
@@ -402,7 +405,8 @@ class JacksonModelGenerator(
                 ),
                 classBuilder,
                 constructorBuilder,
-                classType
+                classType,
+                validationAnnotations
             )
         }
         if (constructorBuilder.parameters.isNotEmpty() && classBuilder.modifiers.isEmpty()) classBuilder.addModifiers(KModifier.DATA)


Test patch:
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
index 1f59da3..4990a90 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
@@ -1,9 +1,10 @@
 package com.cjbooms.fabrikt.generators
 
 import com.beust.jcommander.ParameterException
+import com.cjbooms.fabrikt.cli.CodeGenTypeOverride
 import com.cjbooms.fabrikt.cli.CodeGenerationType
 import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType
-import com.cjbooms.fabrikt.cli.CodeGenTypeOverride
+import com.cjbooms.fabrikt.cli.ValidationLibrary
 import com.cjbooms.fabrikt.configurations.Packages
 import com.cjbooms.fabrikt.generators.model.JacksonModelGenerator
 import com.cjbooms.fabrikt.model.Models
@@ -81,6 +82,16 @@ class ModelGeneratorTest {
         assertThat(models).isEqualTo(expectedModels)
     }
 
+    @Test
+    fun `generate models using jakarta validation`() {
+        val basePackage = "examples.jakarta"
+        val spec = readTextResource("/examples/validationAnnotations/api.yaml")
+        val expectedJakartaModel = readTextResource("/examples/validationAnnotations/jakarta/models/Models.kt")
+        MutableSettings.updateSettings(genTypes = setOf(CodeGenerationType.HTTP_MODELS))
+        val models = JacksonModelGenerator(Packages(basePackage), SourceApi(spec), validationAnnotations = ValidationLibrary.JAKARTA_VALIDATION.annotations).generate()
+        assertThat(Linter.lintString(models.files.first().toString())).isEqualTo(expectedJakartaModel)
+    }
+
     @Test
     fun `sealed classes are correctly grouped into a single file`() {
         val basePackage = "examples.polymorphicModels.sealed"
diff --git a/src/test/resources/examples/jakartaValidationAnnotations/api.yaml b/src/test/resources/examples/jakartaValidationAnnotations/api.yaml
new file mode 100644
index 0000000..f25fab2
--- /dev/null
+++ b/src/test/resources/examples/jakartaValidationAnnotations/api.yaml
@@ -0,0 +1,31 @@
+openapi: 3.0.0
+components:
+  schemas:
+    ValidationAnnotations:
+      type: object
+      required:
+        - user_name
+        - age
+        - bio
+        - friends
+      properties:
+        user_name:
+          type: string
+          pattern: '[a-zA-Z]'
+        age:
+          type: integer
+          format: int32
+          minimum: 0
+          maximum: 100
+          exclusiveMinimum: true
+          exclusiveMaximum: false
+        bio:
+          type: string
+          minLength: 20
+          maxLength: 200
+        friends:
+          type: array
+          items:
+            type: string
+          minItems: 0
+          maxItems: 10
\ No newline at end of file
diff --git a/src/test/resources/examples/jakartaValidationAnnotations/models/Models.kt b/src/test/resources/examples/jakartaValidationAnnotations/models/Models.kt
new file mode 100644
index 0000000..3fb5cf4
--- /dev/null
+++ b/src/test/resources/examples/jakartaValidationAnnotations/models/Models.kt
@@ -0,0 +1,47 @@
+package examples.jakartavalidationAnnotations.models
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import jakarta.validation.constraints.DecimalMax
+import jakarta.validation.constraints.DecimalMin
+import jakarta.validation.constraints.NotNull
+import jakarta.validation.constraints.Pattern
+import jakarta.validation.constraints.Size
+import kotlin.Int
+import kotlin.String
+import kotlin.collections.List
+
+data class ValidationAnnotations(
+    @param:JsonProperty("user_name")
+    @get:JsonProperty("user_name")
+    @get:NotNull
+    @get:Pattern(regexp = "[a-zA-Z]")
+    val userName: String,
+    @param:JsonProperty("age")
+    @get:JsonProperty("age")
+    @get:NotNull
+    @get:DecimalMin(
+        value = "0",
+        inclusive = false
+    )
+    @get:DecimalMax(
+        value = "100",
+        inclusive = true
+    )
+    val age: Int,
+    @param:JsonProperty("bio")
+    @get:JsonProperty("bio")
+    @get:NotNull
+    @get:Size(
+        min = 20,
+        max = 200
+    )
+    val bio: String,
+    @param:JsonProperty("friends")
+    @get:JsonProperty("friends")
+    @get:NotNull
+    @get:Size(
+        min = 0,
+        max = 10
+    )
+    val friends: List<String>
+)
diff --git a/src/test/resources/examples/validationAnnotations/jakarta/models/Models.kt b/src/test/resources/examples/validationAnnotations/jakarta/models/Models.kt
new file mode 100644
index 0000000..cb9162f
--- /dev/null
+++ b/src/test/resources/examples/validationAnnotations/jakarta/models/Models.kt
@@ -0,0 +1,47 @@
+package examples.jakarta.models
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import jakarta.validation.constraints.DecimalMax
+import jakarta.validation.constraints.DecimalMin
+import jakarta.validation.constraints.NotNull
+import jakarta.validation.constraints.Pattern
+import jakarta.validation.constraints.Size
+import kotlin.Int
+import kotlin.String
+import kotlin.collections.List
+
+data class ValidationAnnotations(
+    @param:JsonProperty("user_name")
+    @get:JsonProperty("user_name")
+    @get:NotNull
+    @get:Pattern(regexp = "[a-zA-Z]")
+    val userName: String,
+    @param:JsonProperty("age")
+    @get:JsonProperty("age")
+    @get:NotNull
+    @get:DecimalMin(
+        value = "0",
+        inclusive = false
+    )
+    @get:DecimalMax(
+        value = "100",
+        inclusive = true
+    )
+    val age: Int,
+    @param:JsonProperty("bio")
+    @get:JsonProperty("bio")
+    @get:NotNull
+    @get:Size(
+        min = 20,
+        max = 200
+    )
+    val bio: String,
+    @param:JsonProperty("friends")
+    @get:JsonProperty("friends")
+    @get:NotNull
+    @get:Size(
+        min = 0,
+        max = 10
+    )
+    val friends: List<String>
+)