😸 kt-schema

Validation Constraints

Table of contents

kt-schema emits JSON Schema validation keywords (minimum, maxLength, pattern, format, …) from two sources: Jakarta Bean Validation annotations on your model, and the fixed range or wire format of built-in types. No extra configuration is needed, and jakarta.validation-api is not a dependency of kt-schema.

Jakarta Bean Validation annotations

Annotations from jakarta.validation.constraints and javax.validation.constraints are read from properties (reflection, KSP and APT) and from function parameters (reflection and KSP).

kotlin
@Schema
data class ValidatedShipment(
    @Min(1) @Max(100) val quantity: Int,
    @DecimalMin("0.01") @DecimalMax(value = "1000", inclusive = false) val price: Double,
    @Size(min = 2, max = 50) val name: String,
    @NotEmpty val tags: List<String>,
    @NotBlank val note: String?,
    @Pattern(regexp = "[A-Z]{3}") val currency: String,
    @Email val contact: String,
    @Size(max = 5) val attributes: Map<String, String>,
    @Min(0) val level: Byte,
)

Generated properties (other keywords omitted):

json
{
    "quantity": { "type": "integer", "minimum": 1, "maximum": 100 },
    "price": { "type": "number", "minimum": 0.01, "exclusiveMaximum": 1000 },
    "name": { "type": "string", "minLength": 2, "maxLength": 50 },
    "tags": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
    "note": { "type": ["string", "null"], "minLength": 1 },
    "currency": { "type": "string", "pattern": "[A-Z]{3}" },
    "contact": { "type": "string", "format": "email" },
    "attributes": { "type": "object", "additionalProperties": { "type": "string" }, "maxProperties": 5 },
    "level": { "type": "integer", "minimum": 0, "maximum": 127 }
}

Mapping

AnnotationEmitted keywords
@Min, @Maxminimum, maximum
@DecimalMin, @DecimalMaxminimum/maximum, or exclusiveMinimum/exclusiveMaximum with inclusive = false
@Positive, @PositiveOrZeroexclusiveMinimum: 0, minimum: 0
@Negative, @NegativeOrZeroexclusiveMaximum: 0, maximum: 0
@Size(min, max)minLength/maxLength, minItems/maxItems or minProperties/maxProperties
@NotEmptyminLength: 1 / minItems: 1 / minProperties: 1
@NotBlankminLength: 1 (blank strings are not excluded)
@Pattern(regexp)pattern, copied as written
@Emailformat: "email"

Not read: @NotNull, @Digits, @Past/@Future, @AssertTrue/@AssertFalse and constraints on type arguments (List<@Size(max = 3) String>). The serialization-based generator cannot see these annotations at all, since SerialDescriptor exposes only @SerialInfo annotations.

Rules

Custom annotations

Constraint annotations are matched by name, so any package can be registered through kt-schema.properties. A match is mapped by its simple name: a com.acme.Size(min, max) works once com.acme.* is listed.

properties
introspector.annotations.constraint.names=jakarta.validation.constraints.*,javax.validation.constraints.*,com.acme.*

Built-in type constraints

Types with a fixed range or wire format carry it in the schema without any annotation.

Kotlin / Java typeEmitted keywords
Byte, Shortminimum, maximum of the type range
UByte, UShort, UIntminimum: 0, maximum of the type
ULongminimum: 0
CharminLength: 1, maxLength: 1
java.util.UUID, kotlin.uuid.Uuidformat: "uuid"
kotlin.time.Instant, kotlinx.datetime.Instant, java.time.Instant, OffsetDateTime, ZonedDateTime, java.util.Dateformat: "date-time"
kotlinx.datetime.LocalDate, java.time.LocalDateformat: "date"
kotlinx.datetime.LocalTime, java.time.LocalTime, java.time.OffsetTimeformat: "time"

Int and Long stay plain integer; LocalDateTime is a plain string since JSON Schema has no format for a zone-less date-time. The java.* types apply to reflection, KSP and APT; kotlinx.serialization sees only the Kotlin types it has serializers for.

important

Date/time types are always emitted as ISO-8601 strings. The generators never see your ObjectMapper, so if you serialize with plain Jackson, disable SerializationFeature.WRITE_DATES_AS_TIMESTAMPS so payloads match the schema.

See Also