diff --git a/examples/online-boutique/services/ad-kotlin/pom.xml b/examples/online-boutique/services/ad-kotlin/pom.xml
new file mode 100644
index 0000000000..ba71348f13
--- /dev/null
+++ b/examples/online-boutique/services/ad-kotlin/pom.xml
@@ -0,0 +1,177 @@
+
+
+ 4.0.0
+ xyz.block
+ ad-kotlin
+ 1.0-SNAPSHOT
+
+
+ 1.0-SNAPSHOT
+ 11
+ 1.9.0
+ true
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${kotlin.version}
+
+
+ ${project.groupId}
+ ftl-runtime
+ ${ftl.version}
+
+
+ ${project.groupId}
+ ftl-generator
+ ${ftl.version}
+
+
+
+
+
+
+ kotlin-maven-plugin
+ org.jetbrains.kotlin
+ ${kotlin.version}
+
+
+ compile
+
+ compile
+
+
+
+ ${project.basedir}/src/main/kotlin
+
+
+
+
+ test-compile
+
+ test-compile
+
+
+
+ ${project.basedir}/src/test/kotlin
+
+
+
+
+
+
+ ksp
+
+
+
+
+
+
+
+ com.dyescape
+ kotlin-maven-symbol-processing
+ 1.6
+
+
+ ${project.groupId}
+ ftl-runtime
+ ${ftl.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.2.0
+
+
+ initialize
+
+ copy
+
+
+
+
+ xyz.block
+ ftl-generator
+ ${ftl.version}
+ jar-with-dependencies
+ ftl-generator.jar
+
+
+
+
+
+ copy-dependencies
+ compile
+
+ copy-dependencies
+
+
+ ${project.build.directory}/dependency
+ runtime
+
+
+
+ build-classpath
+ compile
+
+ build-classpath
+
+
+ ${project.build.directory}/classpath.txt
+ dependency
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.0.0
+
+
+ initialize
+
+ exec
+
+
+ java
+
+ -jar
+ target/dependency/ftl-generator.jar
+ --endpoint=http://127.0.0.1:8892
+ --dest=${project.build.directory}
+ --module=ad
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.2.0
+
+
+ generate-sources
+
+ add-source
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/online-boutique/services/ad-kotlin/src/main/kotlin/ftl/ad/Ad.kt b/examples/online-boutique/services/ad-kotlin/src/main/kotlin/ftl/ad/Ad.kt
new file mode 100644
index 0000000000..ea57769fc0
--- /dev/null
+++ b/examples/online-boutique/services/ad-kotlin/src/main/kotlin/ftl/ad/Ad.kt
@@ -0,0 +1,78 @@
+package ftl.ad
+
+import com.google.gson.Gson
+import com.google.gson.annotations.SerializedName
+import com.google.gson.reflect.TypeToken
+import xyz.block.ftl.Context
+import xyz.block.ftl.Ingress
+import xyz.block.ftl.Method
+import xyz.block.ftl.Verb
+import xyz.block.ftl.serializer.makeGson
+import java.util.*
+
+data class Ad(@SerializedName("RedirectURL") val redirectUrl: String, @SerializedName("Text") val text: String)
+data class AdRequest(val contextKeys: List)
+data class AdResponse(val ads: List)
+
+class AdModule {
+ private val database: Map = loadDatabase()
+
+ @Throws(Exception::class)
+ @Verb
+ @Ingress(Method.GET, "/get")
+ fun get(context: Context, req: AdRequest): AdResponse {
+ return when {
+ req.contextKeys.isNotEmpty() -> AdResponse(ads = contextualAds(req.contextKeys))
+ else -> AdResponse(ads = randomAds())
+ }
+ }
+
+ private fun contextualAds(contextKeys: List): List {
+ return contextKeys.map { database[it] ?: throw Exception("no ad registered for this context key") }
+ }
+
+ private fun randomAds(): List {
+ val ads = mutableListOf()
+ val random = Random()
+ repeat(MAX_ADS_TO_SERVE) {
+ ads.add(database.entries.elementAt(random.nextInt(database.size)).value)
+ }
+ return ads
+ }
+
+ companion object {
+ private const val MAX_ADS_TO_SERVE = 2
+ private const val DATABASE_JSON = "{\n" +
+ " \"hair\": {\n" +
+ " \"RedirectURL\": \"/product/2ZYFJ3GM2N\",\n" +
+ " \"Text\": \"Hairdryer for sale. 50% off.\"\n" +
+ " },\n" +
+ " \"clothing\": {\n" +
+ " \"RedirectURL\": \"/product/66VCHSJNUP\",\n" +
+ " \"Text\": \"Tank top for sale. 20% off.\"\n" +
+ " },\n" +
+ " \"accessories\": {\n" +
+ " \"RedirectURL\": \"/product/1YMWWN1N4O\",\n" +
+ " \"Text\": \"Watch for sale. Buy one, get second kit for free\"\n" +
+ " },\n" +
+ " \"footwear\": {\n" +
+ " \"RedirectURL\": \"/product/L9ECAV7KIM\",\n" +
+ " \"Text\": \"Loafers for sale. Buy one, get second one for free\"\n" +
+ " },\n" +
+ " \"decor\": {\n" +
+ " \"RedirectURL\": \"/product/0PUK6V6EV0\",\n" +
+ " \"Text\": \"Candle holder for sale. 30% off.\"\n" +
+ " },\n" +
+ " \"kitchen\": {\n" +
+ " \"RedirectURL\": \"/product/9SIQT8TOJO\",\n" +
+ " \"Text\": \"Bamboo glass jar for sale. 10% off.\"\n" +
+ " }\n" +
+ "}"
+
+ private fun loadDatabase(): Map {
+ return makeGson().fromJson