Repository: tipsy/javalin
Pull request #864 (2020-01-24 20:05:59)

Replace putIfAbsent with computeIfAbsent


Patch:
diff --git a/src/main/java/io/javalin/http/util/RateLimitUtil.kt b/src/main/java/io/javalin/http/util/RateLimitUtil.kt
index e557aa97..24dadb58 100644
--- a/src/main/java/io/javalin/http/util/RateLimitUtil.kt
+++ b/src/main/java/io/javalin/http/util/RateLimitUtil.kt
@@ -46,8 +46,7 @@ class RateLimit(private val ctx: Context) {
      * per [timeUnit], an exception is thrown. All counters are cleared every [timeUnit].
      */
     fun requestPerTimeUnit(numRequests: Int, timeUnit: TimeUnit) = try {
-        limiters.putIfAbsent(timeUnit, RateLimiter(timeUnit))
-        limiters[timeUnit]!!.incrementCounter(ctx, numRequests)
+        limiters.computeIfAbsent(timeUnit) { RateLimiter(timeUnit) }.incrementCounter(ctx, numRequests)
     } catch (e: RuntimeException) {
         throw HttpResponseException(429, "Rate limit exceeded - Server allows $numRequests requests per ${timeUnit.toString().toLowerCase().removeSuffix("s")}.")
     }
diff --git a/src/main/java/io/javalin/plugin/json/JavalinJackson.kt b/src/main/java/io/javalin/plugin/json/JavalinJackson.kt
index df45ef26..c344af2f 100644
--- a/src/main/java/io/javalin/plugin/json/JavalinJackson.kt
+++ b/src/main/java/io/javalin/plugin/json/JavalinJackson.kt
@@ -14,6 +14,7 @@ import io.javalin.core.util.Util
 object JavalinJackson {
 
     private var objectMapper: ObjectMapper? = null
+    private val defaultObjectMapper: ObjectMapper by lazy { defaultObjectMapper() }
 
     @JvmStatic
     fun configure(staticObjectMapper: ObjectMapper) {
@@ -22,13 +23,12 @@ object JavalinJackson {
 
     @JvmStatic
     fun getObjectMapper(): ObjectMapper {
-        objectMapper = objectMapper ?: createObjectMapper()
-        return objectMapper!!
+        return (objectMapper ?: defaultObjectMapper)
     }
 
     fun toJson(`object`: Any): String {
         Util.ensureDependencyPresent(OptionalDependency.JACKSON)
-        return getObjectMapper().writeValueAsString(`object`)
+        return (objectMapper ?: defaultObjectMapper).writeValueAsString(`object`)
     }
 
     fun <T> fromJson(json: String, clazz: Class<T>): T {
@@ -36,10 +36,10 @@ object JavalinJackson {
         if (Util.isKotlinClass(clazz)) {
             Util.ensureDependencyPresent(OptionalDependency.JACKSON_KT)
         }
-        return getObjectMapper().readValue(json, clazz)
+        return (objectMapper ?: defaultObjectMapper).readValue(json, clazz)
     }
 
-    private fun createObjectMapper(): ObjectMapper = try {
+    private fun defaultObjectMapper(): ObjectMapper = try {
         val className = OptionalDependency.JACKSON_KT.testClass
         ObjectMapper().registerModule(Class.forName(className).getConstructor().newInstance() as Module)
     } catch (e: ClassNotFoundException) {
diff --git a/src/main/java/io/javalin/plugin/openapi/OpenApiOptions.kt b/src/main/java/io/javalin/plugin/openapi/OpenApiOptions.kt
index df98d3fa..4ecb3a07 100644
--- a/src/main/java/io/javalin/plugin/openapi/OpenApiOptions.kt
+++ b/src/main/java/io/javalin/plugin/openapi/OpenApiOptions.kt
@@ -16,6 +16,7 @@ import io.javalin.plugin.openapi.ui.ReDocOptions
 import io.javalin.plugin.openapi.ui.SwaggerOptions
 import io.javalin.plugin.openapi.utils.LazyDefaultValue
 import io.swagger.v3.oas.models.OpenAPI
+import io.swagger.v3.oas.models.examples.Example
 import io.swagger.v3.oas.models.info.Info
 
 class OpenApiOptions constructor(val initialConfigurationCreator: InitialConfigurationCreator) {
@@ -88,6 +89,16 @@ class OpenApiOptions constructor(val initialConfigurationCreator: InitialConfigu
         }
     }
 
+    fun examples(examples: Map<Class<*>, Map<String, Example>>) {
+        openApiExamples = examples.mapValues { it.value.toMutableMap() }.toMutableMap()
+    }
+
+    inline fun <reified T : Any> addExample(name: String, example: Example) = addExample(T::class.java, name, example)
+
+    fun <T> addExample(clazz: Class<T>, name: String, example: Example) {
+        openApiExamples.computeIfAbsent(clazz) { mutableMapOf() }[name] = example
+    }
+
     /**
      * Activate annotation scanning for specific package prefixes.
      * This will search for `OpenApi` annotations which define the `path` and `method` * property.
@@ -134,3 +145,5 @@ interface InitialConfigurationCreator {
 fun InitialConfigurationCreator(createInitialConfiguration: () -> OpenAPI) = object : InitialConfigurationCreator {
     override fun create() = createInitialConfiguration()
 }
+
+internal var openApiExamples = mutableMapOf<Class<*>, MutableMap<String, Example>>()
diff --git a/src/main/java/io/javalin/plugin/openapi/dsl/DocumentedContent.kt b/src/main/java/io/javalin/plugin/openapi/dsl/DocumentedContent.kt
index 7e6f41df..475606f6 100644
--- a/src/main/java/io/javalin/plugin/openapi/dsl/DocumentedContent.kt
+++ b/src/main/java/io/javalin/plugin/openapi/dsl/DocumentedContent.kt
@@ -7,6 +7,7 @@ import io.javalin.plugin.openapi.external.mediaTypeArrayOf
 import io.javalin.plugin.openapi.external.mediaTypeArrayOfRef
 import io.javalin.plugin.openapi.external.mediaTypeRef
 import io.javalin.plugin.openapi.external.schema
+import io.javalin.plugin.openapi.openApiExamples
 import io.swagger.v3.oas.models.Components
 import io.swagger.v3.oas.models.media.Content
 import io.swagger.v3.oas.models.media.MediaType
@@ -144,6 +145,9 @@ fun Content.applyDocumentedContent(documentedContent: DocumentedContent) {
             else -> mediaTypeRef(documentedContent.fromType, documentedContent.contentType)
         }
     }
+    openApiExamples[documentedContent.fromType]?.let { examples ->
+        get(documentedContent.contentType)?.examples = examples
+    }
 }
 
 fun Components.applyDocumentedContent(documentedResponse: DocumentedContent) {
diff --git a/src/main/java/io/javalin/plugin/openapi/dsl/DocumentedFormParameter.kt b/src/main/java/io/javalin/plugin/openapi/dsl/DocumentedFormParameter.kt
index a900d62a..78be8aec 100644
--- a/src/main/java/io/javalin/plugin/openapi/dsl/DocumentedFormParameter.kt
+++ b/src/main/java/io/javalin/plugin/openapi/dsl/DocumentedFormParameter.kt
@@ -31,15 +31,15 @@ fun RequestBody.applyDocumentedFormParameters(documentedFormParameters: List<Doc
     if (documentedFormParameters.isNotEmpty() || fileUploadList.isNotEmpty()) {
 
         val formParams = documentedFormParameters
-            .map { it.name to findSchema(it.clazz)?.main }
+                .map { it.name to findSchema(it.clazz)?.main }
         val fileParams = fileUploadList
-            .map { it.name to getFileSchema(it) }
+                .map { it.name to getFileSchema(it) }
 
         val schema = ObjectSchema().apply {
             properties = fileParams.union(formParams).toMap()
             required = documentedFormParameters
-                .filter { it.required }
-                .map { it.name }
+                    .filter { it.required }
+                    .map { it.name }
         }
 
         // Requests with file uploads need to be a multipart content
diff --git a/src/main/java/io/javalin/plugin/openapi/dsl/OpenApiDocumentation.kt b/src/main/java/io/javalin/plugin/openapi/dsl/OpenApiDocumentation.kt
index 940ba2af..932aabc1 100644
--- a/src/main/java/io/javalin/plugin/openapi/dsl/OpenApiDocumentation.kt
+++ b/src/main/java/io/javalin/plugin/openapi/dsl/OpenApiDocumentation.kt
@@ -142,7 +142,7 @@ class OpenApiDocumentation {
 
     @JvmOverloads
     fun uploadedFiles(name: String, openApiUpdater: OpenApiUpdater<RequestBody>? = null) = apply {
-        fileUploadList.add(DocumentedFileUpload(name,true))
+        fileUploadList.add(DocumentedFileUpload(name, true))
         requestBodyList.addIfNotNull(openApiUpdater)
     }
 
diff --git a/src/main/java/io/javalin/plugin/openapi/dsl/openApiBuilderDsl.kt b/src/main/java/io/javalin/plugin/openapi/dsl/openApiBuilderDsl.kt
index 2fb89e40..d4231ecd 100644
--- a/src/main/java/io/javalin/plugin/openapi/dsl/openApiBuilderDsl.kt
+++ b/src/main/java/io/javalin/plugin/openapi/dsl/openApiBuilderDsl.kt
@@ -10,7 +10,6 @@ import io.javalin.core.event.HandlerMetaInfo
 import io.javalin.http.HandlerType
 import io.javalin.plugin.openapi.CreateSchemaOptions
 import io.javalin.plugin.openapi.JavalinOpenApi
-import io.javalin.plugin.openapi.annotations.ContentType
 import io.javalin.plugin.openapi.annotations.HttpMethod
 import io.javalin.plugin.openapi.annotations.PathInfo
 import io.javalin.plugin.openapi.external.schema
diff --git a/src/main/java/io/javalin/plugin/openapi/external/KotlinOpenApiDsl.kt b/src/main/java/io/javalin/plugin/openapi/external/KotlinOpenApiDsl.kt
index 54ab7910..5d82ae09 100644
--- a/src/main/java/io/javalin/plugin/openapi/external/KotlinOpenApiDsl.kt
+++ b/src/main/java/io/javalin/plugin/openapi/external/KotlinOpenApiDsl.kt
@@ -11,9 +11,9 @@ import io.swagger.v3.oas.models.media.DateTimeSchema
 import io.swagger.v3.oas.models.media.IntegerSchema
 import io.swagger.v3.oas.models.media.MapSchema
 import io.swagger.v3.oas.models.media.MediaType
+import io.swagger.v3.oas.models.media.NumberSchema
 import io.swagger.v3.oas.models.media.Schema
 import io.swagger.v3.oas.models.media.StringSchema
-import io.swagger.v3.oas.models.media.NumberSchema
 import io.swagger.v3.oas.models.parameters.Parameter
 import java.math.BigDecimal
 import java.time.LocalDate
diff --git a/src/main/java/io/javalin/plugin/rendering/markdown/Commonmark.kt b/src/main/java/io/javalin/plugin/rendering/markdown/Commonmark.kt
index dd40ea14..7f473b93 100644
--- a/src/main/java/io/javalin/plugin/rendering/markdown/Commonmark.kt
+++ b/src/main/java/io/javalin/plugin/rendering/markdown/Commonmark.kt
@@ -16,7 +16,14 @@ import org.commonmark.renderer.html.HtmlRenderer
 object JavalinCommonmark : FileRenderer {
 
     private var renderer: HtmlRenderer? = null
+    private val defaultRenderer: HtmlRenderer by lazy {
+        HtmlRenderer.builder().build()
+    }
+
     private var parser: Parser? = null
+    private val defaultParser: Parser by lazy {
+        Parser.builder().build()
+    }
 
     @JvmStatic
     fun configure(staticHtmlRenderer: HtmlRenderer, staticMarkdownParser: Parser) {
@@ -26,10 +33,8 @@ object JavalinCommonmark : FileRenderer {
 
     override fun render(filePath: String, model: Map<String, Any?>, ctx: Context): String {
         Util.ensureDependencyPresent(OptionalDependency.COMMONMARK)
-        renderer = renderer ?: HtmlRenderer.builder().build()
-        parser = parser ?: Parser.builder().build()
         val fileContent = JavalinCommonmark::class.java.getResource(filePath).readText()
-        return renderer!!.render(parser!!.parse(fileContent))
+        return (renderer ?: defaultRenderer).render((parser ?: defaultParser).parse(fileContent))
     }
 
 }
diff --git a/src/main/java/io/javalin/plugin/rendering/template/JavalinFreemarker.kt b/src/main/java/io/javalin/plugin/rendering/template/JavalinFreemarker.kt
index c2e794fe..3ab1303a 100644
--- a/src/main/java/io/javalin/plugin/rendering/template/JavalinFreemarker.kt
+++ b/src/main/java/io/javalin/plugin/rendering/template/JavalinFreemarker.kt
@@ -17,6 +17,7 @@ import java.io.StringWriter
 object JavalinFreemarker : FileRenderer {
 
     private var configuration: Configuration? = null
+    private val defaultConfiguration: Configuration by lazy { defaultFreemarkerEngine() }
 
     @JvmStatic
     fun configure(staticConfiguration: Configuration) {
@@ -25,9 +26,8 @@ object JavalinFreemarker : FileRenderer {
 
     override fun render(filePath: String, model: Map<String, Any?>, ctx: Context): String {
         Util.ensureDependencyPresent(OptionalDependency.FREEMARKER)
-        configuration = configuration ?: defaultFreemarkerEngine()
         val stringWriter = StringWriter()
-        configuration!!.getTemplate(filePath).process(model, stringWriter)
+        (configuration ?: defaultConfiguration).getTemplate(filePath).process(model, stringWriter)
         return stringWriter.toString()
     }
 
diff --git a/src/main/java/io/javalin/plugin/rendering/template/JavalinJtwig.kt b/src/main/java/io/javalin/plugin/rendering/template/JavalinJtwig.kt
index 2a497f86..8d9bf1ab 100644
--- a/src/main/java/io/javalin/plugin/rendering/template/JavalinJtwig.kt
+++ b/src/main/java/io/javalin/plugin/rendering/template/JavalinJtwig.kt
@@ -18,6 +18,7 @@ import org.jtwig.environment.EnvironmentConfiguration
 object JavalinJtwig : FileRenderer {
 
     private var configuration: EnvironmentConfiguration? = null
+    private val defaultConfiguration: EnvironmentConfiguration by lazy { DefaultEnvironmentConfiguration() }
 
     @JvmStatic
     fun configure(staticConfiguration: EnvironmentConfiguration) {
@@ -26,8 +27,7 @@ object JavalinJtwig : FileRenderer {
 
     override fun render(filePath: String, model: Map<String, Any?>, ctx: Context): String {
         Util.ensureDependencyPresent(OptionalDependency.JTWIG)
-        val configuration = configuration ?: DefaultEnvironmentConfiguration()
-        val template = JtwigTemplate.classpathTemplate(filePath, configuration)
+        val template = JtwigTemplate.classpathTemplate(filePath, configuration ?: defaultConfiguration)
         return template.render(JtwigModel.newModel(model))
     }
 
diff --git a/src/main/java/io/javalin/plugin/rendering/template/JavalinMustache.kt b/src/main/java/io/javalin/plugin/rendering/template/JavalinMustache.kt
index a89c3821..7a40cde7 100644
--- a/src/main/java/io/javalin/plugin/rendering/template/JavalinMustache.kt
+++ b/src/main/java/io/javalin/plugin/rendering/template/JavalinMustache.kt
@@ -17,6 +17,7 @@ import java.io.StringWriter
 object JavalinMustache : FileRenderer {
 
     private var mustacheFactory: MustacheFactory? = null
+    private val defaultMustacheFactory: MustacheFactory by lazy { DefaultMustacheFactory("./") }
 
     @JvmStatic
     fun configure(staticMustacheFactory: MustacheFactory) {
@@ -25,9 +26,8 @@ object JavalinMustache : FileRenderer {
 
     override fun render(filePath: String, model: Map<String, Any?>, ctx: Context): String {
         Util.ensureDependencyPresent(OptionalDependency.MUSTACHE)
-        mustacheFactory = mustacheFactory ?: DefaultMustacheFactory("./")
         val stringWriter = StringWriter()
-        mustacheFactory!!.compile(filePath).execute(stringWriter, model).close()
+        (mustacheFactory ?: defaultMustacheFactory).compile(filePath).execute(stringWriter, model).close()
         return stringWriter.toString()
     }
 
diff --git a/src/main/java/io/javalin/plugin/rendering/template/JavalinPebble.kt b/src/main/java/io/javalin/plugin/rendering/template/JavalinPebble.kt
index 1741d899..0b2e90c9 100644
--- a/src/main/java/io/javalin/plugin/rendering/template/JavalinPebble.kt
+++ b/src/main/java/io/javalin/plugin/rendering/template/JavalinPebble.kt
@@ -17,6 +17,7 @@ import java.io.StringWriter
 object JavalinPebble : FileRenderer {
 
     private var pebbleEngine: PebbleEngine? = null
+    private val defaultPebbleEngine: PebbleEngine by lazy { defaultPebbleEngine() }
 
     @JvmStatic
     fun configure(staticPebbleEngine: PebbleEngine) {
@@ -25,8 +26,7 @@ object JavalinPebble : FileRenderer {
 
     override fun render(filePath: String, model: Map<String, Any?>, ctx: Context): String {
         Util.ensureDependencyPresent(OptionalDependency.PEBBLE)
-        pebbleEngine = pebbleEngine ?: defaultPebbleEngine()
-        val compiledTemplate = pebbleEngine!!.getTemplate(filePath)
+        val compiledTemplate = (pebbleEngine ?: defaultPebbleEngine).getTemplate(filePath)
         val stringWriter = StringWriter()
         compiledTemplate.evaluate(stringWriter, model)
         return stringWriter.toString()
diff --git a/src/main/java/io/javalin/plugin/rendering/template/JavalinThymeleaf.kt b/src/main/java/io/javalin/plugin/rendering/template/JavalinThymeleaf.kt
index b49833b7..ad2c6370 100644
--- a/src/main/java/io/javalin/plugin/rendering/template/JavalinThymeleaf.kt
+++ b/src/main/java/io/javalin/plugin/rendering/template/JavalinThymeleaf.kt
@@ -18,6 +18,7 @@ import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
 object JavalinThymeleaf : FileRenderer {
 
     private var templateEngine: TemplateEngine? = null
+    private val defaultTemplateEngine: TemplateEngine by lazy { defaultThymeLeafEngine() }
 
     @JvmStatic
     fun configure(staticTemplateEngine: TemplateEngine) {
@@ -26,10 +27,9 @@ object JavalinThymeleaf : FileRenderer {
 
     override fun render(filePath: String, model: Map<String, Any?>, ctx: Context): String {
         Util.ensureDependencyPresent(OptionalDependency.THYMELEAF)
-        templateEngine = templateEngine ?: defaultThymeLeafEngine()
         val context = WebContext(ctx.req, ctx.res, ctx.req.servletContext)
         context.setVariables(model)
-        return templateEngine!!.process(filePath, context)
+        return (templateEngine ?: defaultTemplateEngine).process(filePath, context)
     }
 
     private fun defaultThymeLeafEngine() = TemplateEngine().apply {
diff --git a/src/main/java/io/javalin/plugin/rendering/template/JavalinVelocity.kt b/src/main/java/io/javalin/plugin/rendering/template/JavalinVelocity.kt
index b2d02b9a..4fe5eed1 100644
--- a/src/main/java/io/javalin/plugin/rendering/template/JavalinVelocity.kt
+++ b/src/main/java/io/javalin/plugin/rendering/template/JavalinVelocity.kt
@@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets
 object JavalinVelocity : FileRenderer {
 
     private var velocityEngine: VelocityEngine? = null
+    private val defaultVelocityEngine: VelocityEngine by lazy { defaultVelocityEngine() }
 
     @JvmStatic
     fun configure(staticVelocityEngine: VelocityEngine) {
@@ -26,9 +27,8 @@ object JavalinVelocity : FileRenderer {
 
     override fun render(filePath: String, model: Map<String, Any?>, ctx: Context): String {
         Util.ensureDependencyPresent(OptionalDependency.VELOCITY)
-        velocityEngine = velocityEngine ?: defaultVelocityEngine()
         val stringWriter = StringWriter()
-        velocityEngine!!.getTemplate(filePath, StandardCharsets.UTF_8.name()).merge(
+        (velocityEngine ?: defaultVelocityEngine).getTemplate(filePath, StandardCharsets.UTF_8.name()).merge(
                 VelocityContext(model.toMutableMap()), stringWriter
         )
         return stringWriter.toString()


Test patch:
diff --git a/src/test/java/io/javalin/openapi/TestOpenApiExample.kt b/src/test/java/io/javalin/openapi/TestOpenApiExample.kt
new file mode 100644
index 00000000..7ca045e5
--- /dev/null
+++ b/src/test/java/io/javalin/openapi/TestOpenApiExample.kt
@@ -0,0 +1,177 @@
+package io.javalin.openapi
+
+import io.javalin.Javalin
+import io.javalin.plugin.openapi.JavalinOpenApi
+import io.javalin.plugin.openapi.OpenApiOptions
+import io.javalin.plugin.openapi.OpenApiPlugin
+import io.javalin.plugin.openapi.dsl.document
+import io.javalin.plugin.openapi.dsl.documented
+import io.javalin.plugin.openapi.openApiExamples
+import io.swagger.v3.oas.models.examples.Example
+import io.swagger.v3.oas.models.info.Info
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+private data class ExampleUser(val name: String, val address: ExampleAddress)
+private data class ExampleAddress(val street: String, val number: Int)
+
+val examples = mapOf(
+        ExampleUser::class.java to mapOf(
+                "User example" to Example().apply {
+                    summary = "A correctly configured user"
+                    value = ExampleUser("John", ExampleAddress("Some street", 123))
+                },
+                "User example 2" to Example().apply {
+                    summary = "Another correctly configured user"
+                    value = ExampleUser("Dave", ExampleAddress("Some street", 123))
+                }
+        ),
+        ExampleAddress::class.java to mapOf(
+                "Address example" to Example().apply {
+                    summary = "A correctly configured address"
+                    value = ExampleAddress("Some street", 123)
+                }
+        )
+)
+
+val expectedJson = """
+    {
+      "openapi" : "3.0.1",
+      "info" : { },
+      "paths" : {
+        "/user" : {
+          "get" : {
+            "summary" : "Get user",
+            "operationId" : "getUser",
+            "responses" : {
+              "200" : {
+                "description" : "OK",
+                "content" : {
+                  "application/json" : {
+                    "schema" : {
+                      "$ref" : "#/components/schemas/ExampleUser"
+                    },
+                    "examples" : {
+                      "User example" : {
+                        "summary" : "A correctly configured user",
+                        "value" : {
+                          "name" : "John",
+                          "address" : {
+                            "street" : "Some street",
+                            "number" : 123
+                          }
+                        }
+                      },
+                      "User example 2" : {
+                        "summary" : "Another correctly configured user",
+                        "value" : {
+                          "name" : "Dave",
+                          "address" : {
+                            "street" : "Some street",
+                            "number" : 123
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "/address" : {
+          "get" : {
+            "summary" : "Get address",
+            "operationId" : "getAddress",
+            "responses" : {
+              "200" : {
+                "description" : "OK",
+                "content" : {
+                  "application/json" : {
+                    "schema" : {
+                      "$ref" : "#/components/schemas/ExampleAddress"
+                    },
+                    "examples" : {
+                      "Address example" : {
+                        "summary" : "A correctly configured address",
+                        "value" : {
+                          "street" : "Some street",
+                          "number" : 123
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      },
+      "components" : {
+        "schemas" : {
+          "ExampleAddress" : {
+            "required" : [ "number", "street" ],
+            "type" : "object",
+            "properties" : {
+              "street" : {
+                "type" : "string"
+              },
+              "number" : {
+                "type" : "integer",
+                "format" : "int32"
+              }
+            }
+          },
+          "ExampleUser" : {
+            "required" : [ "address", "name" ],
+            "type" : "object",
+            "properties" : {
+              "name" : {
+                "type" : "string"
+              },
+              "address" : {
+                "$ref" : "#/components/schemas/ExampleAddress"
+              }
+            }
+          }
+        }
+      }
+    }
+""".trimIndent()
+
+class TestOpenApiExample {
+
+    private fun createApp(openApiPlugin: OpenApiPlugin) = Javalin.create {
+        it.registerPlugin(openApiPlugin)
+    }.apply {
+        get("/user", documented(document().result<ExampleUser>("200")) {})
+        get("/address", documented(document().result<ExampleAddress>("200")) {})
+    }
+
+    @Test
+    fun `examples are generated when added in addExample, and class can have multiple examples`() {
+        val app = createApp(OpenApiPlugin(OpenApiOptions(Info()).apply {
+            addExample<ExampleUser>("User example", examples[ExampleUser::class.java]!!["User example"]!!)
+            addExample<ExampleUser>("User example 2", examples[ExampleUser::class.java]!!["User example 2"]!!)
+            addExample<ExampleAddress>("Address example", examples[ExampleAddress::class.java]!!["Address example"]!!)
+        }))
+
+        val openApiJson = JavalinOpenApi.createSchema(app).asJsonString()
+
+        assertThat(openApiJson).isEqualTo(expectedJson.formatJson())
+        openApiExamples.clear()
+    }
+
+    @Test
+    fun `examples are generated when added as a map`() {
+        val app = createApp(OpenApiPlugin(OpenApiOptions(Info()).apply {
+            examples(examples)
+        }))
+
+        val openApiJson = JavalinOpenApi.createSchema(app).asJsonString()
+
+        assertThat(openApiJson).isEqualTo(expectedJson.formatJson())
+        openApiExamples.clear()
+    }
+
+}