Type-safe request parsing, validation, and error handling — generated at compile time with zero reflection.
Kontract turns annotated request data classes into compile-time generated Vert.x contracts. No reflection, less handler boilerplate, and safer refactoring.
- Compile-time generated parsing and validation
- Consistent bad-request responses across endpoints
- Clean handlers focused on business logic
👉 Full working example: sample
❌ When using plain Vert.x, parsing and validating parameters is usually repeated in each handler:
router.get("/users/:userId").handler { ctx ->
val userIdRaw = ctx.pathParam("userId")
?: return@handler ctx.response().setStatusCode(400).end("Missing path param: userId")
val userId = userIdRaw.toLongOrNull()
?: return@handler ctx.response().setStatusCode(400).end("userId must be Long")
if (userId < 1) {
return@handler ctx.response().setStatusCode(400).end("userId must be >= 1")
}
val fields = ctx.queryParam("fields").firstOrNull()
val user = userService.get(userId, fields)
ctx.json(user)
}✅ With Kontract, the same intent is defined once in a request model and generated contract:
@VertxEndpoint(method = HttpMethod.GET, path = "/users/:userId")
data class GetUserRequest(
@PathParam @Min(1) val userId: Long,
@QueryParam val fields: String? = null,
)
GetUserRequest.route(router) { req, ctx ->
val user = userService.get(req.userId, req.fields)
ctx.json(user)
}- Less boilerplate in route handlers
- Validation rules stay close to request definitions
- Consistent HTTP 400 behavior for invalid inputs
- Compile-time code generation via KSP — no runtime reflection overhead
- Type-safe request parsing for path, query, header, cookie, and body parameters
- Built-in validation and consistent error handling
- Extensible type conversion
- Flexible serialization and responses — serialization with optional auto-response handling
- Coroutine support — generated coroutine route APIs
- Gradle plugin for zero-config setup
Recommended setup (use the Gradle plugin for zero-config setup):
plugins {
id("com.google.devtools.ksp") version "2.0.0-1.0.24"
id("io.github.chloeeekim.kontract") version "0.2.0"
}Manual setup is also available when you need fine-grained dependency control.
More options: docs/installation.md
Get started in 3 steps: define request, generate contract, register route.
After you define or change the request data class, run Gradle so KSP generates the *Contract type (for example ./gradlew kspKotlin or ./gradlew build). Until that step succeeds, companion extensions such as GetUserRequest.route are not available because the contract class does not exist yet.
@VertxEndpoint(method = HttpMethod.GET, path = "/users/:userId")
data class GetUserRequest(
@PathParam val userId: Long,
@QueryParam val fields: String? = null,
) {
companion object
}
GetUserRequest.route(router) { req, ctx ->
ctx.json(userService.get(req.userId, req.fields))
}More examples: docs/quick-start.md
Kontract supports path/query/header/cookie/body parameters, required/optional/default rules, built-in validations, custom required messages, and custom converters.
Details:
Configure global error handling and optional response auto-serialization.
Details: docs/response-and-errors.md
Enable coroutine route generation with coRoute() and coRouteWithResponse().
Details: docs/coroutines.md
vertx-contract/
├── kontract-annotation/ ← Annotations + runtime support types
├── kontract-processor/ ← KSP processor (compile-time only)
├── gradle-plugin/ ← Gradle plugin for auto-configuration
└── sample/ ← Usage examples and integration tests
- Kotlin 2.0+
- Vert.x Web 4.5+
- JDK 17+
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License — see the LICENSE file for details.