io.quarkus
quarkus-resteasy-reactive-links-deployment
diff --git a/extensions/resteasy-reactive/pom.xml b/extensions/resteasy-reactive/pom.xml
index 12bf32ee7ecab..36e776edae515 100644
--- a/extensions/resteasy-reactive/pom.xml
+++ b/extensions/resteasy-reactive/pom.xml
@@ -25,8 +25,11 @@
jaxrs-client-reactive
rest-client-reactive
rest-client-reactive-jackson
+ rest-client-reactive-kotlin-serialization
quarkus-resteasy-reactive-links
quarkus-resteasy-reactive-kotlin
+ quarkus-resteasy-reactive-kotlin-serialization-common
+ quarkus-resteasy-reactive-kotlin-serialization
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/deployment/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/deployment/pom.xml
new file mode 100644
index 0000000000000..163d021e52c7f
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/deployment/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-kotlin-serialization-common-parent
+ 999-SNAPSHOT
+
+
+ quarkus-resteasy-reactive-kotlin-serialization-common-deployment
+ Quarkus - RESTEasy Reactive - Kotlin Serialization Common Deployment
+
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-kotlin-serialization-common
+
+
+ io.quarkus
+ quarkus-kotlin-deployment
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/pom.xml
new file mode 100644
index 0000000000000..7f595183dd590
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/pom.xml
@@ -0,0 +1,20 @@
+
+
+
+ quarkus-resteasy-reactive-parent-aggregator
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ quarkus-resteasy-reactive-kotlin-serialization-common-parent
+ Quarkus - RESTEasy Reactive - Kotlin Serialization Common Parent
+ pom
+
+ deployment
+ runtime
+
+
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/pom.xml
new file mode 100644
index 0000000000000..c0ebec3f5cf6f
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/pom.xml
@@ -0,0 +1,111 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-kotlin-serialization-common-parent
+ 999-SNAPSHOT
+
+
+ quarkus-resteasy-reactive-kotlin-serialization-common
+ Quarkus - RESTEasy Reactive - Kotlin Serialization Common
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+
+ compile
+
+
+
+ src/main/kotlin
+ src/main/java
+
+
+
+
+ test-compile
+
+ test-compile
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ default-compile
+ none
+
+
+
+ default-testCompile
+ none
+
+
+ java-compile
+ compile
+
+ compile
+
+
+
+ java-test-compile
+ test-compile
+
+ testCompile
+
+
+
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ javadoc
+ none
+
+
+
+
+
+
+
+
+ io.quarkus
+ quarkus-kotlin
+
+
+ org.jetbrains.kotlinx
+ kotlinx-serialization-json
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+
+
\ No newline at end of file
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/kotlin/serialization/JsonConfig.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/kotlin/serialization/JsonConfig.java
new file mode 100644
index 0000000000000..5895e0e6eae9a
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/kotlin/serialization/JsonConfig.java
@@ -0,0 +1,119 @@
+package io.quarkus.kotlin.serialization;
+
+import java.util.StringJoiner;
+
+import io.quarkus.runtime.annotations.ConfigGroup;
+import io.quarkus.runtime.annotations.ConfigItem;
+
+@ConfigGroup
+public class JsonConfig {
+ /**
+ * Removes JSON specification restriction on
+ * special floating-point values such as `NaN` and `Infinity` and enables their serialization and deserialization.
+ * When enabling it, please ensure that the receiving party will be able to encode and decode these special values.
+ */
+ @ConfigItem(defaultValue = "false")
+ public boolean allowSpecialFloatingPointValues = false;
+
+ /**
+ * Enables structured objects to be serialized as map keys by
+ * changing serialized form of the map from JSON object (key-value pairs) to flat array like `[k1, v1, k2, v2]`.
+ */
+ @ConfigItem(defaultValue = "false")
+ public boolean allowStructuredMapKeys = false;
+
+ /**
+ * Name of the class descriptor property for polymorphic serialization.
+ */
+ @ConfigItem(defaultValue = "type")
+ public String classDiscriminator = "type";
+
+ /**
+ * Enables coercing incorrect JSON values to the default property value in the following cases:
+ * 1. JSON value is `null` but property type is non-nullable.
+ * 2. Property type is an enum type, but JSON value contains unknown enum member.
+ */
+ @ConfigItem(defaultValue = "false")
+ public boolean coerceInputValues = false;
+
+ /**
+ * Specifies whether default values of Kotlin properties should be encoded.
+ */
+ @ConfigItem(defaultValue = "true")
+ public boolean encodeDefaults = true;
+
+ /**
+ * Specifies whether `null` values should be encoded for nullable properties and must be present in JSON object
+ * during decoding.
+ *
+ * When this flag is disabled properties with `null` values without default are not encoded;
+ * during decoding, the absence of a field value is treated as `null` for nullable properties without a default value.
+ *
+ * {@code true} by default.
+ */
+ @ConfigItem(defaultValue = "true")
+ public boolean explicitNulls = true;
+
+ /**
+ * Specifies whether encounters of unknown properties in the input JSON
+ * should be ignored instead of throwing [SerializationException].
+ */
+ @ConfigItem(defaultValue = "false")
+ public boolean ignoreUnknownKeys = false;
+
+ /**
+ * Removes JSON specification restriction (RFC-4627) and makes parser
+ * more liberal to the malformed input. In lenient mode quoted boolean literals,
+ * and unquoted string literals are allowed.
+ *
+ * Its relaxations can be expanded in the future, so that lenient parser becomes even more
+ * permissive to invalid value in the input, replacing them with defaults.
+ */
+ @ConfigItem(defaultValue = "false")
+ public boolean isLenient = false;
+
+ /**
+ * Specifies whether resulting JSON should be pretty-printed.
+ */
+ @ConfigItem(defaultValue = "false")
+ public boolean prettyPrint = false;
+
+ /**
+ * Specifies indent string to use with [prettyPrint] mode
+ */
+ @ConfigItem(defaultValue = " ")
+ public String prettyPrintIndent = " ";
+
+ /**
+ * Specifies whether Json instance makes use of [JsonNames] annotation.
+ *
+ * Disabling this flag when one does not use [JsonNames] at all may sometimes result in better performance,
+ * particularly when a large count of fields is skipped with [ignoreUnknownKeys].
+ */
+ @ConfigItem(defaultValue = "true")
+ public boolean useAlternativeNames = true;
+
+ /**
+ * Switches polymorphic serialization to the default array format.
+ * This is an option for legacy JSON format and should not be generally used.
+ */
+ @ConfigItem(defaultValue = "false")
+ public boolean useArrayPolymorphism = false;
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", JsonConfig.class.getSimpleName() + "[", "]")
+ .add("encodeDefaults=" + encodeDefaults)
+ .add("ignoreUnknownKeys=" + ignoreUnknownKeys)
+ .add("isLenient=" + isLenient)
+ .add("allowStructuredMapKeys=" + allowStructuredMapKeys)
+ .add("prettyPrint=" + prettyPrint)
+ .add("prettyPrintIndent='" + prettyPrintIndent + "'")
+ .add("coerceInputValues=" + coerceInputValues)
+ .add("useArrayPolymorphism=" + useArrayPolymorphism)
+ .add("classDiscriminator='" + classDiscriminator + "'")
+ .add("allowSpecialFloatingPointValues=" + allowSpecialFloatingPointValues)
+ .add("useAlternativeNames=" + useAlternativeNames)
+ .toString();
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/kotlin/serialization/KotlinSerializationConfig.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/kotlin/serialization/KotlinSerializationConfig.java
new file mode 100644
index 0000000000000..72dae1eb4e310
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/kotlin/serialization/KotlinSerializationConfig.java
@@ -0,0 +1,25 @@
+package io.quarkus.kotlin.serialization;
+
+import java.util.StringJoiner;
+
+import io.quarkus.runtime.annotations.ConfigItem;
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+
+@ConfigRoot(name = KotlinSerializationConfig.CONFIG_NAME, phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
+public class KotlinSerializationConfig {
+ public static final String CONFIG_NAME = "kotlin-serialization";
+
+ /**
+ * Configuration element for serializing to json
+ */
+ @ConfigItem(name = "json")
+ public JsonConfig json = new JsonConfig();
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", KotlinSerializationConfig.class.getSimpleName() + "[", "]")
+ .add("json=" + json)
+ .toString();
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/kotlin/io/quarkus/kotlin/serialization/KotlinSerializerRecorder.kt b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/kotlin/io/quarkus/kotlin/serialization/KotlinSerializerRecorder.kt
new file mode 100644
index 0000000000000..26038b0002f4c
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/kotlin/io/quarkus/kotlin/serialization/KotlinSerializerRecorder.kt
@@ -0,0 +1,36 @@
+package io.quarkus.kotlin.serialization
+
+import io.quarkus.runtime.annotations.Recorder
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.EmptySerializersModule
+import java.util.function.Supplier
+
+@Recorder
+@ExperimentalSerializationApi
+open class KotlinSerializerRecorder {
+ open fun configFactory(configuration: KotlinSerializationConfig) = JsonSupplier(configuration)
+}
+
+@ExperimentalSerializationApi
+open class JsonSupplier(open var configuration: KotlinSerializationConfig) : Supplier {
+ @Suppress("unused")
+ constructor() : this(KotlinSerializationConfig())
+
+ override fun get(): Json {
+ return Json {
+ allowSpecialFloatingPointValues = configuration.json.allowSpecialFloatingPointValues
+ allowStructuredMapKeys = configuration.json.allowStructuredMapKeys
+ classDiscriminator = configuration.json.classDiscriminator
+ coerceInputValues = configuration.json.coerceInputValues
+ encodeDefaults = configuration.json.encodeDefaults
+ explicitNulls = configuration.json.explicitNulls
+ ignoreUnknownKeys = configuration.json.ignoreUnknownKeys
+ isLenient = configuration.json.isLenient
+ prettyPrint = configuration.json.prettyPrint
+ prettyPrintIndent = configuration.json.prettyPrintIndent
+ useAlternativeNames = configuration.json.useAlternativeNames
+ useArrayPolymorphism = configuration.json.useArrayPolymorphism
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/test/java/io/quarkus/kotlin/serialization/JsonConfigTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/test/java/io/quarkus/kotlin/serialization/JsonConfigTest.java
new file mode 100644
index 0000000000000..0fa11adb53cd9
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/test/java/io/quarkus/kotlin/serialization/JsonConfigTest.java
@@ -0,0 +1,26 @@
+package io.quarkus.kotlin.serialization;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import kotlinx.serialization.json.JsonConfiguration;
+
+public class JsonConfigTest {
+ @Test
+ public void ensureJsonCoverage() {
+ Set kotlinFields = new TreeSet<>(Arrays.stream(JsonConfiguration.class.getDeclaredFields())
+ .map(f -> f.getName())
+ .collect(Collectors.toSet()));
+ kotlinFields.removeAll(Arrays.stream(JsonConfig.class.getDeclaredFields())
+ .map(f -> f.getName())
+ .collect(Collectors.toSet()));
+ assertTrue(kotlinFields.isEmpty(), "Should find all the Kotlin fields on the quarkus config object. " +
+ "missing elements: " + kotlinFields);
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/deployment/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/deployment/pom.xml
new file mode 100644
index 0000000000000..39ffdaafac328
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/deployment/pom.xml
@@ -0,0 +1,55 @@
+
+
+ 4.0.0
+
+ quarkus-resteasy-reactive-kotlin-serialization-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+
+ quarkus-resteasy-reactive-kotlin-serialization-deployment
+ Quarkus - RestEasy Reactive Kotlin Serialization - Deployment
+
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-kotlin-serialization-common-deployment
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-deployment
+
+
+ io.quarkus
+ quarkus-arc-deployment
+
+
+ io.quarkus
+ quarkus-kotlin-deployment
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-kotlin-serialization
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/deployment/src/main/java/io/quarkus/kotlin/serialization/deployment/KotlinSerializationProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/deployment/src/main/java/io/quarkus/kotlin/serialization/deployment/KotlinSerializationProcessor.java
new file mode 100644
index 0000000000000..24b8d37e71bb3
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/deployment/src/main/java/io/quarkus/kotlin/serialization/deployment/KotlinSerializationProcessor.java
@@ -0,0 +1,64 @@
+package io.quarkus.kotlin.serialization.deployment;
+
+import static io.quarkus.deployment.Feature.RESTEASY_REACTIVE_KOTLIN_SERIALIZATION;
+import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
+import static io.quarkus.resteasy.reactive.common.deployment.ServerDefaultProducesHandlerBuildItem.json;
+
+import java.util.List;
+
+import javax.inject.Singleton;
+import javax.ws.rs.core.MediaType;
+
+import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
+import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.kotlin.serialization.KotlinSerializationConfig;
+import io.quarkus.kotlin.serialization.KotlinSerializationMessageBodyReader;
+import io.quarkus.kotlin.serialization.KotlinSerializationMessageBodyWriter;
+import io.quarkus.kotlin.serialization.KotlinSerializerRecorder;
+import io.quarkus.resteasy.reactive.common.deployment.ServerDefaultProducesHandlerBuildItem;
+import io.quarkus.resteasy.reactive.spi.MessageBodyReaderBuildItem;
+import io.quarkus.resteasy.reactive.spi.MessageBodyWriterBuildItem;
+import kotlinx.serialization.json.Json;
+
+public class KotlinSerializationProcessor {
+ @BuildStep
+ public void additionalProviders(
+ BuildProducer additionalBean,
+ BuildProducer additionalReaders,
+ BuildProducer additionalWriters) {
+ additionalBean.produce(AdditionalBeanBuildItem.builder()
+ .addBeanClass(KotlinSerializationMessageBodyReader.class.getName())
+ .addBeanClass(KotlinSerializationMessageBodyWriter.class.getName())
+ .setUnremovable().build());
+ additionalReaders.produce(new MessageBodyReaderBuildItem(
+ KotlinSerializationMessageBodyReader.class.getName(), Object.class.getName(), List.of(
+ MediaType.APPLICATION_JSON)));
+ additionalWriters.produce(new MessageBodyWriterBuildItem(
+ KotlinSerializationMessageBodyWriter.class.getName(), Object.class.getName(), List.of(
+ MediaType.APPLICATION_JSON)));
+ }
+
+ @BuildStep
+ @Record(STATIC_INIT)
+ public SyntheticBeanBuildItem createJson(KotlinSerializerRecorder recorder, KotlinSerializationConfig config) {
+ return SyntheticBeanBuildItem
+ .configure(Json.class)
+ .scope(Singleton.class)
+ .supplier(recorder.configFactory(config))
+ .unremovable().done();
+ }
+
+ @BuildStep
+ public void feature(BuildProducer feature) {
+ feature.produce(new FeatureBuildItem(RESTEASY_REACTIVE_KOTLIN_SERIALIZATION));
+ }
+
+ @BuildStep
+ public ServerDefaultProducesHandlerBuildItem jsonDefault() {
+ return json();
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/pom.xml
new file mode 100644
index 0000000000000..e34358d5e6b60
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ quarkus-resteasy-reactive-parent-aggregator
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ quarkus-resteasy-reactive-kotlin-serialization-parent
+ Quarkus - RestEasy Reactive Kotlin Serialization
+ pom
+
+
+ deployment
+ runtime
+
+
\ No newline at end of file
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/pom.xml
new file mode 100644
index 0000000000000..c74b2e9c39a10
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/pom.xml
@@ -0,0 +1,99 @@
+
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-kotlin-serialization-parent
+ 999-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ quarkus-resteasy-reactive-kotlin-serialization
+ Quarkus - RestEasy Reactive Kotlin Serialization - Runtime
+
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-kotlin-serialization-common
+
+
+ io.quarkus
+ quarkus-resteasy-reactive
+
+
+
+
+ src/main/kotlin
+
+
+ io.quarkus
+ quarkus-bootstrap-maven-plugin
+
+
+ io.quarkus.kotlin-serialization
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ compile
+
+ compile
+
+
+
+ test-compile
+
+ test-compile
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ default-compile
+ none
+
+
+
+ default-testCompile
+ none
+
+
+ java-compile
+ compile
+
+ compile
+
+
+
+ java-test-compile
+ test-compile
+
+ testCompile
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ javadoc
+ none
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/kotlin/serialization/KotlinSerializationMessageBodyReader.kt b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/kotlin/serialization/KotlinSerializationMessageBodyReader.kt
new file mode 100644
index 0000000000000..9aed0e44bfb14
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/kotlin/serialization/KotlinSerializationMessageBodyReader.kt
@@ -0,0 +1,45 @@
+package io.quarkus.kotlin.serialization
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeFromStream
+import kotlinx.serialization.serializer
+import org.jboss.resteasy.reactive.common.providers.serialisers.AbstractJsonMessageBodyReader
+import org.jboss.resteasy.reactive.common.util.StreamUtil
+import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo
+import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader
+import org.jboss.resteasy.reactive.server.spi.ServerRequestContext
+import java.io.InputStream
+import java.lang.reflect.Type
+import javax.inject.Inject
+import javax.ws.rs.core.MediaType
+import javax.ws.rs.core.MultivaluedMap
+
+class KotlinSerializationMessageBodyReader(@Inject var json: Json) : AbstractJsonMessageBodyReader(), ServerMessageBodyReader {
+ override fun isReadable(type: Class<*>, genericType: Type, annotations: Array, mediaType: MediaType) =
+ isReadable(mediaType, type)
+
+ override fun isReadable(type: Class<*>, genericType: Type, lazyMethod: ResteasyReactiveResourceInfo, mediaType: MediaType) =
+ isReadable(mediaType, type)
+
+ override fun readFrom(
+ type: Class,
+ genericType: Type,
+ annotations: Array,
+ mediaType: MediaType,
+ httpHeaders: MultivaluedMap,
+ entityStream: InputStream
+ ): Any? {
+ return doReadFrom(type, entityStream)
+ }
+
+ override fun readFrom(type: Class, genericType: Type, mediaType: MediaType, context: ServerRequestContext): Any? {
+ return doReadFrom(type, context.inputStream)
+ }
+
+ @ExperimentalSerializationApi
+ private fun doReadFrom(type: Class, entityStream: InputStream): Any? {
+ return if (StreamUtil.isEmpty(entityStream)) null else
+ json.decodeFromStream(serializer(type), entityStream)
+ }
+}
\ No newline at end of file
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/kotlin/serialization/KotlinSerializationMessageBodyWriter.kt b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/kotlin/serialization/KotlinSerializationMessageBodyWriter.kt
new file mode 100644
index 0000000000000..50b0f994f967e
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/kotlin/serialization/KotlinSerializationMessageBodyWriter.kt
@@ -0,0 +1,64 @@
+package io.quarkus.kotlin.serialization
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.encodeToStream
+import kotlinx.serialization.serializer
+import org.jboss.resteasy.reactive.common.providers.serialisers.JsonMessageBodyWriterUtil
+import org.jboss.resteasy.reactive.server.providers.serialisers.json.JsonMessageServerBodyWriterUtil
+import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter.AllWriteableMessageBodyWriter
+import org.jboss.resteasy.reactive.server.spi.ServerRequestContext
+import java.io.OutputStream
+import java.lang.reflect.Type
+import javax.inject.Inject
+import javax.ws.rs.Produces
+import javax.ws.rs.core.MediaType
+import javax.ws.rs.core.MultivaluedMap
+
+@Produces("application/json", "application/*+json", "text/json")
+@OptIn(ExperimentalSerializationApi::class)
+class KotlinSerializationMessageBodyWriter(@Inject var json: Json) : AllWriteableMessageBodyWriter() {
+ override fun writeTo(
+ o: Any, type: Class<*>, genericType: Type, annotations: Array, mediaType: MediaType,
+ httpHeaders: MultivaluedMap, entityStream: OutputStream
+ ) {
+ JsonMessageBodyWriterUtil.setContentTypeIfNecessary(httpHeaders)
+ if (o is String) { // YUK: done in order to avoid adding extra quotes...
+ entityStream.write(o.toByteArray())
+ } else {
+ json.encodeToStream(o, entityStream)
+ }
+ }
+
+ override fun writeResponse(o: Any, genericType: Type, context: ServerRequestContext) {
+ JsonMessageServerBodyWriterUtil.setContentTypeIfNecessary(context)
+ val originalStream = context.orCreateOutputStream
+ val stream: OutputStream = NoopCloseAndFlushOutputStream(originalStream)
+
+ if (o is String) { // YUK: done in order to avoid adding extra quotes...
+ stream.write(o.toByteArray())
+ } else {
+ val encodeToString = json.encodeToString(json.serializersModule.serializer(genericType), o)
+ stream.write(encodeToString.toByteArray())
+ }
+ // we don't use try-with-resources because that results in writing to the http output without the exception mapping coming into play
+ originalStream.close()
+ }
+
+ private class NoopCloseAndFlushOutputStream(private val delegate: OutputStream) : OutputStream() {
+ override fun flush() {}
+ override fun close() {}
+ override fun write(b: Int) {
+ delegate.write(b)
+ }
+
+ override fun write(b: ByteArray) {
+ delegate.write(b)
+ }
+
+ override fun write(b: ByteArray, off: Int, len: Int) {
+ delegate.write(b, off, len)
+ }
+ }
+}
\ No newline at end of file
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..12471282bc2fe
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,22 @@
+name: "RESTEasy Reactive Kotlin Serialization"
+artifact: ${project.groupId}:${project.artifactId}:${project.version}
+metadata:
+ short-name: "resteasy-reactive-kotlin-serialization"
+ keywords:
+ - "resteasy-reactive-json"
+ - "resteasy-reactive-kotlin-serialization"
+ - "kotlin-serialization"
+ - "kotlin"
+ - "jaxrs-json"
+ - "rest"
+ - "jaxrs"
+ - "json"
+ categories:
+ - "web"
+ - "reactive"
+ status: "experimental"
+ codestart:
+ name: "resteasy-reactive"
+ languages:
+ - "kotlin"
+ artifact: "io.quarkus:quarkus-project-core-extension-codestarts"
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/deployment/pom.xml b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/deployment/pom.xml
new file mode 100644
index 0000000000000..c1f9da96b07ff
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/deployment/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+
+ quarkus-rest-client-reactive-kotlin-serialization-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+ quarkus-rest-client-reactive-kotlin-serialization-deployment
+ Quarkus - REST Client Reactive Kotlin Serialization - Deployment
+
+
+ io.quarkus
+ quarkus-rest-client-reactive-kotlin-serialization
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-deployment
+
+
+ io.quarkus
+ quarkus-kotlin-deployment
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+ io.quarkus
+ quarkus-arc-deployment
+
+
+ io.quarkus
+ quarkus-rest-client-reactive-deployment
+
+
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/deployment/src/main/java/io/quarkus/rest/client/reactive/kotlin/deployment/RestClientReactiveKotlinSerializationProcessor.java b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/deployment/src/main/java/io/quarkus/rest/client/reactive/kotlin/deployment/RestClientReactiveKotlinSerializationProcessor.java
new file mode 100644
index 0000000000000..877027d12d513
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/deployment/src/main/java/io/quarkus/rest/client/reactive/kotlin/deployment/RestClientReactiveKotlinSerializationProcessor.java
@@ -0,0 +1,59 @@
+package io.quarkus.rest.client.reactive.kotlin.deployment;
+
+import static io.quarkus.deployment.Feature.REST_CLIENT_REACTIVE_KOTLIN_SERIALIZATION;
+import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
+
+import java.util.Collections;
+
+import javax.inject.Singleton;
+import javax.ws.rs.core.MediaType;
+
+import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
+import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.kotlin.serialization.KotlinSerializationConfig;
+import io.quarkus.kotlin.serialization.KotlinSerializerRecorder;
+import io.quarkus.rest.client.reactive.kotlin.runtime.serializers.ClientKotlinMessageBodyReader;
+import io.quarkus.rest.client.reactive.kotlin.runtime.serializers.ClientKotlinMessageBodyWriter;
+import io.quarkus.resteasy.reactive.spi.MessageBodyReaderBuildItem;
+import io.quarkus.resteasy.reactive.spi.MessageBodyWriterBuildItem;
+import kotlinx.serialization.json.Json;
+
+public class RestClientReactiveKotlinSerializationProcessor {
+
+ @BuildStep
+ void feature(BuildProducer features) {
+ features.produce(new FeatureBuildItem(REST_CLIENT_REACTIVE_KOTLIN_SERIALIZATION));
+ }
+
+ @BuildStep
+ void additionalProviders(
+ BuildProducer additionalBean,
+ BuildProducer additionalReaders,
+ BuildProducer additionalWriters) {
+ additionalBean.produce(AdditionalBeanBuildItem.builder()
+ .addBeanClass(ClientKotlinMessageBodyReader.class.getName())
+ .addBeanClass(ClientKotlinMessageBodyWriter.class.getName())
+ .setUnremovable().build());
+
+ additionalReaders
+ .produce(new MessageBodyReaderBuildItem(ClientKotlinMessageBodyReader.class.getName(), Object.class.getName(),
+ Collections.singletonList(MediaType.APPLICATION_JSON)));
+ additionalWriters
+ .produce(new MessageBodyWriterBuildItem(ClientKotlinMessageBodyWriter.class.getName(), Object.class.getName(),
+ Collections.singletonList(MediaType.APPLICATION_JSON)));
+ }
+
+ @BuildStep
+ @Record(STATIC_INIT)
+ public SyntheticBeanBuildItem createJson(KotlinSerializerRecorder recorder, KotlinSerializationConfig config) {
+ return SyntheticBeanBuildItem
+ .configure(Json.class)
+ .scope(Singleton.class)
+ .supplier(recorder.configFactory(config))
+ .unremovable().done();
+ }
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/pom.xml b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/pom.xml
new file mode 100644
index 0000000000000..da46d6db5352e
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ quarkus-resteasy-reactive-parent-aggregator
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ quarkus-rest-client-reactive-kotlin-serialization-parent
+ Quarkus - REST Client Reactive Kotlin Serialization Parent
+ pom
+
+ runtime
+ deployment
+ tests
+
+
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/pom.xml b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/pom.xml
new file mode 100644
index 0000000000000..8e240163d7402
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/pom.xml
@@ -0,0 +1,103 @@
+
+
+ 4.0.0
+
+ quarkus-rest-client-reactive-kotlin-serialization-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+ quarkus-rest-client-reactive-kotlin-serialization
+ Quarkus - REST Client Reactive Kotlin Serialization
+ Kotlin serialization support for REST Client Reactive
+
+
+
+ io.quarkus
+ quarkus-rest-client-reactive
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-kotlin-serialization-common
+
+
+ io.quarkus
+ quarkus-resteasy-reactive
+
+
+
+
+ src/main/kotlin
+
+
+ io.quarkus
+ quarkus-bootstrap-maven-plugin
+
+
+ io.quarkus.kotlin-serialization.rest-client
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ compile
+
+ compile
+
+
+
+ test-compile
+
+ test-compile
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ default-compile
+ none
+
+
+
+ default-testCompile
+ none
+
+
+ java-compile
+ compile
+
+ compile
+
+
+
+ java-test-compile
+ test-compile
+
+ testCompile
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ javadoc
+ none
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/rest/client/reactive/kotlin/runtime/serializers/ClientKotlinMessageBodyReader.kt b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/rest/client/reactive/kotlin/runtime/serializers/ClientKotlinMessageBodyReader.kt
new file mode 100644
index 0000000000000..82fd3efc8618d
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/rest/client/reactive/kotlin/runtime/serializers/ClientKotlinMessageBodyReader.kt
@@ -0,0 +1,26 @@
+package io.quarkus.rest.client.reactive.kotlin.runtime.serializers
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeFromStream
+import kotlinx.serialization.serializer
+import org.jboss.resteasy.reactive.common.util.StreamUtil
+import java.io.InputStream
+import java.lang.reflect.Type
+import javax.inject.Inject
+import javax.ws.rs.core.MediaType
+import javax.ws.rs.core.MultivaluedMap
+import javax.ws.rs.ext.MessageBodyReader
+
+@OptIn(ExperimentalSerializationApi::class)
+class ClientKotlinMessageBodyReader(@Inject val json: Json) : MessageBodyReader {
+ override fun isReadable(type: Class<*>?, generic: Type?, annotations: Array?, mediaType: MediaType?) = true
+
+ override fun readFrom(
+ type: Class, generic: Type, annotations: Array?, mediaType: MediaType?,
+ httpHeaders: MultivaluedMap?, entityStream: InputStream
+ ): Any? {
+ return if (StreamUtil.isEmpty(entityStream)) null else
+ json.decodeFromStream(serializer(generic), entityStream)
+ }
+}
\ No newline at end of file
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/rest/client/reactive/kotlin/runtime/serializers/ClientKotlinMessageBodyWriter.kt b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/rest/client/reactive/kotlin/runtime/serializers/ClientKotlinMessageBodyWriter.kt
new file mode 100644
index 0000000000000..8996fdfebf25c
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/src/main/kotlin/io/quarkus/rest/client/reactive/kotlin/runtime/serializers/ClientKotlinMessageBodyWriter.kt
@@ -0,0 +1,24 @@
+package io.quarkus.rest.client.reactive.kotlin.runtime.serializers
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.encodeToStream
+import kotlinx.serialization.serializer
+import java.io.OutputStream
+import java.lang.reflect.Type
+import javax.inject.Inject
+import javax.ws.rs.core.MediaType
+import javax.ws.rs.core.MultivaluedMap
+import javax.ws.rs.ext.MessageBodyWriter
+
+@OptIn(ExperimentalSerializationApi::class)
+class ClientKotlinMessageBodyWriter(@Inject val json: Json) : MessageBodyWriter {
+ override fun isWriteable(type: Class<*>, genericType: Type, annotations: Array?, mediaType: MediaType?) = true
+
+ override fun writeTo(
+ t: Any, type: Class<*>, genericType: Type, annotations: Array?, mediaType: MediaType,
+ httpHeaders: MultivaluedMap, entityStream: OutputStream
+ ) {
+ json.encodeToStream(serializer(genericType), t, entityStream)
+ }
+}
\ No newline at end of file
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..3adf07ac8b4b9
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,25 @@
+---
+artifact: ${project.groupId}:${project.artifactId}:${project.version}
+name: "REST Client Reactive Kotlin Serialization"
+metadata:
+ keywords:
+ - "rest-client-kotlin-serialization"
+ - "rest-client"
+ - "web-client"
+ - "microprofile-rest-client"
+ - "json"
+ - "kotlin-serialization"
+ - "resteasy-reactive"
+ categories:
+ - "web"
+ - "serialization"
+ status: "stable"
+ codestart:
+ name: "resteasy-reactive"
+ kind: "core"
+ languages:
+ - "java"
+ - "kotlin"
+ artifact: "io.quarkus:quarkus-project-core-extension-codestarts"
+ config:
+ - "quarkus.rest-client-reactive."
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/pom.xml b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/pom.xml
new file mode 100644
index 0000000000000..80ea03b251ed3
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/pom.xml
@@ -0,0 +1,109 @@
+
+
+ 4.0.0
+
+ quarkus-rest-client-reactive-kotlin-serialization-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+ quarkus-rest-client-reactive-kotlin-serialization-tests
+ Quarkus - REST Client Reactive Kotlin Serialization - Tests
+
+
+ io.quarkus
+ quarkus-rest-client-reactive-kotlin-serialization
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-deployment
+
+
+ io.quarkus
+ quarkus-kotlin-deployment
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+ io.quarkus
+ quarkus-arc-deployment
+
+
+ io.quarkus
+ quarkus-rest-client-reactive-deployment
+
+
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+ true
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+
+ compile
+
+
+
+ src/main/kotlin
+
+
+
+
+ test-compile
+
+ test-compile
+
+
+
+ src/test/kotlin
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-serialization
+ ${kotlin.version}
+
+
+
+
+ kotlinx-serialization
+
+
+
+
+
+
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/EncodeDefaultValuesTest.kt b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/EncodeDefaultValuesTest.kt
new file mode 100644
index 0000000000000..e1bb4a76674e1
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/EncodeDefaultValuesTest.kt
@@ -0,0 +1,35 @@
+package io.quarkus.rest.client.reactive.kotlin.test
+
+import io.quarkus.test.QuarkusUnitTest
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+import javax.inject.Inject
+
+class EncodeDefaultValuesTest {
+ companion object {
+ @RegisterExtension
+ val config = QuarkusUnitTest()
+ .withConfigurationResource("encode-default-values.properties")
+ }
+
+ @Inject
+ lateinit var json: Json
+
+ @Test
+ fun testNoDefaults() {
+ assertThat(json.encodeToString(TestObject()))
+ .isEqualTo("{}")
+ }
+ @Test
+ fun testExplicitNulls() {
+ assertThat(json.encodeToString(TestObject(blank = null)))
+ .isEqualTo("{\"blank\":null}")
+ }
+
+ @Serializable
+ private class TestObject(var name: String = "Default Value", var blank: String? = "")
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/ExplicitNullsTest.kt b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/ExplicitNullsTest.kt
new file mode 100644
index 0000000000000..fc5d2facf4466
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/ExplicitNullsTest.kt
@@ -0,0 +1,30 @@
+package io.quarkus.rest.client.reactive.kotlin.test
+
+import io.quarkus.test.QuarkusUnitTest
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+import javax.inject.Inject
+
+class ExplicitNullsTest {
+ companion object {
+ @RegisterExtension
+ val config = QuarkusUnitTest()
+ .withConfigurationResource("explicit-nulls.properties")
+ }
+
+ @Inject
+ lateinit var json: Json
+
+ @Test
+ fun testExplicitNulls() {
+ assertThat(json.encodeToString(TestObject(blank = null)))
+ .isEqualTo("{}")
+ }
+
+ @Serializable
+ private class TestObject(var blank: String? = "")
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/LenientTest.kt b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/LenientTest.kt
new file mode 100644
index 0000000000000..668c1245b35f3
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/LenientTest.kt
@@ -0,0 +1,31 @@
+package io.quarkus.rest.client.reactive.kotlin.test
+
+import io.quarkus.test.QuarkusUnitTest
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+import javax.inject.Inject
+
+class LenientTest {
+ companion object {
+ @RegisterExtension
+ val config = QuarkusUnitTest()
+ .withConfigurationResource("lenient.properties")
+ }
+
+ @Inject
+ lateinit var json: Json
+
+ @Test
+ fun testLenient() {
+ assertThat(json.decodeFromString("{\"name\":json}"))
+ .isEqualTo(TestObject("json"))
+ }
+
+ @Serializable
+ private data class TestObject(var name: String)
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/PrettyPrintTest.kt b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/PrettyPrintTest.kt
new file mode 100644
index 0000000000000..ba2ee556436f7
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/kotlin/io/quarkus/rest/client/reactive/kotlin/test/PrettyPrintTest.kt
@@ -0,0 +1,37 @@
+
+package io.quarkus.rest.client.reactive.kotlin.test
+
+import io.quarkus.test.QuarkusUnitTest
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonNames
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+import javax.inject.Inject
+
+@OptIn(ExperimentalSerializationApi::class)
+class PrettyPrintTest {
+ companion object {
+ @RegisterExtension
+ val config = QuarkusUnitTest()
+ .withConfigurationResource("pretty-print.properties")
+ }
+
+ @Inject
+ lateinit var json: Json
+
+ @Test
+ fun testAlternateNames() {
+ assertThat(json.encodeToString(TestObject("John Doe")))
+ .isEqualTo("""
+ {
+ "name": "John Doe"
+ }""".trimIndent())
+ }
+
+ @Serializable
+ private data class TestObject(@JsonNames("label") var name: String)
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/encode-default-values.properties b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/encode-default-values.properties
new file mode 100644
index 0000000000000..0092b8adef075
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/encode-default-values.properties
@@ -0,0 +1 @@
+quarkus.kotlin-serialization.json.encode-defaults=false
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/explicit-nulls.properties b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/explicit-nulls.properties
new file mode 100644
index 0000000000000..92e18a17a312c
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/explicit-nulls.properties
@@ -0,0 +1 @@
+quarkus.kotlin-serialization.json.explicit-nulls=false
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/lenient.properties b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/lenient.properties
new file mode 100644
index 0000000000000..b68606c396b7d
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/lenient.properties
@@ -0,0 +1,2 @@
+quarkus.kotlin-serialization.json.is-lenient=true
+quarkus.kotlin-serialization.json.encode-defaults=false
diff --git a/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/pretty-print.properties b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/pretty-print.properties
new file mode 100644
index 0000000000000..fcad5f07927bb
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-kotlin-serialization/tests/src/test/resources/pretty-print.properties
@@ -0,0 +1 @@
+quarkus.kotlin-serialization.json.pretty-print=true
diff --git a/integration-tests/kotlin-serialization/pom.xml b/integration-tests/kotlin-serialization/pom.xml
new file mode 100644
index 0000000000000..e72059383f116
--- /dev/null
+++ b/integration-tests/kotlin-serialization/pom.xml
@@ -0,0 +1,142 @@
+
+
+ 4.0.0
+
+
+ quarkus-integration-tests-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+
+ quarkus-integration-test-kotlin-serialization
+ Quarkus - Integration Tests - Kotlin Serialization
+ Kotlin Serialization integration tests module
+
+
+ 1.3.1
+
+
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-kotlin-serialization
+ 999-SNAPSHOT
+
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-kotlin-serialization-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-arc-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+
+
+ src/main/kotlin
+ src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-serialization
+ ${kotlin.version}
+
+
+
+
+ all-open
+ kotlinx-serialization
+
+
+ all-open:annotation=javax.ws.rs.Path
+
+
+
+
+ maven-failsafe-plugin
+
+ true
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+
+
+
+ build
+
+
+
+
+
+
+
+
diff --git a/integration-tests/kotlin-serialization/src/main/kotlin/io/quarkus/it/kotser/GreetingResource.kt b/integration-tests/kotlin-serialization/src/main/kotlin/io/quarkus/it/kotser/GreetingResource.kt
new file mode 100644
index 0000000000000..a2cd5fc08712c
--- /dev/null
+++ b/integration-tests/kotlin-serialization/src/main/kotlin/io/quarkus/it/kotser/GreetingResource.kt
@@ -0,0 +1,27 @@
+package io.quarkus.it.kotser
+
+import io.quarkus.it.kotser.model.Person
+import javax.ws.rs.Consumes
+import javax.ws.rs.GET
+import javax.ws.rs.POST
+import javax.ws.rs.Path
+import javax.ws.rs.Produces
+import javax.ws.rs.core.MediaType
+
+@Path("/")
+class GreetingResource {
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ fun hello(): Person {
+ return Person("Jim Halpert")
+ }
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ fun marry(person: Person): Person {
+ return Person(person.name.substringBefore(" ") + " Halpert")
+ }
+}
+
diff --git a/integration-tests/kotlin-serialization/src/main/kotlin/io/quarkus/it/kotser/model/Person.kt b/integration-tests/kotlin-serialization/src/main/kotlin/io/quarkus/it/kotser/model/Person.kt
new file mode 100644
index 0000000000000..12191e7cafd10
--- /dev/null
+++ b/integration-tests/kotlin-serialization/src/main/kotlin/io/quarkus/it/kotser/model/Person.kt
@@ -0,0 +1,10 @@
+package io.quarkus.it.kotser.model
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Person(var name: String, var defaulted: String = "hi there!") {
+ override fun toString(): String {
+ TODO("this shouldn't get called. a proper serialization should be invoked.")
+ }
+}
\ No newline at end of file
diff --git a/integration-tests/kotlin-serialization/src/main/resources/application.properties b/integration-tests/kotlin-serialization/src/main/resources/application.properties
new file mode 100644
index 0000000000000..86df10fe75526
--- /dev/null
+++ b/integration-tests/kotlin-serialization/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+quarkus.kotlin-serialization.json.encode-defaults=true
+quarkus.kotlin-serialization.json.pretty-print=true
+quarkus.kotlin-serialization.json.pretty-print-indent=\ \
\ No newline at end of file
diff --git a/integration-tests/kotlin-serialization/src/test/kotlin/io/quarkus/it/kotser/ResourceTest.kt b/integration-tests/kotlin-serialization/src/test/kotlin/io/quarkus/it/kotser/ResourceTest.kt
new file mode 100644
index 0000000000000..f7b11b299cf25
--- /dev/null
+++ b/integration-tests/kotlin-serialization/src/test/kotlin/io/quarkus/it/kotser/ResourceTest.kt
@@ -0,0 +1,40 @@
+package io.quarkus.it.kotser
+
+import io.quarkus.test.junit.QuarkusTest
+import io.restassured.RestAssured.given
+import io.restassured.http.ContentType.JSON
+import org.hamcrest.CoreMatchers.`is`
+import org.junit.jupiter.api.Test
+
+@QuarkusTest
+class ResourceTest {
+ @Test
+ fun testGet() {
+ given()
+ .`when`().get("/")
+ .then()
+ .statusCode(200)
+ .body(`is`(
+ """
+ {
+ "name": "Jim Halpert",
+ "defaulted": "hi there!"
+ }""".trimIndent()
+ ))
+ }
+
+ @Test
+ fun testPost() {
+ given()
+ .body("{\"name\":\"Pam Beasley\"}")
+ .contentType(JSON)
+ .`when`().post("/")
+ .then()
+ .statusCode(200)
+ .body(`is`("""
+ {
+ "name": "Pam Halpert",
+ "defaulted": "hi there!"
+ }""".trimIndent()))
+ }
+}
\ No newline at end of file
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 083b11198c578..476895aaa3f1c 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -236,6 +236,7 @@
maven
scala
kotlin
+ kotlin-serialization
mongodb-panache
mongodb-panache-kotlin
mongodb-rest-data-panache
@@ -266,6 +267,7 @@
rest-client
resteasy-reactive-kotlin
rest-client-reactive
+ rest-client-reactive-kotlin-serialization
rest-client-reactive-multipart
rest-client-reactive-stork
packaging
diff --git a/integration-tests/rest-client-reactive-kotlin-serialization/pom.xml b/integration-tests/rest-client-reactive-kotlin-serialization/pom.xml
new file mode 100644
index 0000000000000..e836d40221a5f
--- /dev/null
+++ b/integration-tests/rest-client-reactive-kotlin-serialization/pom.xml
@@ -0,0 +1,146 @@
+
+
+
+ quarkus-integration-tests-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+ 4.0.0
+ quarkus-integration-test-rest-client-reactive-kotlin-serialization
+ Quarkus - Integration Tests - REST Client Reactive Kotlin Serialization
+
+
+
+ io.quarkus
+ quarkus-rest-client-reactive-kotlin-serialization
+
+
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ org.awaitility
+ awaitility
+ test
+
+
+
+
+ io.quarkus
+ quarkus-rest-client-reactive-kotlin-serialization-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+
+
+ src/main/kotlin
+ src/test/kotlin
+
+
+ src/main/resources
+ true
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-serialization
+ ${kotlin.version}
+
+
+
+
+ all-open
+ kotlinx-serialization
+
+
+ all-open:annotation=javax.ws.rs.Path
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+
+
+
+ build
+
+
+
+
+
+
+
+
+
+ native-image
+
+
+ native
+
+
+
+ native
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+
+
+
+
diff --git a/integration-tests/rest-client-reactive-kotlin-serialization/src/main/kotlin/io/quarkus/it/rest/CountriesClient.kt b/integration-tests/rest-client-reactive-kotlin-serialization/src/main/kotlin/io/quarkus/it/rest/CountriesClient.kt
new file mode 100644
index 0000000000000..205b9d2405023
--- /dev/null
+++ b/integration-tests/rest-client-reactive-kotlin-serialization/src/main/kotlin/io/quarkus/it/rest/CountriesClient.kt
@@ -0,0 +1,22 @@
+package io.quarkus.it.rest
+
+import javax.ws.rs.Consumes
+import javax.ws.rs.GET
+import javax.ws.rs.POST
+import javax.ws.rs.Path
+import javax.ws.rs.Produces
+import javax.ws.rs.core.MediaType
+
+@Path("")
+interface CountriesClient {
+ @POST
+ @Path("/country")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ fun country(country: Country): Country
+
+ @GET
+ @Path("/countries")
+ @Produces(MediaType.APPLICATION_JSON)
+ fun countries(): List
+}
\ No newline at end of file
diff --git a/integration-tests/rest-client-reactive-kotlin-serialization/src/main/kotlin/io/quarkus/it/rest/CountriesResource.kt b/integration-tests/rest-client-reactive-kotlin-serialization/src/main/kotlin/io/quarkus/it/rest/CountriesResource.kt
new file mode 100644
index 0000000000000..cc1f5559d7fb9
--- /dev/null
+++ b/integration-tests/rest-client-reactive-kotlin-serialization/src/main/kotlin/io/quarkus/it/rest/CountriesResource.kt
@@ -0,0 +1,72 @@
+package io.quarkus.it.rest
+
+import io.vertx.ext.web.Router
+import io.vertx.ext.web.RoutingContext
+import io.vertx.ext.web.handler.BodyHandler
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import org.eclipse.microprofile.rest.client.RestClientBuilder
+import java.net.URI
+import javax.enterprise.context.ApplicationScoped
+import javax.enterprise.event.Observes
+import javax.inject.Inject
+import javax.ws.rs.Path
+
+@Path("/")
+@ApplicationScoped
+class CountriesResource {
+ @Inject
+ lateinit var json: Json
+
+ fun init(@Observes router: Router) {
+ router.post().handler(BodyHandler.create())
+
+ router.route("/call-country").blockingHandler { rc: RoutingContext ->
+ val client = RestClientBuilder.newBuilder()
+ .baseUri(URI.create(rc.body.toString()))
+ .build(CountriesClient::class.java)
+ val result = client.country(
+ Country(
+ "Sweden", "SE", "Stockholm",
+ listOf(Currency("SEK", "Swedish Crowns", "kr"))
+ )
+ )
+ rc.response()
+ .setStatusCode(200)
+ .end(result.capital)
+ }
+
+ router.route("/country").blockingHandler { rc: RoutingContext ->
+ val body = json.decodeFromString(rc.bodyAsString)
+ body.capital = "Sthlm"
+
+ rc.response()
+ .putHeader("content-type", "application/json")
+ .end(json.encodeToString(body))
+ }
+
+ router.route("/call-countries").blockingHandler { rc: RoutingContext ->
+ val client = RestClientBuilder.newBuilder()
+ .baseUri(URI.create(rc.body.toString()))
+ .build(CountriesClient::class.java)
+ val result = client.countries()
+ rc.response()
+ .setStatusCode(200)
+ .end("OK")
+ }
+
+ router.route("/countries").blockingHandler { rc: RoutingContext ->
+ val body = listOf(
+ Country(
+ "Sweden", "SE", "Stockholm",
+ listOf(Currency("SEK", "Swedish Crowns", "kr"))
+ )
+ )
+
+ rc.response()
+ .putHeader("content-type", "application/json")
+ .end(json.encodeToString(body))
+ }
+ }
+}
\ No newline at end of file
diff --git a/integration-tests/rest-client-reactive-kotlin-serialization/src/main/kotlin/io/quarkus/it/rest/Country.kt b/integration-tests/rest-client-reactive-kotlin-serialization/src/main/kotlin/io/quarkus/it/rest/Country.kt
new file mode 100644
index 0000000000000..feaa1a335f8b3
--- /dev/null
+++ b/integration-tests/rest-client-reactive-kotlin-serialization/src/main/kotlin/io/quarkus/it/rest/Country.kt
@@ -0,0 +1,17 @@
+package io.quarkus.it.rest
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Country(var name: String, var alpha2Code: String, var capital: String, var currencies: List) {
+ override fun toString(): String {
+ TODO("serialization should never call this method")
+ }
+}
+
+@Serializable
+data class Currency(var code: String, var name: String, var symbol: String) {
+ override fun toString(): String {
+ TODO("serialization should never call this method")
+ }
+}
diff --git a/integration-tests/rest-client-reactive-kotlin-serialization/src/main/resources/application.properties b/integration-tests/rest-client-reactive-kotlin-serialization/src/main/resources/application.properties
new file mode 100644
index 0000000000000..bea6812de9ec1
--- /dev/null
+++ b/integration-tests/rest-client-reactive-kotlin-serialization/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+quarkus.kotlin-serialization.json.encode-defaults=true
+quarkus.kotlin-serialization.json.pretty-print=true
+quarkus.kotlin-serialization.json.pretty-print-indent=\ \
diff --git a/integration-tests/rest-client-reactive-kotlin-serialization/src/test/kotlin/io/quarkus/it/rest/client/BasicTest.kt b/integration-tests/rest-client-reactive-kotlin-serialization/src/test/kotlin/io/quarkus/it/rest/client/BasicTest.kt
new file mode 100644
index 0000000000000..afd417f3f04e2
--- /dev/null
+++ b/integration-tests/rest-client-reactive-kotlin-serialization/src/test/kotlin/io/quarkus/it/rest/client/BasicTest.kt
@@ -0,0 +1,31 @@
+package io.quarkus.it.rest.client
+
+import io.quarkus.test.common.http.TestHTTPResource
+import io.quarkus.test.junit.QuarkusTest
+import io.restassured.RestAssured
+import org.assertj.core.api.Assertions
+import org.junit.jupiter.api.Test
+
+@QuarkusTest
+open class BasicTest {
+
+ @TestHTTPResource("/")
+ lateinit var country: String
+
+ @Test
+ fun callCountry() {
+ val response = RestAssured.with()
+ .body(country)
+ .post("/call-country")
+ Assertions.assertThat(response.asString()).isEqualTo("Sthlm")
+ }
+
+ @Test
+ fun callCountries() {
+ val response = RestAssured.with()
+ .body(country)
+ .post("/call-countries")
+ Assertions.assertThat(response.asString()).isEqualTo("OK")
+ }
+
+}
\ No newline at end of file
diff --git a/integration-tests/rest-client-reactive-kotlin-serialization/src/test/kotlin/io/quarkus/it/rest/client/BasicTestIT.kt b/integration-tests/rest-client-reactive-kotlin-serialization/src/test/kotlin/io/quarkus/it/rest/client/BasicTestIT.kt
new file mode 100644
index 0000000000000..d30a10ebc9768
--- /dev/null
+++ b/integration-tests/rest-client-reactive-kotlin-serialization/src/test/kotlin/io/quarkus/it/rest/client/BasicTestIT.kt
@@ -0,0 +1,6 @@
+package io.quarkus.it.rest.client
+
+import io.quarkus.test.junit.NativeImageTest
+
+@NativeImageTest
+class BasicTestIT : BasicTest()
\ No newline at end of file