Introducing Kotlinx-schema
Kotlinx-schema is an experimental JetBrains library that automates JSON Schema generation from Kotlin and Java code. It eliminates schema drift by deriving schemas directly from your source, ensuring that API documentation and implementation stay synchronized as the codebase evolves.
Unlike traditional reflection-based tools, Kotlinx-schema provides both runtime reflection and compile-time generation via KSP. This KSP integration is essential for Kotlin Multiplatform (KMP) targets where runtime reflection is limited or unavailable, allowing you to ship schemas as part of the build process.
Core capabilities include:
- Polymorphic Class Schemas: Support for nested and sealed classes.
- Function Signatures: Automatic schema extraction for method parameters, optimized for LLM tool calling.
- Metadata Extraction: Seamless integration with KDoc (via KSP) and annotations to populate field descriptions.
- Nullability Control: Configurable mapping of nullable types to optional fields or JSON Schema union types.
- JSON Schema DSL: A programmatic model for building or manipulating schemas manually.
To integrate Kotlinx-schema, configure the KSP plugin and annotate your types to start generating synchronized schemas during your build.
Configuration
To enable KSP-based schema generation in a Kotlin Multiplatform project, add the following to your build.gradle.kts:
1plugins {
2 kotlin("multiplatform")
3 kotlin("plugin.serialization")
4 id("com.google.devtools.ksp") version "2.3.5"
5}
6
7// Check the latest version on Maven Central
8val kotlinxSchemaVersion = "0.1.0"
9
10dependencies {
11 add("kspCommonMainMetadata", "org.jetbrains.kotlinx:kotlinx-schema-ksp:$kotlinxSchemaVersion")
12}
13
14kotlin {
15 jvm()
16
17 js {
18 nodejs()
19 }
20
21 sourceSets {
22 commonMain {
23 // Register source dir for generated sources
24 kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
25
26 dependencies {
27 implementation(libs.kotlinx.serialization.json)
28 // Required for annotating classes to process
29 implementation("org.jetbrains.kotlinx:kotlinx-schema-annotations:$kotlinxSchemaVersion")
30 }
31 }
32 }
33}
34
35ksp {
36 // Generate JsonSchema extension property along with string representation of the schema
37 arg("kotlinx.schema.withSchemaObject", "true")
38 // Process classes from this package
39 arg("kotlinx.schema.rootPackage", "com.example.shapes")
40}For more details, see the quickstart guide and the KSP setup guide.
Generating Class Schemas
The following example demonstrates JSON Schema generation for a sealed class hierarchy:
1package com.example.shapes
2
3import kotlinx.schema.Description
4import kotlinx.schema.Schema
5
6/**
7 * A geometric shape. This sealed class demonstrates polymorphic schema generation.
8 */
9@Schema
10sealed class Shape {
11 @Description("A name for this shape")
12 abstract val name: String
13
14 /**
15 * A circle defined by its radius.
16 */
17 @Schema
18 data class Circle(
19 override val name: String,
20 @Description("Radius in units (must be positive)")
21 val radius: Double,
22 ) : Shape()
23
24 /**
25 * A rectangle with width and height.
26 */
27 @Schema
28 data class Rectangle(
29 override val name: String,
30 @Description("Width in units (must be positive)")
31 val width: Double,
32 @Description("Height in units (must be positive)")
33 val height: Double,
34 ) : Shape()
35}After building the project, the KSP processor generates extension properties you can use to obtain the schema as a string or as a JsonObject.
1import com.example.shapes.Shape
2import com.example.shapes.jsonSchemaString
3import kotlinx.serialization.json.Json
4
5fun main() {
6 println(Shape::class.jsonSchemaString)
7}The generated schema is:
1{
2 "$schema": "https://json-schema.org/draft/2020-12/schema",
3 "$id": "com.example.shapes.Shape",
4 "description": "A geometric shape. This sealed class demonstrates polymorphic schema generation.",
5 "type": "object",
6 "additionalProperties": false,
7 "oneOf": [
8 {
9 "$ref": "#/$defs/com.example.shapes.Shape.Circle"
10 },
11 {
12 "$ref": "#/$defs/com.example.shapes.Shape.Rectangle"
13 }
14 ],
15 "$defs": {
16 "com.example.shapes.Shape.Circle": {
17 "type": "object",
18 "description": "A circle defined by its radius.",
19 "properties": {
20 "name": {
21 "type": "string"
22 },
23 "radius": {
24 "type": "number",
25 "description": "Radius in units (must be positive)"
26 }
27 },
28 "required": [
29 "name",
30 "radius"
31 ],
32 "additionalProperties": false
33 },
34 "com.example.shapes.Shape.Rectangle": {
35 "type": "object",
36 "description": "A rectangle with width and height.",
37 "properties": {
38 "name": {
39 "type": "string"
40 },
41 "width": {
42 "type": "number",
43 "description": "Width in units (must be positive)"
44 },
45 "height": {
46 "type": "number",
47 "description": "Height in units (must be positive)"
48 }
49 },
50 "required": [
51 "name",
52 "width",
53 "height"
54 ],
55 "additionalProperties": false
56 }
57 }
58}The generated schema also incorporates information from KDoc!
KSP generates extensions for all classes annotated with @Schema.
You can also use the jsonSchema extension property:
1val jsonSchema: JsonObject = Shape::class.jsonSchemaGenerating Function Schemas
The library also supports schema generation for function signatures:
1/**
2 * Greets the user with a personalized message.
3 *
4 * @param name the name of the person to greet
5 * @return a greeting message addressed to the specified name
6 */
7@Schema
8internal fun sayHello(
9 @Description("Name to greet") name: String?,
10): String = "Hello, ${name ?: "friend"}!"This is particularly useful when you need a JSON Schema representation of callable tools (for example, in LLM integrations).
1import com.example.shapes.sayHelloJsonSchema
2import com.example.shapes.sayHelloJsonSchemaString
3import kotlinx.serialization.json.Json
4import kotlinx.serialization.json.JsonObject
5
6private val json = Json { prettyPrint = true }
7
8fun main() {
9 val functionCallSchemaString: String = sayHelloJsonSchemaString()
10 val functionCallSchema: JsonObject = sayHelloJsonSchema()
11
12 println("$functionCallSchemaString\n")
13
14 println("${functionCallSchema::class}: ${json.encodeToString(functionCallSchema)}")
15}The resulting JSON representation includes both the raw string and the JsonObject model:
1{"type":"function","name":"sayHello","description":"Greets the user with a personalized message.","strict":true,"parameters":{"type":"object","properties":{"name":{"type":["string","null"],"description":"Name to greet"}},"required":["name"],"additionalProperties":false}}
2
3class kotlinx.serialization.json.JsonObject: {
4 "type": "function",
5 "name": "sayHello",
6 "description": "Greets the user with a personalized message.",
7 "strict": true,
8 "parameters": {
9 "type": "object",
10 "properties": {
11 "name": {
12 "type": [
13 "string",
14 "null"
15 ],
16 "description": "Name to greet"
17 }
18 },
19 "required": [
20 "name"
21 ],
22 "additionalProperties": false
23 }
24}Conclusion
Kotlinx-schema provides a type-safe, compile-time approach to JSON Schema generation, ensuring that API definitions and implementation stay in sync. Its modular architecture allows plugging in additional metadata sources and targeting new schema or serialization formats, making it a robust choice for modern Kotlin development—especially when targeting Kotlin Multiplatform or integrating with LLMs.
As an experimental project, community feedback is essential for shaping its future. ✨
Resources
- GitHub: Kotlin/kotlinx-schema
- Maven Central: Artifacts
- API Reference: Documentation
- Samples: Project Examples