Runtime Builder DSL
The OpenApiSchemaBuilder provides a Kotlin DSL for building and modifying OpenAPI specifications programmatically at runtime. Use it to extend compile-time generated schemas, build specs entirely in code, or combine both approaches.
When to Use
- Extend compile-time schemas — add servers, security, or extra endpoints at startup
- Dynamic endpoints — describe routes registered at runtime that the annotation processor can't see
- Testing — build expected schemas in tests without JSON strings
Basic Usage
val spec = OpenApiSchemaBuilder()
.openApiVersion("3.1.0")
.info { it.title("My API").version("1.0") }
spec.path("/users").operation("get") {
tags("users")
summary("List users")
responses {
response("200") {
description("OK")
content {
mediaType("application/json") {
schema { ref("#/components/schemas/User") }
}
}
}
}
}
val json = spec.toJson()Schema DSL
The SchemaBuilder DSL provides a clean way to define inline schemas without working with raw JSON nodes. It supports the three most common patterns:
Simple Type
schema { type("string") }
// → { "type": "string" }Type with Format
schema { type("integer"); format("int32") }
// → { "type": "integer", "format": "int32" }Reference
schema { ref("#/components/schemas/User") }
// → { "$ref": "#/components/schemas/User" }The schema DSL is available on media types, parameters, headers, and object schema properties.
Paths & Operations
spec.path("/users/{id}").operation("get") {
tags("users")
summary("Get user by ID")
description("Returns a single user")
operationId("getUserById")
deprecated(false)
}Multiple methods on the same path:
val path = spec.path("/users")
path.operation("get") { summary("List users") }
path.operation("post") { summary("Create user") }Parameters
Define parameters with a trailing schema lambda:
parameters {
parameter("limit", "query") { type("integer") }
parameter("offset", "query", description = "Starting position") {
type("integer"); format("int32")
}
}Full parameter options:
parameter(
name = "X-Request-ID",
location = "header",
description = "Tracking ID",
required = true,
deprecated = false,
allowEmptyValue = false,
example = "abc-123",
) { type("string") }Request Body
requestBody {
description("User to create")
required(true)
content {
mediaType("application/json") {
schema { ref("#/components/schemas/User") }
}
}
}Responses
responses {
response("200") {
description("OK")
content {
mediaType("application/json") {
schema { ref("#/components/schemas/User") }
example("""{"name": "John"}""")
}
}
headers {
header("X-Request-Id") { type("string") }
header("X-Rate-Limit", description = "Rate limit remaining") {
type("integer")
}
}
}
response("404") { description("Not found") }
}Object Schemas
Build inline object schemas with properties:
mediaType("application/json") {
objectSchema {
property("name", "string", null)
property("age", "integer", "int32")
property("address") { ref("#/components/schemas/Address") }
arrayProperty("tags") { ref("#/components/schemas/Tag") }
arrayProperty("scores", "integer", "int32")
additionalProperties { ref("#/components/schemas/Value") }
}
}Callbacks
callbacks {
callback("onEvent", "{request.body#/url}/callback", "post") {
summary("Event callback")
requestBody {
content {
mediaType("application/json") {
schema { ref("#/components/schemas/Event") }
}
}
}
responses {
response("200") { description("OK") }
}
}
}Security
Operation-level
spec.path("/secure").operation("get") {
security {
securityRequirement("BearerAuth")
securityRequirement("OAuth2", "read", "write")
}
}Global security and schemes
See Setup with Javalin for configuring security schemes and global security on the builder.
Extending Compile-time Schemas
Load a compile-time generated spec and modify it at runtime:
val schema = OpenApiSchemaBuilder.fromJson(compileTimeJson)
// Add runtime configuration
schema.server { it.url("https://api.example.com") }
schema.withBearerAuth()
// Add security to an existing endpoint
schema.path("/users").operation("get") {
security { securityRequirement("BearerAuth") }
}
val json = schema.toJson()Reopening an existing operation preserves all fields — only the fields you set are changed. This makes it safe to layer runtime additions on top of compile-time output.
Merge Behavior
The builder supports incremental construction. Reopening a path, operation, response, or media type merges with existing data:
- Operations: preserve tags, summary, parameters, responses, etc. Only fields you explicitly set are overwritten
- Parameters: same name + location replaces; different name or location appends
- Responses: new status codes are added; same status code merges content and headers
- Content types: new media types are added; same media type merges schema and example
- Tags:
tags(...)replaces;addTag(...)/addTags(...)appends