Arjan van Wieringen
04/17/2025, 5:31 AMAidan
04/18/2025, 6:31 PMfailed with exception: kotlin.IllegalStateException: TLS sessions are not supported on Native platform.
Internet searches aren't giving me a ton of good info. Does anyone know if CIO is fully supported or not for iOS?Sean Chin Jun Kai
04/19/2025, 6:29 AMKuldar
04/19/2025, 1:12 PMcall.sessions.clear<UserSession>()
call.sessions.set(UserSession(...))
Any suggestions what should I try next?Ville Orkas
04/20/2025, 6:48 AMjava.lang.IllegalStateException: No instance for key AttributeKey: CallLogger
at io.ktor.util.Attributes$DefaultImpls.get(Attributes.kt:77)
at io.ktor.util.AttributesJvmBase.get(AttributesJvm.kt:17)
at io.ktor.client.plugins.logging.LoggingKt$Logging$2$4.invokeSuspend(Logging.kt:622)
at io.ktor.client.plugins.logging.LoggingKt$Logging$2$4.invoke(Unknown Source:15)
at io.ktor.client.plugins.logging.LoggingKt$Logging$2$4.invoke(Unknown Source:6)
at io.ktor.client.plugins.logging.ReceiveHook$install$1.invokeSuspend(Logging.kt:759)
at io.ktor.client.plugins.logging.ReceiveHook$install$1.invoke(Unknown Source:11)
at io.ktor.client.plugins.logging.ReceiveHook$install$1.invoke(Unknown Source:6)
at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
at io.ktor.client.plugins.ReceiveError$install$1.invokeSuspend(HttpCallValidator.kt:165)
at io.ktor.client.plugins.ReceiveError$install$1.invoke(Unknown Source:11)
at io.ktor.client.plugins.ReceiveError$install$1.invoke(Unknown Source:6)
at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
at io.ktor.util.pipeline.DebugPipelineContext.execute$ktor_utils(DebugPipelineContext.kt:63)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:92)
at io.ktor.client.call.HttpClientCall.bodyNullable(HttpClientCall.kt:99)
I'm using 3.1.2, and I'm not doing anything more than calling GET on an API that returns JSON. Whats going on? I can't find anything on this onlineAdam S
04/20/2025, 8:39 AMvar fileDescription
and var fileName
are outside of the post("/upload") { }
block? What if the form has multiple uploads? Or if the endpoint is called twice simultaneously - one request could overwrite the value from another?david dereba
04/21/2025, 6:19 AMpackage com.root.plugins
import com.google.gson.*
import com.google.gson.FieldNamingPolicy
import com.google.gson.Strictness
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import com.root.utility.UtilityFunctions
import io.ktor.http.ContentType
import io.ktor.serialization.gson.*
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.Encoder
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import kotlinx.serialization.modules.SerializersModule
import java.lang.reflect.Type
import java.time.Instant
import kotlinx.datetime.Instant as KtxInstant
@OptIn(ExperimentalSerializationApi::class)
fun Application.configureSerialization() {
// Create the fully configured Gson instance
val gson = GsonBuilder()
.setPrettyPrinting()
.setStrictness(Strictness.LENIENT)
.serializeNulls()
.registerTypeAdapter(UtilityFunctions.ApiResponseGsonSerializer::class.java, UtilityFunctions.ApiResponseGsonSerializer())
.registerTypeAdapter(Instant::class.java, object : JsonSerializer<Instant>, JsonDeserializer<Instant> {
override fun serialize(src: Instant, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.toString())
}
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Instant {
return Instant.parse(json.asString)
}
})
.registerTypeAdapter(Instant::class.java, object : TypeAdapter<Instant>() {
override fun write(out: JsonWriter, value: Instant?) {
if (value == null) {
out.nullValue()
} else {
out.value(value.toString()) // ISO-8601
}
}
override fun read(reader: JsonReader): Instant? {
return if (reader.peek() == JsonToken.NULL) {
reader.nextNull(); null
} else {
Instant.parse(reader.nextString())
}
}
}.nullSafe())
.registerTypeAdapter(KtxInstant::class.java, object : JsonSerializer<KtxInstant>, JsonDeserializer<KtxInstant> {
override fun serialize(src: KtxInstant, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.toString())
}
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): KtxInstant {
return KtxInstant.parse(json.asString)
}
})
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()
install(ContentNegotiation) {
// Apply the pre-configured Gson instance
register(ContentType.Application.Json, GsonConverter(gson))
json(Json {
isLenient = true
prettyPrint = true
encodeDefaults = true
ignoreUnknownKeys = true
coerceInputValues = true
namingStrategy = JsonNamingStrategy.SnakeCase
serializersModule = SerializersModule {
contextual(KtxInstant::class) {
KotlinxInstantSerializer
}
}
})
}
routing {
get("/json/kotlinx-serialization") {
call.respond(mapOf("hello" to "world"))
}
get("/json/gson") {
call.respond(mapOf("hello" to "world"))
}
}
}
// And define your serializer
object KotlinxInstantSerializer : KSerializer<KtxInstant> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("KotlinxInstant", PrimitiveKind.STRING)
override fun serialize(encoder: kotlinx.serialization.encoding.Encoder, value: KtxInstant) {
encoder.encodeString(value.toString())
}
override fun deserialize(decoder: Decoder): KtxInstant {
return KtxInstant.parse(decoder.decodeString())
}
}
then this is the full error log
2025-04-21 09:13:55.340 [eventLoopGroupProxy-4-1] ERROR i.k.server.application.Application - Error occurred: JsonIOException || Failed making field 'java.time.Instant#seconds' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.
See <https://github.com/google/gson/blob/main/Troubleshooting.md#reflection-inaccessible>
com.google.gson.JsonIOException: Failed making field 'java.time.Instant#seconds' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.
See <https://github.com/google/gson/blob/main/Troubleshooting.md#reflection-inaccessible>
at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:76)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:388)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:161)
at com.google.gson.Gson.getAdapter(Gson.java:628)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:201)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:395)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:161)
at com.google.gson.Gson.getAdapter(Gson.java:628)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:201)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:395)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:161)
at com.google.gson.Gson.getAdapter(Gson.java:628)
at com.google.gson.Gson.toJson(Gson.java:928)
at com.google.gson.Gson.toJsonTree(Gson.java:802)
at com.google.gson.Gson.toJsonTree(Gson.java:779)
at com.root.routing.LeasesRoutesKt.handleLeaseCreation(LeasesRoutes.kt:53)
at com.root.routing.LeasesRoutesKt.access$handleLeaseCreation(LeasesRoutes.kt:1)
at com.root.routing.LeasesRoutesKt$handleLeaseCreation$1.invokeSuspend(LeasesRoutes.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuation.resumeWith(DispatchedContinuation.kt:197)
at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:146)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:120)
at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:11)
at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:70)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final long java.time.Instant.seconds accessible: module java.base does not "opens java.time" to unnamed module @10315254
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:68)
What could be the issue. please assist.Yassine Abou
04/23/2025, 9:48 PMCIO
engine)
2. After Removing CIO
Engine:`Uncaught runtime errors: ERROR wasm validation error: at offset 5557696: type mismatch: expression has type (ref null 1950) but expected externref`
3. Here is my setup:
my ktor version is 3.1.0
and
compose version is 1.7.3.
Dependencies (commonMain):
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.ktor.client.cio)
Koin DI Configuration:
single { Json { ignoreUnknownKeys = true isLenient ; = true encodeDefaults = false } }
// 2. HTTP Client Configuration
single<HttpClient> { HttpClient(CIO) { engine { requestTimeout = 0 }
install(ContentNegotiation) { json( json = get(), contentType = ContentType.Application.Json ) } }
here is the repository link for more context: https://github.com/yassineAbou/LLMSAustin Pederson
04/25/2025, 7:48 PMContentNegotiation
plugin. It looks like the ContentNegotiation
plugin always uses Transfer-Encoding: chunked
which is a problem for S3. This limits me to having to manually serializes this object before uploading it. Is there any way to turn off this behavior? I've scoured the web but found no way to do this as of yet...Kev
04/26/2025, 7:50 AMCould not find io.ktor:ktor-server-routing:3.1.2.
Searched in the following locations:
- <https://repo.maven.apache.org/maven2/io/ktor/ktor-server-routing/3.1.2/ktor-server-routing-3.1.2.pom>
Arjan van Wieringen
04/27/2025, 7:14 AMcreateApplicationPlugin
/ createRouteScopedPlugin
are typically instantiated as values with a configuration block? In a lot of cases it is much easier to just do:
fun MyCustomPlugin(val myDeps: MyDeps) = createRouteScopedPlugin("MyCustomPlugin") {
...use myDeps
}
instead of a configuration class with mutable variables.ursus
04/29/2025, 2:35 PMwebSocketSession.send(frame)
this suspends until it's actually sent? or only until enqueued (and will be sent later)?
if it's the later - is there a way to tell it's actually been delivered?
--
I need to update my entity's state from SENDING
to SENT
Colton Idle
04/30/2025, 1:19 AMcommonMain.dependencies {
implementation("io.ktor:ktor-client:3.0.0")
Android works
iOS works
Running wasm comes with a
Could not determine the dependencies of task ':kotlinNpmInstall'.
> Could not resolve all dependencies for configuration ':composeApp:wasmJsNpmAggregated'.
> Could not resolve io.ktor:ktor-client:3.0.0.
Required by:
project :composeApp
> No matching variant of io.ktor:ktor-client:3.0.0 was found. The consumer was configured to find a library for use during 'kotlin-runtime', preferably optimized for non-jvm, as well as attribute 'org.jetbrains.kotlin.js.public.package.json' with value 'public-package-json', attribute 'org.jetbrains.kotlin.platform.type' with value 'wasm', attribute 'org.jetbrains.kotlin.wasm.target' with value 'js' but:
Jay
05/01/2025, 10:32 AMinstall(plugin = ContentEncoding) {
gzip()
deflate()
}
Jay
05/01/2025, 11:12 AMursus
05/02/2025, 4:09 PMappInForeground
.collectLatest { isInForeground ->
if (isInForeground) {
val webSocketSession = ktorClient.webSocketSession(..)
webSocketSession listenAndSend(webSocketSession)
}
}
is this enough? or does it need cancelling / closing explicitly? Since the WebSocketSession implements CoroutineScope im not sureMarc
05/02/2025, 5:47 PMERROR io.ktor.server.Application - Unhandled: GET - /swagger
java.lang.ClassCastException: class kotlinx.html.DIV cannot be cast to class kotlinx.html.CoreAttributeGroupFacade (kotlinx.html.DIV and kotlinx.html.CoreAttributeGroupFacade are in unnamed module of loader 'app')
at io.ktor.server.plugins.swagger.SwaggerKt$swaggerUI$5$2.invokeSuspend$lambda$9(Swagger.kt:103) [...] etc
I’m not doing anything misterious, just followed the doccumentation steps 😕Dejan Lozanovic
05/04/2025, 1:17 PMCharann
05/06/2025, 3:44 AMEugen Martynov
05/06/2025, 7:04 AMarnaud.giuliani
05/06/2025, 1:34 PMversion.ktor.yaml
file mean that it takes all versions from 2.0 to all superior ones?
"[2.0,)":
- io.insert-koin:koin-ktor:$koin
- io.insert-koin:koin-logger-slf4j:$koin
Umesh Solanki
05/06/2025, 3:50 PMsubmitFormWithBinaryData
I'm using HttpClient(Js)? formData.append didn't work.
Thread in Slack Conversationdayanruben
05/06/2025, 3:52 PMHong Phuc
05/07/2025, 3:34 AMfrevib
05/07/2025, 12:45 PMfun main() = runBlocking {
launch(<http://Dispatchers.IO|Dispatchers.IO>) {
embeddedServer(CIO, port = 8080, module = Application::modules)
.start(wait = true)
}
launch(<http://Dispatchers.IO|Dispatchers.IO>) {
startKafkaConsumer()
}
}
or have the Kafka consumer be a module of Ktor, e.g.:
fun main() {
embeddedServer(CIO, port = 8080, module = Application::modules)
.start(wait = true)
}
fun Application.modules() {
kafkaModule()
ktorModules()
}
fun Application.kafkaModule() {
launch(<http://Dispatchers.IO|Dispatchers.IO>) {
startKafkaConsumer()
}
}
Approach 1 seems more architecturally correct, having both Ktor server and Kafka consumer running in a coroutine on the same level. The 2nd approach makes Ktor managing the lifecycle of the Kafka consumer, which is arguably less good.Hong Phuc
05/08/2025, 3:10 AMhttpClient
thread-safe? From the answer in here and here, I inferred that using httpClient
with the OkHttp
engine is thread-safe. Still can someone confirm if this is true? Thanks in advance
Also is the httpClient
supposed to be used as a singleton? Is it possible to change to a specific engine so that I can create multiple instance of httpClient
?Ulf
05/08/2025, 2:33 PMHttpCache
plugin. We've also made a custom logging plugin based on similar code as in the Logging
plugin.
However, one difference from the Logging
plugin is that we would like to log the response even if nothing is sent over the network, ie the cached item is valid and returned directly (see for example HttpCache.proceedWithCache
). Logging the response body becomes problematic and I'm unsure how to solve that. In the Logging
plugin the ResponseObserver
which is running in the receivePipeline
is used to split the ByteReadChannel that contains the rawContent
so that one is used for logging, but when response comes from cache the receivePipeline
will never run. I can intercept the response in the responsePipeline
though, but am unsure how to handle it here so that i can both log it and have the channel possible to consume later when reading the body, i've tried to something similar with split( )
as in ResponseObserver
but so far failed...
(the response body is there but hidden as responseBody
in SavedHttpCall
, so if one could access it directly things would be quite simple...)hellman
05/09/2025, 7:33 AM2025-05-09 07:18:11.727 [eventLoopGroupProxy-4-1] TRACE io.ktor.server.Application - Failed to decode request
java.lang.IllegalArgumentException: text is empty (possibly HTTP/0.9)
Has anyone seen this and know what we're doing wrong?hellman
05/12/2025, 7:13 AMsse()
routes. If we want to send a HTTP response instead of emitting events (e.g., call.respond(HttpStatusCode.BadRequest)
), that response code is simply ignored and we always get HTTP 200. Is this an expected behavior? I feel it makes error handling for SSE endpoints difficult.romainbsl
05/12/2025, 10:00 AMonRequest
.
Maybe there is a semantic explanation for both usage, but at the end what do you suggest to use ?