A friendly kotlin library to validate API endpoints using an OpenApi 3 or Swagger 2 specification. Great with webflux functional. It works happily with Spring Webflux 6's baseline of Jakarta JVM runtime >=17.
Supports specifications in YAML and JSON
and Spring Webflux 6 + Java >=17
For use with Spring Boot 2 and Webflux 5, use openapi-spring-webflux-validator version 3.5.0. Java 8 or greater is required.
<dependency> <groupId>io.github.cdimascio</groupId> <artifactId>openapi-spring-webflux-validator</artifactId> <version>4.2.0</version> </dependency>compile 'io.github.cdimascio:openapi-spring-webflux-validator:4.2.0'For sbt, grape, ivy and more, see here
This section and the next describe usage with Kotlin and Java respectively.
See this complete Spring Webflux example that uses openapi-spring-webflux-validator.
This one-time configuration requires you to provide the location of the openapi/swagger specification and an optional custom error handler.
Supports JSON and YAML
import io.github.cdimascio.openapi.Validate val validate = Validate.configure("static/api.yaml")with custom error handler
import org.springframework.web.reactive.function.server.ServerRequest data class MyError(val request: ServerRequest, val code: String, val messages: List<String>) val validate = Validate.configure("static/api.json") { request, status, messages -> MyError(request, status.name, messages) }with custom ObjectMapper factory:
val validate = Validate.configure( openApiSwaggerPath = "api.yaml", errorHandler = { request, status, message -> ValidationError(request, status.value(), message[0]) }, objectMapperFactory = { ObjectMapper() .registerKotlinModule() .registerModule(JavaTimeModule()) .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) } )You can now validate a request in a coroutine style, using the validate instance created above:
without a body
validate.request(req) { // Do stuff e.g. return a list of names ok().body(Mono.just(listOf("carmine", "alex", "eliana"))) }with body
validate.request(req).withBody(User::class.java) { body -> // Note that body is deserialized as User! // Now you can do stuff. // For example, lets echo the request as the response ok().body(Mono.just(body)) }with body you want to process as string (e.g. for computing a request signature), or that you want to deserialize somehow specifically
val identity: (String) -> String = { it } validate.request(req).withBody(String::class.java, readValue = identity) { body -> ok().body(Mono.just("content length is ${body.length}")) }Or you can validate a request in a coroutine style, using the validate instance created above:
without a body
validate.requestAndAwait(req) { // Do stuff e.g. return a list of names ok().bodyValueAndAwait(listOf("carmine", "alex", "eliana")) }with body
validate.request(req).awaitBody(User::class.java) { body: User -> // Note that body is deserialized as User! // Now you can do stuff. // For example, lets echo the request as the response ok().bodyValueAndAwait(body) }with body you want to process as string (e.g. for computing a request signature), or that you want to deserialize somehow specifically
val identity: (String) -> String = { it } validate.request(req).awaitBody(String::class.java, identity) { body: String -> ok().bodyValueAndAwait("content length is ${body.length}") }This one-time configuration requires you to provide the location of the openapi/swagger specification and an optional custom error handler.
import io.github.cdimascio.openapi.Validate; Validate<ValidationError> validate = Validate.configure("static/api.json")with custom error handler
import org.springframework.web.reactive.function.server.ServerRequest; class MyError { private ServerRequest request; private String id; private String messages; public MyError(ServerRequest request, String id, List<String> messages) { this.request = request; this.id = id; this.messages = messages; } public ServerRequest getRequest() { return request; } public String getId() { return id; } public void setId(String id) { this.id = id; } public List<String> getMessages() { return messages; } public void setMessages(List<String> messages) { this.messages = messages; } }Validate<ValidationError> validate = Validate.configure("static/api.json", (request, status, messages) -> new MyError(request, status.getName(), messages) );Using the validate instance created above, you can now validate a request:
without a body
ArrayList<String> users = new ArrayList<String>() {{ add("carmine"); add("alex"); add("eliana"); }}; validate.request(req, () -> // Do stuff e.g. return a list of user names ServerResponse.ok().bodyValue(users) );with body
validate .request(req) .withBody(User.class, user -> // Note that body is deserialized as User! // Now you can do stuff. // For example, lets echo the request as the response ServerResponse.ok().bodyValue(user) );with body you want to process as string (e.g. for computing a request signature)
validate .request(req) .withBody(String.class, s -> s, body -> ServerResponse.ok().bodyValue("content length is " + body.length()) );Let's assume a POST request to create a user requires the following request body:
{ "firstname": "carmine", "lastname": "dimasico" }Let's now assume an API user misspells lastname as lastnam
curl -X POST http://localhost:8080/api/users -H "Content-Type: application/json" -d'{ "firstname": "c", "lastnam": "d" }'openapi-spring-webflux-validator automatically validates the request against a Swagger spect and returns:
{ "code": 400, "messages":[ "Object instance has properties which are not allowed by the schema: [\"lastnam\"]", "Object has missing required properties ([\"lastname\"])" ] } Woah! Cool!! :-D
Let's say you have an endpoint /users that supports both GET and POST operations.
You can create those routes and validate them like so:
Create the routes in a reactive or coroutine style:
package myproject.controllers import org.springframework.core.io.ClassPathResource import org.springframework.http.MediaType.* import org.springframework.web.reactive.function.server.ServerResponse.permanentRedirect import org.springframework.web.reactive.function.server.coRouter import org.springframework.web.reactive.function.server.plus import org.springframework.web.reactive.function.server.router import java.net.URI class Routes(private val userHandler: UserHandler) { fun router() = router { "/api".nest { accept(APPLICATION_JSON).nest { POST("/users", userHandler::create) } accept(TEXT_EVENT_STREAM).nest { GET("/users", userHandler::findAll) } } } + coRouter { "/coApi".nest { accept(APPLICATION_JSON).nest { POST("/users", userHandler::coCreate) } accept(TEXT_EVENT_STREAM).nest { GET("/users", userHandler::coFindAll) } } } }package myproject import io.github.cdimascio.openapi.Validate val validate = Validate.configure("static/api.yaml")Validate with openapi-spring-webflux-validator
package myproject.controllers import myproject.models.User import myproject.validate import org.springframework.web.reactive.function.server.ServerRequest import org.springframework.web.reactive.function.server.ServerResponse import org.springframework.web.reactive.function.server.ServerResponse.ok import org.springframework.web.reactive.function.server.bodyValueAndAwait import reactor.core.publisher.Flux import reactor.core.publisher.Mono class UserHandler { fun findAll(req: ServerRequest): Mono<ServerResponse> { return validate.request(req) { ok().bodyValue(listOf("carmine", "alex", "eliana")) } } fun create(req: ServerRequest): Mono<ServerResponse> { return validate.request(req).withBody(User::class.java) { // it is the request body deserialized as User ok().bodyValue(it) } } suspend fun coFindAll(req: ServerRequest): ServerResponse { return validate.requestAndAwait(req) { ok().bodyValueAndAwait(listOf("carmine", "alex", "eliana")) } } suspend fun coCreate(req: ServerRequest): ServerResponse { return validate.request(req).awaitBody(User::class.java) { // it is the request body deserialized as User ok().bodyValueAndAwait(it) } } }Thanks goes to these wonderful people (emoji key):
Carmine DiMascio π» | Krzysiek KruczyΕski π» | Chejerla Karthik π» | Katie Levy π» | Ilya Builuk π» | Simon Zambrovski π» |
This project follows the all-contributors specification. Contributions of any kind welcome!

