Skip to content

Migration from 6.x to 7.0

Javalin OpenAPI 7.0 is a major release built around OpenAPI 3.1.0 and a completely redesigned core. Below you'll find what's new and what you need to change.

6.x7.0
Java11+17+
Kotlin1.92.3
Javalin6.x7.x

What's New

OpenAPI 3.1.0 & JSON Schema 2020-12

Generated specs are now OpenAPI 3.1.0 (was 3.0.3), fully aligned with JSON Schema 2020-12. See Breaking Changes for details on how this affects generated output.

Runtime Builder DSL

The new openapi-generator module provides a Kotlin DSL for building or extending OpenAPI specifications programmatically at runtime. Define entirely new operations or modify existing ones generated by the annotation processor:

kotlin
spec.path("/users/{id}").operation("get") {
    tags("users")
    summary("Get user by ID")
    parameters {
        parameter("id", "path", required = true) { type("string") }
    }
    responses {
        response("200") {
            description("OK")
            content {
                mediaType("application/json") {
                    schema { ref("#/components/schemas/User") }
                }
            }
        }
        response("404") { description("User not found") }
    }
}

See Runtime Builder DSL for details.

Naming Strategies

New @OpenApiNaming annotation applies automatic name transformation to properties and enum values at the class level:

kotlin
@OpenApiNaming(OpenApiNamingStrategy.SNAKE_CASE)
class UserResponse(
    val firstName: String,  // → "first_name"
    val lastName: String,   // → "last_name"
)

See Naming Strategies for details.

Enhanced Enum Support

Enums now support @OpenApiPropertyType, @OpenApiDescription, and @OpenApiName annotations. Integer enums can be modeled with @OpenApiPropertyType(definedBy = Int::class) combined with @OpenApiName:

kotlin
@OpenApiPropertyType(definedBy = Int::class)
@OpenApiDescription("Sort order:\n * 1 - Ascending\n * 2 - Descending")
enum class SortOrder {
    @OpenApiName("1") ASCENDING,
    @OpenApiName("2") DESCENDING
}

See Enums for details.

Raw Examples

@OpenApiExample now supports a raw field for JSON-formatted examples (previously only available on @OpenApiExampleProperty):

kotlin
@OpenApiExample(raw = """{"key": "value"}""")

See Examples for details.

Additional Type Mappings

New built-in type mappings:

Java TypeOpenAPI TypeFormat
UUIDstringuuid
OffsetDateTimestringdate-time
LocalTimestringtime
Durationstringduration
URIstringuri
URLstringuri

Simplified Plugin API

Security methods are available directly on OpenApiSchemaBuilder — no more withSecurity { ... } wrapper:

kotlin
openApiConfig.withDefinitionConfiguration { version, builder ->
    builder
        .info { it.title("My API") }
        .withBearerAuth()
        .withGlobalSecurity("BearerAuth")
}

withDefinitionProcessor is now a top-level plugin option. Both SwaggerConfiguration and ReDocConfiguration use fluent with*() methods matching OpenApiPluginConfiguration style.

UI Updates

UI6.x7.0
Swagger UI5.17.145.31.2
ReDoc2.1.42.5.0

Documentation

New documentation site covering all features: setup guides, OpenAPI annotations, JSON Schema generation, type composition, validation, naming strategies, enums, examples, and the runtime builder DSL. You're reading it right now.


Breaking Changes

Dependencies

Update the version in your build file:

kotlin
val openapi = "7.0.0" // was 6.x
xml
<version>7.0.0</version> <!-- was 6.x -->

New openapi-generator module

7.0 extracted the runtime builder DSL into a new openapi-generator module. This is a transitive dependency of openapi-annotation-processor and all Javalin plugins, so no changes to your dependency declarations are needed unless you were importing internal generator classes directly.

Gson removed

The openapi-specification module no longer depends on Gson. If your project relied on the transitive Gson dependency, add it explicitly:

kotlin
implementation("com.google.code.gson:gson:2.11.0")

Jackson (jackson-databind, jackson-module-kotlin) remains the default and only JSON library used internally.

Groovy configuration script path

6.x auto-detected openapi.groovy from the source tree. 7.0 requires an explicit annotation processor option:

kotlin
tasks.withType<JavaCompile> {
    options.compilerArgs.add(
        "-Aopenapi.groovy.path=$projectDir/src/main/compile/openapi.groovy"
    )
}
kotlin
kapt {
    arguments {
        arg("openapi.groovy.path", "$projectDir/src/main/compile/openapi.groovy")
    }
}
xml
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>io.javalin.community.openapi</groupId>
                <artifactId>openapi-annotation-processor</artifactId>
                <version>7.0.0</version>
            </path>
        </annotationProcessorPaths>
        <compilerArgs>
            <arg>-Aopenapi.groovy.path=${project.basedir}/src/main/compile/openapi.groovy</arg>
        </compilerArgs>
    </configuration>
</plugin>

If you don't use a Groovy configuration script, no changes are needed.

OpenAPI 3.0.3 → 3.1.0

Generated specs are now OpenAPI 3.1.0 (was 3.0.3), based on JSON Schema 2020-12. This changes the generated output in several ways.

Nullable types

The nullable: true keyword is replaced by type arrays. This affects all nullable properties in your generated schemas:

json
// 7.0 (OpenAPI 3.1.0)
{ "type": ["string", "null"] }

// 6.x (OpenAPI 3.0.3)
{ "type": "string", "nullable": true }

Nullable $ref types are wrapped in anyOf:

json
// 7.0
{
  "anyOf": [
    { "$ref": "#/components/schemas/Address" },
    { "type": "null" }
  ]
}

// 6.x
{ "$ref": "#/components/schemas/Address", "nullable": true }

Nullable oneOf/anyOf types append a null entry:

json
// 7.0 — oneOf with nullable
{
  "oneOf": [
    { "$ref": "#/components/schemas/Cat" },
    { "$ref": "#/components/schemas/Dog" },
    { "type": "null" }
  ]
}

No annotation changes needed — this is handled automatically by the generator. If you have tooling that parses the generated spec (validators, code generators, etc.), it needs to understand 3.1.0 nullable semantics.

additionalProperties default

6.x explicitly set "additionalProperties": false on every object schema. 7.0 omits it, following JSON Schema 2020-12 where the default is true. If your consumers relied on strict additionalProperties: false, use a definition processor to re-add it.

exclusiveMinimum / exclusiveMaximum

Changed from Boolean flags to numeric String values, matching JSON Schema 2020-12:

kotlin
// 7.0 — numeric value (the exclusive bound itself)
@OpenApiNumberValidation(exclusiveMinimum = "0")

// 6.x — boolean flag (means: minimum is exclusive)
@OpenApiNumberValidation(minimum = "0", exclusiveMinimum = true)

Parameter examples

Parameter-level examples are now emitted at the parameter level (per spec), not nested inside the schema:

json
// 7.0
{ "name": "id", "in": "path", "schema": { "type": "string" }, "example": "abc" }

// 6.x
{ "name": "id", "in": "path", "schema": { "type": "string", "example": "abc" } }

Plugin configuration

definitionConfiguration API replaced

The DefinitionConfiguration / SecurityComponentConfiguration classes are removed. The withDefinitionConfiguration callback now receives an OpenApiSchemaBuilder directly:

kotlin
// 7.0
openApiConfig.withDefinitionConfiguration { version, builder ->
    builder
        .info { it.title("My API") }
        .server { it.url("https://api.example.com") }
        .withBearerAuth()
        .withGlobalSecurity("BearerAuth")
}
// Definition processor is now a separate top-level option:
openApiConfig.withDefinitionProcessor { content ->
    content.set<TextNode>("x-custom", TextNode("value"))
    content.toPrettyString()
}

// 6.x
openApiConfig.withDefinitionConfiguration { version, definition ->
    definition
        .withInfo { it.title("My API") }
        .withServer { it.url("https://api.example.com") }
        .withSecurity { security ->
            security
                .withBearerAuth()
                .withGlobalSecurity("BearerAuth")
        }
        .withDefinitionProcessor { content ->
            content.set<TextNode>("x-custom", TextNode("value"))
            content.toPrettyString()
        }
}

Key differences:

  • withInfo(...)info(...)
  • withServer(...)server(...)
  • Security methods (withBearerAuth, withBasicAuth, withOAuth2, etc.) moved from SecurityComponentConfiguration to OpenApiSchemaBuilder directly — no more withSecurity(security -> ...) wrapper
  • withDefinitionProcessor moved from DefinitionConfiguration to OpenApiPluginConfiguration

Swagger & ReDoc configuration style

Both SwaggerConfiguration and ReDocConfiguration now use @JvmField constructor parameters and fluent with*() methods, matching the OpenApiPluginConfiguration style.

Direct property assignment still works identically in Kotlin. Java users should replace setX() setters with withX() fluent methods or direct field access:

kotlin
// Works in both 6.x and 7.0
config.registerPlugin(SwaggerPlugin {
    it.documentationPath = "/api/docs"
    it.uiPath = "/swagger"
})

The same applies to ReDocConfiguration.

Annotation changes

formParams removed

The formParams field was removed from @OpenApi. Use requestBody with the appropriate content type instead:

kotlin
// 7.0
@OpenApi(
    requestBody = OpenApiRequestBody(
        content = [OpenApiContent(from = File::class, mimeType = "multipart/form-data")]
    )
)

// 6.x
@OpenApi(
    formParams = [OpenApiParam(name = "file", type = File::class)]
)

Full before/after example

kotlin
Javalin.start { config ->
    config.registerPlugin(OpenApiPlugin { openapi ->
        openapi
            .withDocumentationPath("/openapi")
            .withRoles(Rules.ANONYMOUS)
            .withDefinitionConfiguration { version, builder ->
                builder
                    .info { it.title("My API").description("API docs") }
                    .server { it.url("https://api.example.com") }
                    .withBearerAuth()
                    .withGlobalSecurity("BearerAuth")
            }
            .withDefinitionProcessor { content ->
                content.set<TextNode>("x-gen", TextNode("true"))
                content.toPrettyString()
            }
    })

    config.registerPlugin(SwaggerPlugin { it.documentationPath = "/openapi" })
    config.registerPlugin(ReDocPlugin { it.documentationPath = "/openapi" })
}
kotlin
Javalin.createAndStart { config ->
    config.registerPlugin(OpenApiPlugin { openapi ->
        openapi
            .withDocumentationPath("/openapi")
            .withRoles(Rules.ANONYMOUS)
            .withDefinitionConfiguration { version, definition ->
                definition
                    .withInfo { it.title("My API").description("API docs") }
                    .withServer { it.url("https://api.example.com") }
                    .withSecurity { security ->
                        security
                            .withBearerAuth()
                            .withGlobalSecurity("BearerAuth")
                    }
                    .withDefinitionProcessor { content ->
                        content.set<TextNode>("x-gen", TextNode("true"))
                        content.toPrettyString()
                    }
            }
    })

    config.registerPlugin(SwaggerPlugin { it.documentationPath = "/openapi" })
    config.registerPlugin(ReDocPlugin { it.documentationPath = "/openapi" })
}