Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
771f471
feat: proof of concept
dzikoysk Jun 8, 2026
9bdb4a6
chore: move introspection related code to a separate module
dzikoysk Jun 14, 2026
1717cec
chore: cleanup
dzikoysk Jun 14, 2026
e248c0d
chore: migrate dynamic module to new introspection api
dzikoysk Jun 14, 2026
96935e1
feat: support transient
dzikoysk Jun 14, 2026
dcb1ce5
chore: phase 1 transition
dzikoysk Jun 17, 2026
66336cb
test: prototype
dzikoysk Jun 17, 2026
9371903
chore: finding 1
dzikoysk Jun 17, 2026
f0835ae
chore: finding 2
dzikoysk Jun 17, 2026
1e0e08b
chore: finding 3
dzikoysk Jun 17, 2026
7e3d895
chore: yet another migration
dzikoysk Jun 20, 2026
be96af3
chore: cleanup
dzikoysk Jun 20, 2026
c9ff59d
feat: impl ksp backend
dzikoysk Jun 21, 2026
4411a49
feat: ksp output
dzikoysk Jun 21, 2026
325d20b
chore: cleanup docs
dzikoysk Jun 21, 2026
4668091
chore: migrate ap specific openapi schema generator to new introspect…
dzikoysk Jun 21, 2026
09d007b
feat: abstract common part of annotation processors
dzikoysk Jun 22, 2026
7b190fd
feat: ksp example project + docs
dzikoysk Jun 23, 2026
9f74c64
chore: update example
dzikoysk Jun 23, 2026
25f8ed1
Merge remote-tracking branch 'origin/main' into GH-49-openapi-dynamic
dzikoysk Jun 23, 2026
a7e8c48
chore: adjust error messages
dzikoysk Jun 27, 2026
12f5e89
chore: cleanup
dzikoysk Jun 28, 2026
2f05318
chore: docs
dzikoysk Jun 28, 2026
9821a3d
chore: cleanup test
dzikoysk Jun 28, 2026
2e3e726
chore: yet another cleanup
dzikoysk Jun 29, 2026
1f95899
chore: yet another cleanup
dzikoysk Jul 8, 2026
c14dd55
chore: tests
dzikoysk Jul 9, 2026
db3bfb8
fix: tests
dzikoysk Jul 9, 2026
2e67149
chore: cleanup
dzikoysk Jul 13, 2026
66444cf
cr: address copilot comments
dzikoysk Jul 17, 2026
9cf4608
chore: another round of cleanups
dzikoysk Jul 19, 2026
55f6e9c
chore: cleanup
dzikoysk Jul 21, 2026
bcd2f2c
chore: cleanup
dzikoysk Jul 21, 2026
01325d6
chore: another round of cleanups
dzikoysk Jul 26, 2026
99134a4
fix: address a couple of issues
dzikoysk Jul 27, 2026
7c85eaf
chore: cleanup
dzikoysk Jul 28, 2026
6a1cd68
chore: cleanup
dzikoysk Aug 1, 2026
3c85d0a
cr: address comments
dzikoysk Aug 1, 2026
a885fff
chore: one more cleanup I promise
dzikoysk Aug 1, 2026
f1d614f
chore: cleanup
dzikoysk Aug 1, 2026
0a412ed
chore: bump
dzikoysk Aug 1, 2026
21550c2
chore: code style
dzikoysk Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ allprojects {
apply(plugin = "maven-publish")

group = "io.javalin.community.openapi"
version = "7.2.2"
version = "7.3.0-RC.1"

repositories {
mavenCentral()
Expand Down
3 changes: 2 additions & 1 deletion docs/.vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ export default defineConfig({
text: 'Advanced',
collapsed: false,
items: [
{ text: 'Compile-time Configuration', link: '/advanced/configuration' },
{ text: 'Static Configuration', link: '/advanced/configuration' },
{ text: 'Scripting Configuration', link: '/advanced/scripting' },
{ text: 'Runtime Builder DSL', link: '/advanced/runtime-builder' },
],
},
Expand Down
190 changes: 16 additions & 174 deletions docs/advanced/configuration.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,14 @@
# Compile-time Configuration
# Static Configuration

Configure the annotation processor using a Groovy script. This allows custom type mappings, property filters, and advanced type processors.
Pass key-value options to the backend at build time. The `info.*` options apply to both the annotation processor (APT/Kapt) and KSP; `openapi.groovy.path` is APT/Kapt-only.

## Setup
## Options

Create a Groovy configuration script (e.g. `openapi.groovy`) anywhere in your project
and point the annotation processor to it using the `openapi.groovy.path` option:

```groovy
import io.javalin.openapi.experimental.*

@ExperimentalCompileOpenApiConfiguration
class OpenApiConfiguration
implements OpenApiAnnotationProcessorConfigurer {

@Override
void configure(
OpenApiAnnotationProcessorConfiguration configuration
) {
// Configuration goes here
}
}
```

::: code-group

```kotlin [Gradle (Kotlin)]
kapt {
arguments {
arg(
"openapi.groovy.path",
"$projectDir/src/main/compile/openapi.groovy"
)
}
}
```

```groovy [Gradle (Groovy)]
kapt {
arguments {
arg(
'openapi.groovy.path',
"$projectDir/src/main/compile/openapi.groovy"
)
}
}
```

```xml [Maven]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-Aopenapi.groovy.path=${project.basedir}/src/main/compile/openapi.groovy</arg>
</compilerArgs>
</configuration>
</plugin>
```

:::

## Annotation Processor Options

The following options can be passed to the annotation processor:

| Option | Description |
|-------------------------|----------------------------------------------------------|
| `openapi.info.title` | Set the `info.title` field in the generated specification |
| `openapi.info.version` | Set the `info.version` field in the generated specification |
| `openapi.groovy.path` | Path to the Groovy configuration script |
| Option | Description |
|------------------------|------------------------------------------------------------------------------------------------------|
| `openapi.info.title` | Set the `info.title` field in the generated specification |
| `openapi.info.version` | Set the `info.version` field in the generated specification |
| `openapi.groovy.path` | Path to a Groovy script for advanced configuration (APT/Kapt only, see [Scripting Configuration](./scripting)) |

::: code-group

Expand All @@ -82,6 +21,13 @@ kapt {
}
```

```kotlin [Gradle (KSP)]
ksp {
arg("openapi.info.title", "My API")
arg("openapi.info.version", "1.0.0")
}
```

```xml [Maven]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand All @@ -97,108 +43,4 @@ kapt {

:::

## Custom Type Mappings

Map custom types to simple OpenAPI types:

```groovy
void configure(
OpenApiAnnotationProcessorConfiguration configuration
) {
configuration.simpleTypeMappings[
'org.bson.types.ObjectId'
] = new SimpleType("string")

configuration.simpleTypeMappings[
'com.example.CustomId'
] = new SimpleType(/* type */ "integer", /* format */ "int64")
}
```

## Property Filters

Control which properties are included in schemas:

```groovy
configuration.propertyInSchemeFilter = {
ctx, type, property ->
!property.simpleName
.toString()
.startsWith("internal")
}
```

## Custom Type Processors

Insert custom logic for handling specific types (e.g., unwrapping `AtomicReference<T>`):

```groovy
configuration.insertEmbeddedTypeProcessor({
EmbeddedTypeProcessorContext context ->
if (context.type.simpleName == 'AtomicReference'
&& context.type.generics.size() == 1) {
context.parentContext.typeSchemaGenerator.addType(
context.scheme,
context.type.generics[0],
context.inlineRefs,
context.references,
false
)
return true // handled
}

return false // use default processing
})
```

Custom type processors run before all built-in type processing, so they can override the default behavior for any type.

## Debug Mode

Enable debug output during annotation processing:

```groovy
configuration.debug = true
```

## Parser Validation

Validate the generated specification with Swagger Parser:

```groovy
configuration.validateWithParser = true // default
```

## Full Example

```groovy
import io.javalin.openapi.experimental.*

@ExperimentalCompileOpenApiConfiguration
class OpenApiConfiguration
implements OpenApiAnnotationProcessorConfigurer {

@Override
void configure(
OpenApiAnnotationProcessorConfiguration configuration
) {
configuration.simpleTypeMappings[
'org.bson.types.ObjectId'
] = new SimpleType("string")

configuration.simpleTypeMappings[
'com.example.Money'
] = new SimpleType("string")

configuration.propertyInSchemeFilter = {
ctx, type, property ->
!property.simpleName
.toString()
.startsWith("_")
}

configuration.debug = false
configuration.validateWithParser = true
}
}
```
For custom type mappings, property filters, and custom type processors, see [Scripting Configuration](./scripting).
74 changes: 67 additions & 7 deletions docs/advanced/runtime-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ The `OpenApiSchemaBuilder` provides a Kotlin DSL for building and modifying Open

## 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
- **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

Expand Down Expand Up @@ -41,21 +41,21 @@ The `SchemaBuilder` DSL provides a clean way to define inline schemas without wo

```kotlin
schema { type("string") }
// { "type": "string" }
// -> { "type": "string" }
```

### Type with Format

```kotlin
schema { type("integer"); format("int32") }
// { "type": "integer", "format": "int32" }
// -> { "type": "integer", "format": "int32" }
```

### Reference

```kotlin
schema { ref("#/components/schemas/User") }
// { "$ref": "#/components/schemas/User" }
// -> { "$ref": "#/components/schemas/User" }
```

The schema DSL is available on media types, parameters, headers, and object schema properties.
Expand Down Expand Up @@ -217,7 +217,67 @@ schema.path("/users").operation("get") {
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.
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.

## Auto-generating Docs for Registered Routes

For a springdoc-style experience - documenting routes that are registered programmatically (and that the compile-time processor never sees) - use the **dynamic hook** module. It adds undocumented Javalin routes to the served document.

```kotlin [Gradle (Kotlin)]
dependencies {
val openapi = "7.3.0-RC.1"
implementation("io.javalin.community.openapi:javalin-openapi-dynamic-hook:$openapi")
}
```

Register `RegisteredRoutesHook` on the `OpenApiPlugin`:

```kotlin
Javalin.start { config ->
config.registerPlugin(OpenApiPlugin { it.withHook(RegisteredRoutesHook()) })

config.routes.get("/users") { /* ... */ }
config.routes.get("/users/{id}") { /* ... */ }
}
```

Every newly discovered route is documented with its path, method, path parameters (typed as `string`), and a default `200` response. Existing operations from compile-time documentation are left unchanged unless the route carries runtime metadata.

Routes registered by `OpenApiPlugin`, `SwaggerPlugin`, and `ReDocPlugin` are excluded by default, including custom UI and WebJar paths. Add prefix exclusions for your own non-API routes:

```kotlin
RegisteredRoutesHook { routes ->
routes.withIgnoredPathPrefixes("/assets", "/internal")
}
```

Each prefix matches the path itself and its descendants, so `/assets` excludes `/assets/logo.svg` but not `/assets-admin`. A trailing `/*` is accepted as an equivalent spelling. To include the default excluded plugin routes, call `clearDefaultIgnoredRoutes()`.

### Enriching a Route

Javalin handlers are opaque lambdas, so without extra information the hook can only emit those stubs - it cannot infer request/response bodies. Attach an `OpenApiMetadata` to a route to describe it using the same operation DSL shown above; `schema(Class)` resolves the type through the reflection schema engine (no annotation processing required):

```kotlin
config.routes.addEndpoint(
Endpoint.create(HandlerType.GET, "/users/{id}")
.addMetadata(OpenApiMetadata {
summary("Get a user")
responses {
response("200") {
description("The user")
content { mediaType("application/json") { schema(User::class.java) } }
}
}
})
.handler { /* ... */ }
)
```

For newly discovered operations, the path-parameter skeleton is auto-added before your metadata is applied. Existing operations retain their parameters, and referenced types (e.g. `User`) are emitted into `components/schemas`.

::: warning
The document is built on its first request, so routes registered after that request are not included. Runtime reflection cannot see CLASS-retention annotations or automatically discover discriminator subtypes. Runtime reflection is opt-in: it only happens when you add this module and register the hook, so the compile-time backends stay reflection-free.
:::

## Merge Behavior

Expand Down
Loading
Loading