Changelog 3.3 version
3.3.3
released 27th November 2025
Client
Discrepancies when parsing URL host with CIO and Darwin engines compared to the rest engines
To reproduce, run the following code:
val client = HttpClient(Darwin) {
defaultRequest {
host = "httpbin.org/status"
}
}
val r = client.get("/200")
println(r.status)
As a result, the following exception is thrown:
TRACE] (io.ktor.client.plugins.HttpCallValidator): Processing exception io.ktor.client.engine.darwin.DarwinHttpRequestException: Exception in http request: Error Domain=NSURLErrorDomain Code=-1003 "A server with the specified hostname could not be found." UserInfo={_kCFStreamErrorCodeKey=8, NSUnderlyingError=0x6000007bc840 {Error Domain=kCFErrorDomainCFNetwork Code=-1003 "(null)" UserInfo={_NSURLErrorNWPathKey=satisfied (Path is satisfied), interface: en0[802.11], ipv4, dns, proxy, _kCFStreamErrorCodeKey=8, _kCFStreamErrorDomainKey=12}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <764B8322-F2DE-4008-B3F2-D3251C655BB3>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <764B8322-F2DE-4008-B3F2-D3251C655BB3>.<1>"
), NSLocalizedDescription=A server with the specified hostname could not be found., NSErrorFailingURLStringKey=http://status/200, NSErrorFailingURLKey=http://status/200, _kCFStreamErrorDomainKey=12} for request http://httpbin.org/status/200
Uncaught Kotlin exception: io.ktor.client.engine.darwin.DarwinHttpRequestException: Exception in http request: Error Domain=NSURLErrorDomain Code=-1003 "A server with the specified hostname could not be found." UserInfo={_kCFStreamErrorCodeKey=8, NSUnderlyingError=0x6000007bc840 {Error Domain=kCFErrorDomainCFNetwork Code=-1003 "(null)" UserInfo={_NSURLErrorNWPathKey=satisfied (Path is satisfied), interface: en0[802.11], ipv4, dns, proxy, _kCFStreamErrorCodeKey=8, _kCFStreamErrorDomainKey=12}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <764B8322-F2DE-4008-B3F2-D3251C655BB3>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <764B8322-F2DE-4008-B3F2-D3251C655BB3>.<1>"
), NSLocalizedDescription=A server with the specified hostname could not be found., NSErrorFailingURLStringKey=http://status/200, NSErrorFailingURLKey=http://status/200, _kCFStreamErrorDomainKey=12}
at 0 ktor_native.kexe 0x103c51635 kfun:kotlin.Exception#<init>(kotlin.String?;kotlin.Throwable?){} + 133 (/opt/buildAgent/work/f43969c6214a19e7/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:25:63)
at 1 ktor_native.kexe 0x103b8cc17 kfun:io.ktor.utils.io.errors.IOException#<init>(kotlin.String;kotlin.Throwable?){} + 119 (/opt/buildAgent/work/8d547b974a7be21f/ktor-io/posix/src/io/ktor/utils/io/errors/IOException.kt:4:58)
at 2 ktor_native.kexe 0x103b8cc7d kfun:io.ktor.utils.io.errors.IOException#<init>(kotlin.String){} + 93 (/opt/buildAgent/work/8d547b974a7be21f/ktor-io/posix/src/io/ktor/utils/io/errors/IOException.kt:5:50)
at 3 ktor_native.kexe 0x103fbe0a6 kfun:io.ktor.client.engine.darwin.DarwinHttpRequestException#<init>(platform.Foundation.NSError){} + 198 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/DarwinUtils.kt:114:71)
at 4 ktor_native.kexe 0x103fc4587 kfun:io.ktor.client.engine.darwin#handleNSError(io.ktor.client.request.HttpRequestData;platform.Foundation.NSError){}kotlin.Throwable + 375 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/TimeoutUtils.kt:28:103)
at 5 ktor_native.kexe 0x103fca2f0 kfun:io.ktor.client.engine.darwin.internal.DarwinTaskHandler#complete(platform.Foundation.NSURLSessionTask;platform.Foundation.NSError?){} + 448 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/internal/DarwinTaskHandler.kt:57:29)
at 6 ktor_native.kexe 0x103fc0e68 kfun:io.ktor.client.engine.darwin.KtorNSURLSessionDelegate#objc:URLSession:task:didCompleteWithError: + 584 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/KtorNSURLSessionDelegate.kt:63:16)
at 7 ktor_native.kexe 0x103fc25ec _696f2e6b746f723a6b746f722d636c69656e742d64617277696e2f6f70742f6275696c644167656e742f776f726b2f386435343762393734613762653231662f6b746f722d636c69656e742f6b746f722d636c69656e742d64617277696e2f64617277696e2f7372632f696f2f6b746f722f636c69656e742f656e67696e652f64617277696e2f4b746f724e5355524c53657373696f6e44656c65676174652e6b74_knbridge8 + 380 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/KtorNSURLSessionDelegate.kt:61:14)
at 8 CFNetwork 0x7ff8191e0c60 CFURLCredentialStorageCopyAllCredentials + 44247
at 9 libdispatch.dylib 0x7ff8147227fa _dispatch_call_block_and_release + 11
at 10 libdispatch.dylib 0x7ff814723a43 _dispatch_client_callout + 7
at 11 libdispatch.dylib 0x7ff814729ac3 _dispatch_lane_serial_drain + 693
at 12 libdispatch.dylib 0x7ff81472a5e6 _dispatch_lane_invoke + 416
at 13 libdispatch.dylib 0x7ff814734ad6 _dispatch_workloop_worker_thread + 761
at 14 libsystem_pthread.dylib 0x7ff81489fce2 _pthread_wqthread + 325
at 15 libsystem_pthread.dylib 0x7ff81489ec66 start_wqthread + 14
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@787a1b0, Dispatchers.IO]
at 0 ktor_native.kexe 0x103c5751b kfun:kotlin.Throwable#<init>(kotlin.String?){} + 107 (/opt/buildAgent/work/f43969c6214a19e7/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:28:37)
at 1 ktor_native.kexe 0x103c515a7 kfun:kotlin.Exception#<init>(kotlin.String?){} + 103 (/opt/buildAgent/work/f43969c6214a19e7/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:23:44)
at 2 ktor_native.kexe 0x103c51707 kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 103 (/opt/buildAgent/work/f43969c6214a19e7/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:34:44)
at 3 ktor_native.kexe 0x103ec4377 kfun:kotlinx.coroutines.internal.DiagnosticCoroutineContextException#<init>(kotlin.coroutines.CoroutineContext){} + 199 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/internal/CoroutineExceptionHandlerImpl.kt:31:5)
at 4 ktor_native.kexe 0x103e8cda5 kfun:kotlinx.coroutines.internal#handleUncaughtCoroutineException(kotlin.coroutines.CoroutineContext;kotlin.Throwable){} + 1333 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/CoroutineExceptionHandlerImpl.common.kt:47:33)
at 5 ktor_native.kexe 0x103e32c57 kfun:kotlinx.coroutines#handleCoroutineException(kotlin.coroutines.CoroutineContext;kotlin.Throwable){} + 1047 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt:32:5)
at 6 ktor_native.kexe 0x103e22e4f kfun:kotlinx.coroutines.StandaloneCoroutine.handleJobException#internal + 191 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/Builders.common.kt:194:9)
at 7 ktor_native.kexe 0x103e3fd9f kfun:kotlinx.coroutines.JobSupport.finalizeFinishingState#internal + 1503 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/JobSupport.kt:230:59)
at 8 ktor_native.kexe 0x103e4baed kfun:kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath#internal + 2749 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/JobSupport.kt:909:16)
at 9 ktor_native.kexe 0x103e4afed kfun:kotlinx.coroutines.JobSupport.tryMakeCompleting#internal + 653 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/JobSupport.kt:866:16)
at 10 ktor_native.kexe 0x103e4ab2e kfun:kotlinx.coroutines.JobSupport#makeCompletingOnce(kotlin.Any?){}kotlin.Any? + 510 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/JobSupport.kt:831:30)
at 11 ktor_native.kexe 0x103e20a65 kfun:kotlinx.coroutines.AbstractCoroutine#resumeWith(kotlin.Result<1:0>){} + 309 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt:100:21)
at 12 ktor_native.kexe 0x103c5c8bd kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 1581 (/opt/buildAgent/work/f43969c6214a19e7/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:43:32)
at 13 ktor_native.kexe 0x103e92901 kfun:kotlinx.coroutines.DispatchedTask#run(){} + 3217 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt:104:58)
at 14 ktor_native.kexe 0x103e965b9 kfun:kotlinx.coroutines.internal.LimitedDispatcher.Worker.run#internal + 409 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt:115:33)
at 15 ktor_native.kexe 0x103ec2abf kfun:kotlinx.coroutines.MultiWorkerDispatcher.$workerRunLoop$lambda$2COROUTINE$0.invokeSuspend#internal + 2047 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt:101:38)
at 16 ktor_native.kexe 0x103c5c5de kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 846 (/opt/buildAgent/work/f43969c6214a19e7/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:30:39)
at 17 ktor_native.kexe 0x103e92a94 kfun:kotlinx.coroutines.DispatchedTask#run(){} + 3620 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt:106:71)
at 18 ktor_native.kexe 0x103e385e0 kfun:kotlinx.coroutines.EventLoopImplBase#processNextEvent(){}kotlin.Long + 1728 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/EventLoop.common.kt:280:44)
at 19 ktor_native.kexe 0x103ebabf5 kfun:kotlinx.coroutines.BlockingCoroutine.joinBlocking#internal + 581 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:120:43)
at 20 ktor_native.kexe 0x103eb9330 kfun:kotlinx.coroutines#runBlocking(kotlin.coroutines.CoroutineContext;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.CoroutineScope,0:0>){0§<kotlin.Any?>}0:0 + 2000 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:59:26)
at 21 ktor_native.kexe 0x103eb95e0 kfun:kotlinx.coroutines#runBlocking$default(kotlin.coroutines.CoroutineContext?;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.CoroutineScope,0:0>;kotlin.Int){0§<kotlin.Any?>}0:0 + 304 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:36:15)
at 22 ktor_native.kexe 0x103ec01ad kfun:kotlinx.coroutines.MultiWorkerDispatcher.workerRunLoop#internal + 205 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt:93:35)
at 23 ktor_native.kexe 0x103ec1e7b kfun:kotlinx.coroutines.MultiWorkerDispatcher.<init>$lambda$1$lambda$0#internal + 59 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt:80:28)
at 24 ktor_native.kexe 0x103ec37ef kfun:kotlinx.coroutines.MultiWorkerDispatcher.$<init>$lambda$1$lambda$0$FUNCTION_REFERENCE$4.invoke#internal + 63 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt:80:13)
at 25 ktor_native.kexe 0x103ec38bf kfun:kotlinx.coroutines.MultiWorkerDispatcher.$<init>$lambda$1$lambda$0$FUNCTION_REFERENCE$4.$<bridge-UNN>invoke(){}#internal + 63 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt:80:13)
at 26 ktor_native.kexe 0x103c68f7d WorkerLaunchpad + 189 (/opt/buildAgent/work/f43969c6214a19e7/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:107:54)
at 27 ktor_native.kexe 0x103e06fde _ZN6Worker19processQueueElementEb + 2734
at 28 ktor_native.kexe 0x103e06489 _ZN12_GLOBAL__N_113workerRoutineEPv + 121
at 29 libsystem_pthread.dylib 0x7ff8148a3258 _pthread_start + 124
at 30 libsystem_pthread.dylib 0x7ff81489ec7a thread_start + 14
The same code with the CIO engine running on JVM throws the UnresolvedAddressException:
Exception in thread "main" java.nio.channels.UnresolvedAddressException
at java.base/sun.nio.ch.Net.checkAddress(Net.java:131)
at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:673)
at io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:44)
at io.ktor.network.sockets.ConnectUtilsJvmKt.connect(ConnectUtilsJvm.kt:21)
at io.ktor.network.sockets.TcpSocketBuilder.connect(TcpSocketBuilder.kt:37)
at io.ktor.client.engine.cio.ConnectionFactory.connect(ConnectionFactory.kt:30)
at io.ktor.client.engine.cio.Endpoint$connect$2$connect$1.invokeSuspend(Endpoint.kt:207)
at io.ktor.client.engine.cio.Endpoint$connect$2$connect$1.invoke(Endpoint.kt)
at io.ktor.client.engine.cio.Endpoint$connect$2$connect$1.invoke(Endpoint.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:89)
at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:151)
at kotlinx.coroutines.TimeoutKt.withTimeoutOrNull(Timeout.kt:107)
at io.ktor.client.engine.cio.Endpoint.connect(Endpoint.kt:215)
at io.ktor.client.engine.cio.Endpoint.makeDedicatedRequest(Endpoint.kt:100)
at io.ktor.client.engine.cio.Endpoint.execute(Endpoint.kt:64)
at io.ktor.client.engine.cio.CIOEngine.execute(CIOEngine.kt:79)
at io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:99)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
For the rest of the engines (OkHttp, Java, Apache, Apache5, Android), the 200 status code is returned.
Darwin: New SSE handlers stop responding after canceling few SSE sessions
When using the Darwin engine on iOS (both simulator and actual devices), it sometimes gets stuck after closing an SSE session.
Steps to reproduce:
- Start a new SSE session and delay for about a second to ensure the connection is established
- Ideally, send some data through the session and log/print it to verify that everything works as expected
- Close the session, e.g. via cancel() or by canceling the containing coroutine scope
- Repeat until step 2 fails
It seems to work about 6 times before it fails. Failure means that the server no longer receives any requests from the client. Once this happens, no requests (not limited to SSE) are possible from the client instance.
The problem appears to be tied to a client instance, when one instance freezes, other existing and new instances continue to work normally.
This does not happen with the CIO engine. However, CIO is not a valid replacement due to its lack of HTTPS support on iOS.
A small example project demonstrating the issue based on the default multiplatform template is attached as zip. The relevant code is in the server's Application.kt and the composeApp's App.kt files.
Logging: SimpleLogger should be an object, not a class
What's the reason SimpleLogger is a class created everytime Logger.SIMPLE is called? It could be an object to avoid creating new instances of it every time.
Jetty Client: Support HTTP/2 over cleartext (h2c)
Currently the client engine throws an exception when trying to call an h2c endpoint:
HttpClient(Jetty).get("http://localhost:8084/content/hello") // Throws
Unrecognized SSL message, plaintext connection?
javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:42)
at io.ktor.client.engine.jetty.jakarta.JettyHttpRequestKt.executeRequest(JettyHttpRequest.kt:41)
at io.ktor.client.engine.jetty.jakarta.JettyHttp2Engine.execute(JettyHttp2Engine.kt:33)
at io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:183)
at io.ktor.client.engine.HttpClientEngine.executeWithinCallContext(HttpClientEngine.kt:184)
at io.ktor.client.engine.HttpClientEngine$install$1.invokeSuspend(HttpClientEngine.kt:154)
at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
at io.ktor.client.plugins.HttpSend$DefaultSender.execute(HttpSend.kt:137)
... 22 more
Caused by: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
at java.base/sun.security.ssl.SSLEngineInputRecord.bytesInCompletePacket(SSLEngineInputRecord.java:145)
at java.base/sun.security.ssl.SSLEngineInputRecord.bytesInCompletePacket(SSLEngineInputRecord.java:64)
at java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:612)
at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:506)
at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:482)
at java.base/javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:679)
at org.eclipse.jetty.io.ssl.SslConnection.unwrap(SslConnection.java:432)
at org.eclipse.jetty.io.ssl.SslConnection$SslEndPoint.fill(SslConnection.java:779)
Suppressed: java.io.IOException: Broken pipe
at org.eclipse.jetty.io.ssl.SslConnection$SslEndPoint.flush(SslConnection.java:1193)
at org.eclipse.jetty.io.WriteFlusher.flush(WriteFlusher.java:419)
at org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:272)
at org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:251)
at org.eclipse.jetty.io.AbstractEndPoint.write(AbstractEndPoint.java:368)
at org.eclipse.jetty.http2.internal.HTTP2Flusher.process(HTTP2Flusher.java:331)
at org.eclipse.jetty.util.IteratingCallback.processing(IteratingCallback.java:253)
at org.eclipse.jetty.util.IteratingCallback.iterate(IteratingCallback.java:232)
at org.eclipse.jetty.http2.HTTP2Session.frame(HTTP2Session.java:956)
at org.eclipse.jetty.http2.HTTP2Session.frames(HTTP2Session.java:929)
at org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory$HTTP2ClientConnection.onOpen(HTTP2ClientConnectionFactory.java:155)
at org.eclipse.jetty.io.AbstractEndPoint.upgrade(AbstractEndPoint.java:435)
at org.eclipse.jetty.io.NegotiatingClientConnection.replaceConnection(NegotiatingClientConnection.java:117)
at org.eclipse.jetty.io.NegotiatingClientConnection.onFillable(NegotiatingClientConnection.java:87)
... 13 more
The solution is not to pass sslContextFactory when calling HTTP2Client.connect
Java, ContentEncoding: IllegalHeaderNameException is thrown for ":status" pseudo header with HTTP/2
My understanding is that ':status' is an HTTP/2 pseudo header (mdn)
Exception:
io.ktor.http.IllegalHeaderNameException: Header name ':status' contains illegal character ':' (code 58)
at io.ktor.http.HttpHeaders.checkHeaderName(HttpHeaders.kt:161)
at io.ktor.http.HeadersBuilder.validateName(Headers.kt:41)
at io.ktor.util.StringValuesBuilderImpl.ensureListForKey(StringValues.kt:311)
at io.ktor.util.StringValuesBuilderImpl.appendAll(StringValues.kt:271)
at io.ktor.client.plugins.compression.ContentEncodingKt.ContentEncoding$lambda$9$decode$lambda$7$lambda$5(ContentEncoding.kt:160)
Ktor 3.3.0
HttpClient setup. The error occurs when using ContentEncoding but I assume it would always occur if the headers are accessed.
val client = HttpClient(Java) {
install(ContentEncoding) {
deflate(1.0F)
gzip(0.9F)
}
engine {
pipelining = true
protocolVersion = java.net.http.HttpClient.Version.HTTP_2
}
}
Header from debug watch
Curl: Client sends both Transfer-Encoding and Content-Length headers for DELETE requests with body
Send a DELETE request, and the server returns a 400 error. Through packet capture analysis with Wireshark, it is found that the cause is that the request header contains both Transfer-Encoding and Content-Length. According to the HTTP specification, these two headers should not be set simultaneously; if they are set together, some servers will return a 400 error. This problem can be solved by modifying the Ktor source code, and the modification location is as follows:
Exception handling issue in client cache
Compiler Plugin
OpenAPI gen: missing operationId for KDoc fields
I should be able to specify @operationId getWidgets and have it be included in the final spec.
OpenAPI: StackOverflowError when a response object has property with @Contextual serializer
Getting a StackOverflowError inside JsonSchema when trying out the new open api generator
e: Daemon compilation failed: null
java.lang.Exception
at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69)
at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: java.lang.AssertionError: Cannot add a performance measurements because it's already finalized
at org.jetbrains.kotlin.com.intellij.openapi.diagnostic.DefaultLogger.error(DefaultLogger.java:83)
at org.jetbrains.kotlin.com.intellij.openapi.diagnostic.Logger.error(Logger.java:436)
at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.handleExceptions(ObjectTree.java:195)
at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.runWithTrace(ObjectTree.java:141)
at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.executeAll(ObjectTree.java:162)
at org.jetbrains.kotlin.com.intellij.openapi.util.Disposer.dispose(Disposer.java:205)
at org.jetbrains.kotlin.com.intellij.openapi.util.Disposer.dispose(Disposer.java:193)
at org.jetbrains.kotlin.cli.common.UtilsKt.disposeRootInWriteAction$lambda$0(utils.kt:142)
at org.jetbrains.kotlin.com.intellij.openapi.application.ActionsKt.runWriteAction$lambda$0(actions.kt:16)
at org.jetbrains.kotlin.com.intellij.mock.MockApplication.runWriteAction(MockApplication.java:209)
at org.jetbrains.kotlin.com.intellij.openapi.application.ActionsKt.runWriteAction(actions.kt:16)
at org.jetbrains.kotlin.cli.common.UtilsKt.disposeRootInWriteAction(utils.kt:141)
at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.execute(AbstractCliPipeline.kt:93)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:79)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:45)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:90)
at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:352)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunnerBase.runCompiler(IncrementalJvmCompilerRunnerBase.kt:176)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunnerBase.runCompiler(IncrementalJvmCompilerRunnerBase.kt:39)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:499)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:416)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:128)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:684)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:94)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1810)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
... 3 more
Caused by: java.lang.AssertionError: Cannot add a performance measurements because it's already finalized
at org.jetbrains.kotlin.util.PerformanceManager.ensureNotFinalizedAndSameThread(PerformanceManager.kt:382)
at org.jetbrains.kotlin.util.PerformanceManager.measureSideTime$compiler_common(PerformanceManager.kt:305)
at org.jetbrains.kotlin.util.PerformanceManagerKt.tryMeasureSideTime(PerformanceManager.kt:406)
at org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass$Factory.create$frontend_common_jvm(VirtualFileKotlinClass.kt:73)
at org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache$Companion.getKotlinBinaryClassOrClassFileContent$lambda$0(KotlinBinaryClassCache.kt:98)
at org.jetbrains.kotlin.com.intellij.mock.MockApplication.runReadAction(MockApplication.java:194)
at org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache$Companion.getKotlinBinaryClassOrClassFileContent(KotlinBinaryClassCache.kt:97)
at org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache$Companion.getKotlinBinaryClassOrClassFileContent$default(KotlinBinaryClassCache.kt:78)
at org.jetbrains.kotlin.load.kotlin.VirtualFileFinder.findKotlinClassOrContent(VirtualFileFinder.kt:39)
at org.jetbrains.kotlin.fir.java.deserialization.JvmClassFileBasedSymbolProvider.extractClassMetadata(JvmClassFileBasedSymbolProvider.kt:171)
at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.findAndDeserializeClass(AbstractFirDeserializedSymbolProvider.kt:256)
at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.classCache$lambda$0(AbstractFirDeserializedSymbolProvider.kt:163)
at org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCacheWithPostCompute.getValue(FirThreadUnsafeCachesFactory.kt:75)
at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.getClass(AbstractFirDeserializedSymbolProvider.kt:343)
at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.getClass$default(AbstractFirDeserializedSymbolProvider.kt:326)
at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.getClassLikeSymbolByClassId(AbstractFirDeserializedSymbolProvider.kt:420)
at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider.computeClass(FirCachingCompositeSymbolProvider.kt:147)
at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider.access$computeClass(FirCachingCompositeSymbolProvider.kt:27)
at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider$special$$inlined$createCache$1.invoke(FirCachesFactory.kt:163)
at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider$special$$inlined$createCache$1.invoke(FirCachesFactory.kt:147)
at org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCache.getValue(FirThreadUnsafeCachesFactory.kt:57)
at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider.getClassLikeSymbolByClassId(FirCachingCompositeSymbolProvider.kt:174)
at org.jetbrains.kotlin.fir.resolve.ToSymbolUtilsKt.toSymbol(ToSymbolUtils.kt:57)
at org.jetbrains.kotlin.fir.types.ConeTypeContext.toClassLikeSymbol(ConeTypeContext.kt:260)
at org.jetbrains.kotlin.fir.types.ConeTypeContext.getParameters(ConeTypeContext.kt:252)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.toIndexed$lambda$0$0(AbstractSignatureParts.kt:242)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.flattenTree(AbstractSignatureParts.kt:228)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.flattenTree(AbstractSignatureParts.kt:232)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.toIndexed(AbstractSignatureParts.kt:238)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.computeIndexedQualifiers(AbstractSignatureParts.kt:201)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.computeIndexedQualifiers$default(AbstractSignatureParts.kt:196)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhance(SignatureEnhancement.kt:1015)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhance(SignatureEnhancement.kt:1006)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhanceReturnType(SignatureEnhancement.kt:902)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhanceReturnType(SignatureEnhancement.kt:851)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhance$fir_jvm(SignatureEnhancement.kt:169)
at org.jetbrains.kotlin.fir.java.enhancement.FirEnhancedSymbolsStorage$EnhancementSymbolsCache.enhancedVariables$lambda$0(SignatureEnhancement.kt:1152)
at org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCache.getValue(FirThreadUnsafeCachesFactory.kt:57)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhancedProperty(SignatureEnhancement.kt:137)
at org.jetbrains.kotlin.fir.java.scopes.JavaClassMembersEnhancementScope.processPropertiesByName$lambda$0(JavaClassMembersEnhancementScope.kt:33)
at org.jetbrains.kotlin.fir.scopes.impl.AbstractFirUseSiteMemberScope.processPropertiesByName(AbstractFirUseSiteMemberScope.kt:159)
at org.jetbrains.kotlin.fir.java.scopes.JavaClassMembersEnhancementScope.processPropertiesByName(JavaClassMembersEnhancementScope.kt:32)
at org.jetbrains.kotlin.fir.scopes.impl.FirScopeWithCallableCopyReturnTypeUpdater.processPropertiesByName(FirScopeWithCallableCopyReturnTypeUpdater.kt:40)
at org.jetbrains.kotlin.fir.scopes.FirContainingNamesAwareScopeKt.processAllProperties(FirContainingNamesAwareScope.kt:30)
at io.ktor.compiler.utils.PropertiesUtilsKt.getAllPropertiesFromType(PropertiesUtils.kt:42)
at io.ktor.openapi.model.JsonSchema$Companion.schemaDefinitionForType(JsonSchema.kt:89)
at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:71)
at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema$default(JsonSchema.kt:50)
at io.ktor.openapi.model.JsonSchema$Companion.schemaDefinitionForType(JsonSchema.kt:91)
at io.ktor.openapi.model.JsonSchema$Companion.findSchemaDefinitions(JsonSchema.kt:44)
at io.ktor.openapi.routing.interpreters.CallRespondInterpreter.check$lambda$0(CallRespondInterpreter.kt:43)
at io.ktor.openapi.routing.RouteNode.resolve(RouteNode.kt:16)
at io.ktor.openapi.routing.RouteNode$CallFeature.resolve(RouteNode.kt:58)
at io.ktor.openapi.routing.RouteCollector.resolve(RouteCollector.kt:42)
at io.ktor.openapi.routing.RouteCollector.collectRoutes(RouteCollector.kt:17)
at io.ktor.openapi.OpenApiSpecGenerator.buildSpecification(OpenApiSpecGenerator.kt:17)
at io.ktor.openapi.OpenApiExtension.saveSpecification(OpenApiExtension.kt:59)
at io.ktor.compiler.KtorCompilerPluginRegistrar.registerExtensions$lambda$0(KtorCompilerPluginRegistrar.kt:27)
at org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrarKt.registerInProject$lambda$1(CompilerPluginRegistrar.kt:72)
at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.runWithTrace(ObjectTree.java:130)
... 39 more
Failed to compile with Kotlin daemon: java.lang.Exception
at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69)
at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: java.lang.AssertionError: Cannot add a performance measurements because it's already finalized
at org.jetbrains.kotlin.com.intellij.openapi.diagnostic.DefaultLogger.error(DefaultLogger.java:83)
at org.jetbrains.kotlin.com.intellij.openapi.diagnostic.Logger.error(Logger.java:436)
at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.handleExceptions(ObjectTree.java:195)
at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.runWithTrace(ObjectTree.java:141)
at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.executeAll(ObjectTree.java:162)
at org.jetbrains.kotlin.com.intellij.openapi.util.Disposer.dispose(Disposer.java:205)
at org.jetbrains.kotlin.com.intellij.openapi.util.Disposer.dispose(Disposer.java:193)
at org.jetbrains.kotlin.cli.common.UtilsKt.disposeRootInWriteAction$lambda$0(utils.kt:142)
at org.jetbrains.kotlin.com.intellij.openapi.application.ActionsKt.runWriteAction$lambda$0(actions.kt:16)
at org.jetbrains.kotlin.com.intellij.mock.MockApplication.runWriteAction(MockApplication.java:209)
at org.jetbrains.kotlin.com.intellij.openapi.application.ActionsKt.runWriteAction(actions.kt:16)
at org.jetbrains.kotlin.cli.common.UtilsKt.disposeRootInWriteAction(utils.kt:141)
at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.execute(AbstractCliPipeline.kt:93)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:79)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:45)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:90)
at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:352)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunnerBase.runCompiler(IncrementalJvmCompilerRunnerBase.kt:176)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunnerBase.runCompiler(IncrementalJvmCompilerRunnerBase.kt:39)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:499)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:416)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:128)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:684)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:94)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1810)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
... 3 more
Caused by: java.lang.AssertionError: Cannot add a performance measurements because it's already finalized
at org.jetbrains.kotlin.util.PerformanceManager.ensureNotFinalizedAndSameThread(PerformanceManager.kt:382)
at org.jetbrains.kotlin.util.PerformanceManager.measureSideTime$compiler_common(PerformanceManager.kt:305)
at org.jetbrains.kotlin.util.PerformanceManagerKt.tryMeasureSideTime(PerformanceManager.kt:406)
at org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass$Factory.create$frontend_common_jvm(VirtualFileKotlinClass.kt:73)
at org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache$Companion.getKotlinBinaryClassOrClassFileContent$lambda$0(KotlinBinaryClassCache.kt:98)
at org.jetbrains.kotlin.com.intellij.mock.MockApplication.runReadAction(MockApplication.java:194)
at org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache$Companion.getKotlinBinaryClassOrClassFileContent(KotlinBinaryClassCache.kt:97)
at org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache$Companion.getKotlinBinaryClassOrClassFileContent$default(KotlinBinaryClassCache.kt:78)
at org.jetbrains.kotlin.load.kotlin.VirtualFileFinder.findKotlinClassOrContent(VirtualFileFinder.kt:39)
at org.jetbrains.kotlin.fir.java.deserialization.JvmClassFileBasedSymbolProvider.extractClassMetadata(JvmClassFileBasedSymbolProvider.kt:171)
at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.findAndDeserializeClass(AbstractFirDeserializedSymbolProvider.kt:256)
at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.classCache$lambda$0(AbstractFirDeserializedSymbolProvider.kt:163)
at org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCacheWithPostCompute.getValue(FirThreadUnsafeCachesFactory.kt:75)
at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.getClass(AbstractFirDeserializedSymbolProvider.kt:343)
at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.getClass$default(AbstractFirDeserializedSymbolProvider.kt:326)
at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.getClassLikeSymbolByClassId(AbstractFirDeserializedSymbolProvider.kt:420)
at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider.computeClass(FirCachingCompositeSymbolProvider.kt:147)
at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider.access$computeClass(FirCachingCompositeSymbolProvider.kt:27)
at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider$special$$inlined$createCache$1.invoke(FirCachesFactory.kt:163)
at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider$special$$inlined$createCache$1.invoke(FirCachesFactory.kt:147)
at org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCache.getValue(FirThreadUnsafeCachesFactory.kt:57)
at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider.getClassLikeSymbolByClassId(FirCachingCompositeSymbolProvider.kt:174)
at org.jetbrains.kotlin.fir.resolve.ToSymbolUtilsKt.toSymbol(ToSymbolUtils.kt:57)
at org.jetbrains.kotlin.fir.types.ConeTypeContext.toClassLikeSymbol(ConeTypeContext.kt:260)
at org.jetbrains.kotlin.fir.types.ConeTypeContext.getParameters(ConeTypeContext.kt:252)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.toIndexed$lambda$0$0(AbstractSignatureParts.kt:242)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.flattenTree(AbstractSignatureParts.kt:228)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.flattenTree(AbstractSignatureParts.kt:232)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.toIndexed(AbstractSignatureParts.kt:238)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.computeIndexedQualifiers(AbstractSignatureParts.kt:201)
at org.jetbrains.kotlin.load.java.typeEnhancement.AbstractSignatureParts.computeIndexedQualifiers$default(AbstractSignatureParts.kt:196)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhance(SignatureEnhancement.kt:1015)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhance(SignatureEnhancement.kt:1006)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhanceReturnType(SignatureEnhancement.kt:902)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhanceReturnType(SignatureEnhancement.kt:851)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhance$fir_jvm(SignatureEnhancement.kt:169)
at org.jetbrains.kotlin.fir.java.enhancement.FirEnhancedSymbolsStorage$EnhancementSymbolsCache.enhancedVariables$lambda$0(SignatureEnhancement.kt:1152)
at org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCache.getValue(FirThreadUnsafeCachesFactory.kt:57)
at org.jetbrains.kotlin.fir.java.enhancement.FirSignatureEnhancement.enhancedProperty(SignatureEnhancement.kt:137)
at org.jetbrains.kotlin.fir.java.scopes.JavaClassMembersEnhancementScope.processPropertiesByName$lambda$0(JavaClassMembersEnhancementScope.kt:33)
at org.jetbrains.kotlin.fir.scopes.impl.AbstractFirUseSiteMemberScope.processPropertiesByName(AbstractFirUseSiteMemberScope.kt:159)
at org.jetbrains.kotlin.fir.java.scopes.JavaClassMembersEnhancementScope.processPropertiesByName(JavaClassMembersEnhancementScope.kt:32)
at org.jetbrains.kotlin.fir.scopes.impl.FirScopeWithCallableCopyReturnTypeUpdater.processPropertiesByName(FirScopeWithCallableCopyReturnTypeUpdater.kt:40)
at org.jetbrains.kotlin.fir.scopes.FirContainingNamesAwareScopeKt.processAllProperties(FirContainingNamesAwareScope.kt:30)
at io.ktor.compiler.utils.PropertiesUtilsKt.getAllPropertiesFromType(PropertiesUtils.kt:42)
at io.ktor.openapi.model.JsonSchema$Companion.schemaDefinitionForType(JsonSchema.kt:89)
at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:71)
at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema$default(JsonSchema.kt:50)
at io.ktor.openapi.model.JsonSchema$Companion.schemaDefinitionForType(JsonSchema.kt:91)
at io.ktor.openapi.model.JsonSchema$Companion.findSchemaDefinitions(JsonSchema.kt:44)
at io.ktor.openapi.routing.interpreters.CallRespondInterpreter.check$lambda$0(CallRespondInterpreter.kt:43)
at io.ktor.openapi.routing.RouteNode.resolve(RouteNode.kt:16)
at io.ktor.openapi.routing.RouteNode$CallFeature.resolve(RouteNode.kt:58)
at io.ktor.openapi.routing.RouteCollector.resolve(RouteCollector.kt:42)
at io.ktor.openapi.routing.RouteCollector.collectRoutes(RouteCollector.kt:17)
at io.ktor.openapi.OpenApiSpecGenerator.buildSpecification(OpenApiSpecGenerator.kt:17)
at io.ktor.openapi.OpenApiExtension.saveSpecification(OpenApiExtension.kt:59)
at io.ktor.compiler.KtorCompilerPluginRegistrar.registerExtensions$lambda$0(KtorCompilerPluginRegistrar.kt:27)
at org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrarKt.registerInProject$lambda$1(CompilerPluginRegistrar.kt:72)
at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.runWithTrace(ObjectTree.java:130)
... 39 more
Using fallback strategy: Compile without Kotlin daemon
Try ./gradlew --stop if this issue persists
If it does not look related to your configuration, please file an issue with logs to https://kotl.in/issue
exception: ERROR: null
exception: java.lang.StackOverflowError
exception: at org.jetbrains.kotlin.fir.types.ConeTypeUtilsKt.withArguments(ConeTypeUtils.kt:148)
exception: at io.ktor.compiler.utils.FirScopedEvaluator.resolveType(ScopedExpressionEvaluator.kt:234)
exception: at io.ktor.compiler.utils.FirScopedEvaluator.resolveType(ScopedExpressionEvaluator.kt:239)
exception: at io.ktor.compiler.utils.FirScopedEvaluator.resolveTypeProjection(ScopedExpressionEvaluator.kt:182)
exception: at io.ktor.openapi.routing.RouteStackKt.resolveType(RouteStack.kt:41)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
...
exception: exception in thread "main" java.lang.AssertionError
exception: at org.jetbrains.kotlin.com.intellij.openapi.diagnostic.DefaultLogger.error(DefaultLogger.java:83)
exception: at org.jetbrains.kotlin.com.intellij.openapi.diagnostic.Logger.error(Logger.java:436)
exception: at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.handleExceptions(ObjectTree.java:195)
exception: at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.runWithTrace(ObjectTree.java:141)
exception: at org.jetbrains.kotlin.com.intellij.openapi.util.ObjectTree.executeAll(ObjectTree.java:162)
exception: at org.jetbrains.kotlin.com.intellij.openapi.util.Disposer.dispose(Disposer.java:205)
exception: at org.jetbrains.kotlin.com.intellij.openapi.util.Disposer.dispose(Disposer.java:193)
exception: at org.jetbrains.kotlin.cli.common.UtilsKt.disposeRootInWriteAction$lambda$0(utils.kt:142)
exception: at org.jetbrains.kotlin.com.intellij.openapi.application.ActionsKt.runWriteAction$lambda$0(actions.kt:16)
exception: at org.jetbrains.kotlin.com.intellij.mock.MockApplication.runWriteAction(MockApplication.java:209)
exception: at org.jetbrains.kotlin.com.intellij.openapi.application.ActionsKt.runWriteAction(actions.kt:16)
exception: at org.jetbrains.kotlin.cli.common.UtilsKt.disposeRootInWriteAction(utils.kt:141)
exception: at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.execute(AbstractCliPipeline.kt:93)
exception: at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:79)
exception: at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:45)
exception: at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:90)
exception: at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:352)
exception: at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:330)
exception: at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:294)
exception: at org.jetbrains.kotlin.cli.common.CLICompiler$Companion.doMainNoExit(CLICompiler.kt:431)
exception: at org.jetbrains.kotlin.cli.common.CLICompiler$Companion.doMainNoExit$default(CLICompiler.kt:426)
exception: at org.jetbrains.kotlin.cli.common.CLICompiler$Companion.doMain(CLICompiler.kt:418)
exception: at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler$Companion.main(K2JVMCompiler.kt:252)
exception: at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.main(K2JVMCompiler.kt)
exception: caused by: java.lang.StackOverflowError
exception: at org.jetbrains.kotlin.fir.types.ConeTypeUtilsKt.withArguments(ConeTypeUtils.kt:148)
exception: at io.ktor.compiler.utils.FirScopedEvaluator.resolveType(ScopedExpressionEvaluator.kt:234)
exception: at io.ktor.compiler.utils.FirScopedEvaluator.resolveType(ScopedExpressionEvaluator.kt:239)
exception: at io.ktor.compiler.utils.FirScopedEvaluator.resolveTypeProjection(ScopedExpressionEvaluator.kt:182)
exception: at io.ktor.openapi.routing.RouteStackKt.resolveType(RouteStack.kt:41)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
exception: at io.ktor.openapi.model.JsonSchema$Companion.asJsonSchema(JsonSchema.kt:52)
...
Server
Double ResponseSent invocation when exception is thrown after respond
When a route calls call.respond(...) and then throws an exception, Ktor retries the send pipeline via handleFailure, causing the ResponseSent plugin hook to execute a second time. This makes ResponseSent fire more than once for a single request:
@Test
fun testResponseSentCalledOnce() = runTest {
var responseSentCalled = 0
createAndStartServer {
serverConfig {
enableSsl = false
enableHttp2 = false
}
install(
createRouteScopedPlugin("Plugin") {
on(ResponseSent) {
responseSentCalled++
}
}
)
get("/") {
call.respond("ok")
throw ExpectedTestException("exception")
}
}
withUrl("/") {
assertEquals(HttpStatusCode.OK, status)
assertEquals("ok", bodyAsText())
}
delay(500)
assertEquals(1, responseSentCalled)
}
Improve logging for CORS plugin
See https://youtrack.jetbrains.com/issue/KTOR-3014 - the lack of proper logging made debugging time longer. Looking at the source of the CORS feature, it has several error modes where it responds respondCorsFailed() or respond(HttpStatusCode.Forbidden). For example, one of such cases is where this method returns false:
private fun corsCheckOrigins(origin: String): Boolean {
return allowsAnyHost || normalizeOrigin(origin) in hostsNormalized
}
As a user of ktor, I'd expect helpful log messages that would suggest what needs to be fixed, like CORS failed because given origin 'http://foobar.com' is not in allowed hosts: ['http://baz.com', 'http://boo.com'].
NettyHttp2Handler throws IllegalArgumentException: 'ktor.ApplicationCall' is already in use
In some specific circumstances NettyHttp2Handler fails to initialize because of an exception thrown on AttributeKey.newInstance call.
Failed to initialize a channel. Closing: [id: 0xbd290e81, L:/127.0.0.1:8084 - R:/127.0.0.1:53580]
java.lang.NoClassDefFoundError: Could not initialize class io.ktor.server.netty.http2.NettyHttp2Handler
at io.ktor.server.netty.NettyChannelInitializer.configurePipeline(NettyChannelInitializer.kt:202)
at io.ktor.server.netty.NettyChannelInitializer.initChannel(NettyChannelInitializer.kt:153)
at io.ktor.server.netty.NettyChannelInitializer.initChannel(NettyChannelInitializer.kt:51)
at io.netty.channel.ChannelInitializer.initChannel(ChannelInitializer.java:129)
... 13 more
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.IllegalArgumentException: 'ktor.ApplicationCall' is already in use [in thread "eventLoopGroupProxy-9-1"]
at io.netty.util.ConstantPool.createOrThrow(ConstantPool.java:108)
at io.netty.util.ConstantPool.newInstance(ConstantPool.java:90)
at io.netty.util.AttributeKey.newInstance(AttributeKey.java:55)
at io.ktor.server.netty.http2.NettyHttp2Handler.<clinit>(NettyHttp2Handler.kt:215)
... 22 more
NettyHttp2Handler calls AttributeKey.newInstance during companion object initialization and AttributeKey.newInstance adds the key to a static ConstantPool.
Conditions:
The problem can be reproduced when multiple ClassLoaders are involved:
ClassLoaderA (parent) - loads netty-common (AttributeKey, ConstantPool)
├── ClassLoaderB (child) - loads ktor-server-netty (NettyHttp2Handler)
└── ClassLoaderC (child) - loads ktor-server-netty (NettyHttp2Handler)
- The class
AttributeKey(netty-common) is loaded by ClassLoaderA (parent/shared classloader). - The class
NettyHttp2Handler(ktor-server-netty) is loaded by ClassLoaderB NettyHttp2Handlercompanion object initializes, callingAttributeKey.newInstance, which registers the key in the sharedConstantPool(static field inClassLoaderA)- The class
NettyHttp2Handleris also loaded byClassLoaderC(a sibling classloader) NettyHttp2Handlerin ClassLoaderC tries to initialize its companion object, callingAttributeKey.newInstanceagain- Since
ConstantPoolis shared (in parent ClassLoaderA), the key name is already registered →IllegalArgumentException
The real scenario is using Netty engine in a Gradle's shared BuildService which is used to run tests on a module including ktor-server-netty as a dependency:
- ClassLoaderA (Gradle core/common): loads Netty's
AttributeKeyandConstantPool - ClassLoaderB (Gradle plugin classloader): loads
ktor-server-nettyfor the BuildService - ClassLoaderC (Test execution classloader): loads
ktor-server-nettyfrom test dependencies
Test Infrastructure
ktor-test-server: Support HTTP/2
We should introduce HTTP/2 support on the test server to be able to test HTTP/2 client features.
Note: It is about the test server used to test Ktor itself, not ktor-server-test-host.
3.3.2
released 5th November 2025
Client
HttpRequestRetry: SendCountExceedException when max retries is more than maxSendCount of HttpSend
HttpRequestRetry doesn't take into account HttpSend.maxSendCount. This is reducible with following scenario where the maxRetries count is larger than 20.
testApplication {
routing {
get("/") {
call.respond(OK)
}
}
var counter = 0
val client = createClient {
install(HttpRequestRetry) {
delayMillis { 0L }
retryIf(25) { _, _ ->
counter++
counter <= 25
}
}
}
val response = client.get("/")
response.status shouldBe OK
}
results in following stacktrace:
io.ktor.client.plugins.SendCountExceedException: Max send count 20 exceeded. Consider increasing the property maxSendCount if more is required.
at app//io.ktor.client.plugins.HttpSend$DefaultSender.execute(HttpSend.kt:131)
at app//io.ktor.client.plugins.HttpRequestRetry$intercept$1.invokeSuspend(HttpRequestRetry.kt:298)
at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at app//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at app//kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284)
at app//kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at app//kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at app//kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at app//io.ktor.server.testing.TestApplicationKt.testApplication(TestApplication.kt:329)
at app//io.ktor.server.testing.TestApplicationKt.testApplication(TestApplication.kt:287)
Darwin: The `maxFrameSize` option has no effect
Ktor version 2.3.9 / Darwin engine / iOS
We have a potentially large chunk of binary data sent in one frame over websocket, and rejected by Network framework and the maxFrameSize is taking no effect on changing the actual allowed frame size. Testing it in swift reveals that maximumMessageSize of NSURLSessionWebSocketTask works, but the same thing applied in ktor through maxFrameSize returns same issue and same max size as default(1048576 bytes). Our suspicion, is that the value is overridden somewhere between session creation and message exchange, but it’s hard to debug jar binaries(at least with my iOS background).
Sample app:
https://github.com/chaserunner/ktor-websocket-ios
Add Socks proxy support to Darwin engine
from what it seems, socks proxy is supported on iOS 17.0+
can we have the feature enabled on Darwin engine ?
HttpCache: FileStorage doesn't use given dispatcher for all file operations
The fun FileStorage documentation states that the given dispatcher is used for file operations.
But the underlying FileCacheStorage only uses it for the store function. Other functions like find , which ultimately call into readCacheUnsafe performing file operations, don't use the dispatcher at all. This doesn't align with the documented behaviour and causes issues with Android's StrictMode (DiskReadViolations).
Curl: SOCKS proxy doesn't work
@Test
fun testSocksProxy() = clientTests(only("Curl")) {
config {
engine {
proxy = ProxyBuilder.socks("127.0.0.1", 8083)
}
}
test { client ->
val response = client.get("http://google.com").body<String>()
assertEquals("proxy", response)
}
}
Throws an exception:
kotlin.IllegalStateException: Connection failed for request: CurlRequestData(url='http://google.com', method='GET', content: 0 bytes). Reason: proxy handshake error
at kotlin.Throwable#<init>(/opt/buildAgent/work/3b4e5b3c1f97a79b/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:30)
at kotlin.Exception#<init>(/opt/buildAgent/work/3b4e5b3c1f97a79b/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:23)
at kotlin.RuntimeException#<init>(/opt/buildAgent/work/3b4e5b3c1f97a79b/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:34)
at kotlin.IllegalStateException#<init>(/opt/buildAgent/work/3b4e5b3c1f97a79b/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:70)
at io.ktor.client.engine.curl.internal.CurlMultiApiHandler.collectFailedResponse#internal(/Users/Osip.Fatkullin/Developer/jb/ktor/ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/internal/CurlMultiApiHandler.kt:291)
at io.ktor.client.engine.curl.internal.CurlMultiApiHandler.processCompletedEasyHandle#internal(/Users/Osip.Fatkullin/Developer/jb/ktor/ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/internal/CurlMultiApiHandler.kt:258)
at io.ktor.client.engine.curl.internal.CurlMultiApiHandler.handleCompleted#internal(/Users/Osip.Fatkullin/Developer/jb/ktor/ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/internal/CurlMultiApiHandler.kt:202)
at io.ktor.client.engine.curl.internal.CurlMultiApiHandler#perform(/Users/Osip.Fatkullin/Developer/jb/ktor/ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/internal/CurlMultiApiHandler.kt:141)
The root cause is that ProxyConfig.toString() returns a string starting with schema socks:// for SOCKS proxy. And this is not a valid schema. Curl is expecting a schema with version, like socks5:// or socks4://
Java: Improve error message when SOCKS proxy is used
Trying to send http request with socks proxy but got an error: "Tunnel failed, got: 501".
Proxy config:
val socksProxy = ProxyBuilder.socks(host = "localhost", port = 9050)
Doesn't work:
fun ktorRequest(socksProxy: ProxyConfig) {
val client = HttpClient { Java
engine { proxy = socksProxy
} install(WebSockets)
} runBlocking { val response: HttpResponse = client.get("https://ipwhois.app/json/")
val content: String = response.receive()
println(content)
}
}
Works:
fun javaRequest(socksProxy: ProxyConfig) {
val url = URL("https://ipwhois.app/json/")
val conn: URLConnection = url.openConnection(socksProxy)
val reader = BufferedReader(InputStreamReader(conn.getInputStream()))
println(reader.readLine())
}
Trace:
Exception in thread "main" java.io.IOException: Tunnel failed, got: 501
at java.net.http/jdk.internal.net.http.PlainTunnelingConnection.lambda$connectAsync$2(PlainTunnelingConnection.java:96)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1146)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
at java.base/java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:610)
at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:649)
at java.base/java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:478)
at java.net.http/jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:155)
at java.base/java.util.concurrent.CompletableFuture$UniCompletion.claim(CompletableFuture.java:568)
at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:638)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2143)
at java.net.http/jdk.internal.net.http.Http1Response$HeadersReader.handle(Http1Response.java:695)
at java.net.http/jdk.internal.net.http.Http1Response$HeadersReader.handle(Http1Response.java:621)
at java.net.http/jdk.internal.net.http.Http1Response$Receiver.accept(Http1Response.java:612)
at java.net.http/jdk.internal.net.http.Http1Response$HeadersReader.tryAsyncReceive(Http1Response.java:668)
at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.flush(Http1AsyncReceiver.java:233)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
at java.base/java.lang.Thread.run(Thread.java:831)
Is there any guide on how to use socks proxy with some of the supported engines at jvm?
Response body channel is canceled while the body is being saved when having HttpRequestRetry and onDownload
Since Ktor client 3.3.0 (also in 3.3.1) I get the following error in my code when I try to download a resource from an URL.
2025-10-16T07:56:54.597+0200 ERROR AIPModelImpl - Initialization: Could not download resource '`[`https://mpmediasoft.de/OpenData/AIPBrowserDE/V2/IFR/aip-de-ifr-core-model-metadata.json`](https://mpmediasoft.de/OpenData/AIPBrowserDE/V2/IFR/aip-de-ifr-core-model-metadata.json)`': Channel was cancelled
2025-10-16T07:56:54.597+0200 ERROR ResourceDownloaderKt - Exception while downloading resource '`[`https://mpmediasoft.de/OpenData/AIPBrowserDE/V2/IFR/aip-de-ifr-core-model-metadata.json`](https://mpmediasoft.de/OpenData/AIPBrowserDE/V2/IFR/aip-de-ifr-core-model-metadata.json)`'.
io.ktor.utils.io.ClosedByteChannelException: Channel was cancelled
at io.ktor.utils.io.CloseToken$wrapCause$1.invoke(CloseToken.kt:16)
at io.ktor.utils.io.CloseToken$wrapCause$1.invoke(CloseToken.kt:16)
at io.ktor.utils.io.CloseToken.wrapCause(CloseToken.kt:21)
at io.ktor.utils.io.CloseToken.wrapCause$default(CloseToken.kt:16)
at io.ktor.utils.io.ByteChannel.getClosedCause(ByteChannel.kt:61)
at io.ktor.utils.io.ByteReadChannelOperationsKt.rethrowCloseCauseIfNeeded(ByteReadChannelOperations.kt:544)
at io.ktor.utils.io.ByteReadChannelOperationsKt.readRemaining(ByteReadChannelOperations.kt:222)
at io.ktor.client.call.SavedCallKt.save(SavedCall.kt:38)
at io.ktor.client.statement.HttpStatement.fetchResponse(HttpStatement.kt:166)
at io.ktor.client.statement.HttpStatement$fetchResponse$1.invokeSuspend(HttpStatement.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:829)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
Caused by: java.io.IOException: Channel was cancelled
at io.ktor.utils.io.ByteReadChannelKt.cancel(ByteReadChannel.kt:58)
at io.ktor.client.plugins.HttpRequestRetryKt.throwOnInvalidResponseBody(HttpRequestRetry.kt:488)
at io.ktor.client.plugins.HttpRequestRetryKt.access$throwOnInvalidResponseBody(HttpRequestRetry.kt:1)
at io.ktor.client.plugins.HttpRequestRetryKt$throwOnInvalidResponseBody$1.invokeSuspend(HttpRequestRetry.kt)
... 6 more
When I switch back to Ktor 3.2.3 the error does not occur anymore and everything works.
HttpCache: InvalidCacheStateException when varyKeys stored in files contain uppercase letters since 3.3.0
https://github.com/ktorio/ktor/pull/5030 updated the varyKeys map to use lowercased keys in order to allow case-insensitive matching. The corresponding logic in FileCacheStorage has not been updated however, which can cause an exception when upgrading from an older version to 3.3.0. This is the sequence of events:
- An old entry exists in the cache on disk, with a
varyKeyskey that contains uppercase letters - Client updates to 3.3.0 or later
- Updated client prepares for a request, and calls
findResponse()findResponse()uses header lookup (which is case-insensitive) to verify that theVarykeys match- Cache entry matches and the request proceeds, with revalidation
- Client sends the request to revalidate, server responds with
Not Modified - Client calls
findAndRefresh()with the response, which calls the other variant offindResponse()- That variant calls
CacheStorage.find(), which looks up keys directly in thevaryKeysmap (case-sensitive) - This lookup fails because the map keys are using uppercase characters, whereas the requested keys are lowercased
- That variant calls
- This causes an
InvalidCacheStateExceptionto be thrown because it looks like the cache entry has disappeared.
The (easiest) fix is to update the code in FileCacheStorage that deserializes cache entries from disk to lowercase the keys in the varyKeys map, as that's the only other "real" source of cache entries (in-memory caches need to be populated from somewhere else).
Compiler Plugin
OpenAPI gen: missing KDoc fields
Looks like a few KDoc fields are missing from the parser that were documented for support:
- [x]
description <text> - [x]
externalDocs <url> - [x]
security <schema>
We also have a bug with type parameter inference that has yet to be reported.
Docs
Docs: Application module loading timeout
This section (https://ktor.io/docs/server-modules.html#configuration-options) contains a bunch of doc errors:
-
ktor.application.startupTimeoutMillis- default timeout is 10s (not 100s that mentioned in docs: proof
) -
to set
ktor.applicationconfig params we need to modify server config files, notgradle.properties
Native server setup fails to recognize the architecture amd64 on Linux systems
In the Ktor documentation for native servers, the sample code for selecting the native target fails on some Linux systems (e.g., Arch, Debian) because System.getProperty("os.arch") returns "amd64" instead of "x86_64".
Repro:
- OS: Arch Linux (amd64)
- Following the docs at: https://ktor.io/docs/server-native.html
- The build fails with "Host OS is not supported in Kotlin/Native."
Fix:
Update the snippet to handle both "x86_64" and "amd64", e.g.:
hostOs == "Linux" && (arch == "x86_64" || arch == "amd64") → linuxX64("native")
Verified on Arch Linux, works correctly.
Server
Netty: `application` and `call` coroutine scopes share the same job instance since 3.3.0
When it comes to launching coroutines in a Ktor service, here's the advice I've traditionally seen presented:
- For work that's tied to the request, use
call.launch { }. - For background work that's tied to the lifetime of the service, use
application.launch { }.
The idea is that the routing plugin creates a new coroutine for each incoming HTTP request, whereas the application's coroutine scope is cancelled when the application shuts down.
However, I recently noticed that the job of the coroutineContext is the same instance for both the application and call objects. For example, here's a simple Ktor service that prints out the job objects:
fun main() {
embeddedServer(Netty, port = 8080) {
install(CallLogging)
routing {
get("/test") {
println("application.coroutineContext.job = " + application.coroutineContext.job)
println("call.coroutineContext.job = " + call.coroutineContext.job)
call.respondText("OK")
}
}
}.start(wait = true)
}
When I call this endpoint in Ktor 3.2.3, I get this output:
application.coroutineContext.job = SupervisorJobImpl{Active}@189d4fcb
call.coroutineContext.job = StandaloneCoroutine{Active}@65a627a4
However, when I call this endpoint in Ktor 3.3.0, I get this instead:
application.coroutineContext.job = SupervisorJobImpl{Active}@673bf579
call.coroutineContext.job = SupervisorJobImpl{Active}@673bf579
Since I didn't see anything about this in the release notes for 3.3.0, I wanted to check to see whether this was an intentional change or a bug.
Thanks so much!
Server call.request.path() returns routing selectors in path
LOG:
SUCCESS; Parameters [$LocalPort=[8080]] @ /LocalPortRouteSelector(port=8080)/port/(method:GET)
so it is impossible to look up url in the log
fun Application.module() {
routing {
localPort(8081) {
get("/port") {
println(call.request.path())
call.respond("hey, 8081")
}
}
}
}
StaticContent doesn't allow siblings
In 2.x, multiple staticResources("/", …) siblings can be considered by the resolver, and the second one could serve if the first didn’t have the file.
In 3.x, only the first one is considered
I think the reason is https://github.com/ktorio/ktor/pull/4667/files
Code to reproduce:
fun Route.configureStaticRoutes() {
staticResources("/", "static/en-us")
staticResources("/", "static") {
enableAutoHeadResponse()
}
}
Netty: java.lang.VerifyError is thrown on Android since 3.3.0
After upgrading to ktor 3.3.0 I am seeing an exception with netty
The good news is that this issue seems to be already fixed in netty repo
https://github.com/netty/netty/issues/15654
.n.c.Chan...nitializer W [eventLoopGroupProxy-3-1] Failed to initialize a channel. Closing: [id: 0xa147a20e, L:/192.168.40.102:10631 - R:/192.168.10.100:51407]java.lang.VerifyError: Rejecting class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledDirectByteBuf that attempts to sub-type erroneous class io.netty.buffer.UnpooledDirectByteBuf (declaration of 'io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledDirectByteBuf' appears in /data/app/de.silab.sip.android.sip_app.fip-qeq9Yj__35KAQU-Xr8NtzQ==/base.apk!classes28.dex) (Ask Gemini)
at io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:95)
at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:188)
at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:179)
at io.netty.buffer.Unpooled.directBuffer(Unpooled.java:128)
at io.netty.handler.codec.http2.Http2CodecUtil.<clinit>(Http2CodecUtil.java:66)
at io.ktor.server.netty.NettyChannelInitializer.configurePipeline(NettyChannelInitializer.kt:201)
at io.ktor.server.netty.NettyChannelInitializer.initChannel(NettyChannelInitializer.kt:176)
at io.ktor.server.netty.NettyChannelInitializer.initChannel(NettyChannelInitializer.kt:51)
at io.netty.channel.ChannelInitializer.initChannel(ChannelInitializer.java:128)
at io.netty.channel.ChannelInitializer.handlerAdded(ChannelInitializer.java:111)
at io.netty.channel.AbstractChannelHandlerContext.callHandlerAdded(AbstractChannelHandlerContext.java:999)
at io.netty.channel.DefaultChannelPipeline.callHandlerAdded0(DefaultChannelPipeline.java:558)
at io.netty.channel.DefaultChannelPipeline.access$100(DefaultChannelPipeline.java:45)
at io.netty.channel.DefaultChannelPipeline$PendingHandlerAddedTask.execute(DefaultChannelPipeline.java:1482)
at io.netty.channel.DefaultChannelPipeline.callHandlerAddedForAllHandlers(DefaultChannelPipeline.java:1136)
at io.netty.channel.DefaultChannelPipeline.invokeHandlerAddedIfNeeded(DefaultChannelPipeline.java:599)
at io.netty.channel.AbstractChannel$AbstractUnsafe$2.operationComplete(AbstractChannel.java:383)
at io.netty.channel.AbstractChannel$AbstractUnsafe$2.operationComplete(AbstractChannel.java:374)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505)
at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649)
at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638)
at io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:110)
at io.netty.channel.DefaultChannelPromise.setSuccess(DefaultChannelPromise.java:78)
at io.netty.channel.DefaultChannelPromise.setSuccess(DefaultChannelPromise.java:73)
at io.netty.channel.nio.AbstractNioChannel.lambda$doRegister$0$io-netty-channel-nio-AbstractNioChannel(AbstractNioChannel.java:465)
at io.netty.channel.nio.AbstractNioChannel$$ExternalSyntheticLambda0.operationComplete(D8$$SyntheticClass:0)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505)
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:198)
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:36)
at io.netty.channel.nio.AbstractNioChannel.doRegister(AbstractNioChannel.java:462)
at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:408)
at io.netty.channel.AbstractChannel$AbstractUnsafe.access$300(AbstractChannel.java:288)
at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:352)
at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:148)
Other
WebRTC Client. Remove redundant targets
We have some targets like JVM, desktopNative, tvOS, etc that are published but don't have clients, which is confusing for users.
3.3.1
released 10th October 2025
Client
NumberFormatException when Content-Length header value contains null bytes
Test Code
@JvmStatic
fun main(args: Array<String>) {
runBlocking {
val response = ktorClient.post("http://192.168.1.199/boafrm/formShellCmdProcess"){
setBody("FormShellcmd_Param_Shellcmd=ls")
contentType(ContentType.parse("text/plain"))
accept(ContentType.parse("*/*"))
}
logger.debug {
"status: ${response.status}"
}
}
}
Error Message
Exception in thread "main" java.lang.NumberFormatException: For input string: "35 "
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.parseLong(Long.java:631)
at io.ktor.client.engine.cio.UtilsKt$readResponse$2.invokeSuspend(utils.kt:136)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
{width=70%}
{width=70%}
Http Request Header
POST /boafrm/formShellCmdProcess HTTP/1.1
Host: 192.168.1.199
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: text/plain;charset=UTF-8
Content-Length: 30
Origin: http://192.168.1.199
Connection: keep-alive
Referer: http://192.168.1.199/index/chn/modem_shell_debug.htm
Pragma: no-cache
Cache-Control: no-cache
Http Response Header
The char is '\u0000' after "35"
This response can be parsed successfully in firefox browser
It should be compatible
SerializationException: Serializer for class 'ClientSSESession' is not found when server responds with JSON
Here is the context: Ktor 3.0.0 is used to handle SSE requests in a multiplatform library (packaged as an XCFramework named Falcon) in the context of an iOS app. The error kotlinx.serialization.SerializationException: Serializer for class 'ClientSSESession' is not found. is encountered, when the server returns a 429 response (too many requests). Everything works well, if the server returns a 200 response followed by an event stream.
I know I am not giving you a lot of detail (my apologies), but here is the stack trace. Maybe this will give you enough information about what’s causing this error. I will be happy to provide any other details that will help your investigation.
io.ktor.client.plugins.sse.SSEClientException: Serializer for class 'ClientSSESession' is not found.
Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
at 0 Falcon 0x10849ee93 kfun:kotlin.Throwable#<init>(){} + 99 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:32:28)
at 1 Falcon 0x1084980e7 kfun:kotlin.Exception#<init>(){} + 87 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:21:35)
at 2 Falcon 0x108498307 kfun:kotlin.RuntimeException#<init>(){} + 87 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:32:35)
at 3 Falcon 0x1084988a7 kfun:kotlin.IllegalStateException#<init>(){} + 87 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:68:35)
at 4 Falcon 0x10885296b kfun:io.ktor.client.plugins.sse.SSEClientException#<init>(io.ktor.client.statement.HttpResponse?;kotlin.Throwable?;kotlin.String?){} + 127 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/SSE.kt:110:5)
at 5 Falcon 0x108857bfb kfun:io.ktor.client.plugins.sse.mapToSSEException#internal + 451 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/builders.kt:251:9)
at 6 Falcon 0x108858287 kfun:io.ktor.client.plugins.sse.$serverSentEventsSession$lambda$1COROUTINE$1.invokeSuspend#internal + 1343 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/builders.kt:58:51)
at 7 Falcon 0x1085d62c7 kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any?-trampoline + 71 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:50:24)
at 8 Falcon 0x1084a4463 kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 647 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:30:39)
at 9 Falcon 0x1085d63b3 kfun:kotlin.coroutines.Continuation#resumeWith(kotlin.Result<1:0>){}-trampoline + 99 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/libraries/stdlib/src/kotlin/coroutines/Continuation.kt:26:12)
at 10 Falcon 0x1086e558b kfun:kotlinx.coroutines.DispatchedTask#run(){} + 1895 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt:101:71)
at 11 Falcon 0x108687c07 kfun:kotlinx.coroutines.EventLoop#processUnconfinedEvent(){}kotlin.Boolean + 379 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/EventLoop.common.kt:65:14)
at 12 Falcon 0x1086e61a3 kfun:kotlinx.coroutines.resumeUnconfined#internal + 443 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt:177:50)
at 13 Falcon 0x1086e5b47 kfun:kotlinx.coroutines#dispatch__at__kotlinx.coroutines.DispatchedTask<0:0>(kotlin.Int){0§<kotlin.Any?>} + 611 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt:149:13)
at 14 Falcon 0x10867ee4b kfun:kotlinx.coroutines.CancellableContinuationImpl.dispatchResume#internal + 119 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt:470:9)
at 15 Falcon 0x10867f3ef kfun:kotlinx.coroutines.CancellableContinuationImpl#resumeImpl(0:0;kotlin.Int;kotlin.Function3<kotlin.Throwable,0:0,kotlin.coroutines.CoroutineContext,kotlin.Unit>?){0§<kotlin.Any?>} + 583 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt:504:21)
at 16 Falcon 0x10867f65f kfun:kotlinx.coroutines.CancellableContinuationImpl#resumeImpl$default(0:0;kotlin.Int;kotlin.Function3<kotlin.Throwable,0:0,kotlin.coroutines.CoroutineContext,kotlin.Unit>?;kotlin.Int){0§<kotlin.Any?>} + 267 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt:493:5)
at 17 Falcon 0x10867db53 kfun:kotlinx.coroutines.CancellableContinuationImpl#resumeWith(kotlin.Result<1:0>){} + 235 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt:359:9)
at 18 Falcon 0x1085d63b3 kfun:kotlin.coroutines.Continuation#resumeWith(kotlin.Result<1:0>){}-trampoline + 99 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/libraries/stdlib/src/kotlin/coroutines/Continuation.kt:26:12)
at 19 Falcon 0x1086a0023 kfun:kotlinx.coroutines.ResumeAwaitOnCompletion.invoke#internal + 903 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/JobSupport.kt:1557:51)
at 20 Falcon 0x108715253 kfun:kotlinx.coroutines.JobNode#invoke(kotlin.Throwable?){}-trampoline + 63 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/JobSupport.kt:<unknown>)
at 21 Falcon 0x1086906cf kfun:kotlinx.coroutines.JobSupport.completeStateFinalization#internal + 855 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/JobSupport.kt:311:23)
at 22 Falcon 0x10869033b kfun:kotlinx.coroutines.JobSupport.tryFinalizeSimpleState#internal + 315 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/JobSupport.kt:288:9)
at 23 Falcon 0x108696f77 kfun:kotlinx.coroutines.JobSupport.tryMakeCompleting#internal + 659 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/JobSupport.kt:887:17)
at 24 Falcon 0x108696853 kfun:kotlinx.coroutines.JobSupport#makeCompleting(kotlin.Any?){}kotlin.Boolean + 291 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/JobSupport.kt:837:30)
at 25 Falcon 0x1086835df kfun:kotlinx.coroutines.CompletableDeferredImpl.complete#internal + 95 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/CompletableDeferred.kt:87:9)
at 26 Falcon 0x108712cff kfun:kotlinx.coroutines.CompletableDeferred#complete(1:0){}kotlin.Boolean-trampoline + 99 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/CompletableDeferred.kt:32:5)
at 27 Falcon 0x10888d523 kfun:io.ktor.client.engine.darwin.internal.DarwinTaskHandler#receiveData(platform.Foundation.NSURLSessionDataTask;platform.Foundation.NSData){} + 687 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/internal/DarwinTaskHandler.kt:46:22)
at 28 Falcon 0x10888657b kfun:io.ktor.client.engine.darwin.KtorNSURLSessionDelegate#objc:URLSession:dataTask:didReceiveData: + 343 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/KtorNSURLSessionDelegate.kt:57:21)
at 29 Falcon 0x1088874db _696f2e6b746f723a6b746f722d636c69656e742d64617277696e2f6f70742f6275696c644167656e742f776f726b2f386435343762393734613762653231662f6b746f722d636c69656e742f6b746f722d636c69656e742d64617277696e2f64617277696e2f7372632f696f2f6b746f722f636c69656e742f656e67696e652f64617277696e2f4b746f724e5355524c53657373696f6e44656c65676174652e6b74_knbridge4 + 291 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/KtorNSURLSessionDelegate.kt:55:5)
at 30 NewRelic 0x103b0e033 -[NRURLSessionTaskDelegateBase URLSession:dataTask:didReceiveData:] + 163
at 31 CFNetwork 0x1847c18f7 __77-[__NSCFURLSessionDelegateWrapper dataTask:didReceiveData:completionHandler:]_block_invoke_3 + 31
at 32 libdispatch.dylib 0x102df4ebf _dispatch_call_block_and_release + 23
at 33 libdispatch.dylib 0x102df67b7 _dispatch_client_callout + 15
at 34 libdispatch.dylib 0x102dfeaab _dispatch_lane_serial_drain + 911
at 35 libdispatch.dylib 0x102dff7af _dispatch_lane_invoke + 419
at 36 libdispatch.dylib 0x102e0c1ef _dispatch_root_queue_drain_deferred_wlh + 323
at 37 libdispatch.dylib 0x102e0b75b _dispatch_workloop_worker_thread + 731
at 38 libsystem_pthread.dylib 0x102aabb73 _pthread_wqthread + 283
at 39 libsystem_pthread.dylib 0x102aaa933 start_wqthread + 7
Caused by: kotlinx.serialization.SerializationException: Serializer for class 'ClientSSESession' is not found.
Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
at 0 Falcon 0x10849ece7 kfun:kotlin.Throwable#<init>(kotlin.String?){} + 119 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:28:44)
at 1 Falcon 0x10849816f kfun:kotlin.Exception#<init>(kotlin.String?){} + 115 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:23:51)
at 2 Falcon 0x10849838f kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 115 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:34:51)
at 3 Falcon 0x10849870f kfun:kotlin.IllegalArgumentException#<init>(kotlin.String?){} + 115 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:59:51)
at 4 Falcon 0x10874010f kfun:kotlinx.serialization.SerializationException#<init>(kotlin.String?){} + 95 (/opt/buildAgent/work/b2fef8360e1bcf3d/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt:51:44)
at 5 Falcon 0x108762b53 kfun:kotlinx.serialization.internal#serializerNotRegistered__at__kotlin.reflect.KClass<*>(){}kotlin.Nothing + 231 (/opt/buildAgent/work/b2fef8360e1bcf3d/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt:90:11)
at 6 Falcon 0x108741d3f kfun:kotlinx.serialization#serializer__at__kotlin.reflect.KClass<0:0>(){0§<kotlin.Any>}kotlinx.serialization.KSerializer<0:0> + 187 (/opt/buildAgent/work/b2fef8360e1bcf3d/core/commonMain/src/kotlinx/serialization/Serializers.kt:299:85)
at 7 Falcon 0x1084871d7 kfun:io.ktor.serialization.kotlinx#serializerForTypeInfo__at__kotlinx.serialization.modules.SerializersModule(io.ktor.util.reflect.TypeInfo){}kotlinx.serialization.KSerializer<*> + 703 (/opt/buildAgent/work/8d547b974a7be21f/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/common/src/io/ktor/serialization/kotlinx/SerializerLookup.kt:30:26)
at 8 Falcon 0x1084844ef kfun:io.ktor.serialization.kotlinx.KotlinxSerializationConverter.$deserializeCOROUTINE$3.invokeSuspend#internal + 1539 (/opt/buildAgent/work/8d547b974a7be21f/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/common/src/io/ktor/serialization/kotlinx/KotlinxSerializationConverter.kt:64:51)
at 9 Falcon 0x108484c13 kfun:io.ktor.serialization.kotlinx.KotlinxSerializationConverter#deserialize#suspend(io.ktor.utils.io.charsets.Charset;io.ktor.util.reflect.TypeInfo;io.ktor.utils.io.ByteReadChannel;kotlin.coroutines.Continuation<kotlin.Any?>){}kotlin.Any? + 387 (/opt/buildAgent/work/8d547b974a7be21f/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/common/src/io/ktor/serialization/kotlinx/KotlinxSerializationConverter.kt:58:5)
at 10 Falcon 0x108481717 kfun:io.ktor.serialization.ContentConverter#deserialize#suspend(io.ktor.utils.io.charsets.Charset;io.ktor.util.reflect.TypeInfo;io.ktor.utils.io.ByteReadChannel;kotlin.coroutines.Continuation<kotlin.Any?>){}kotlin.Any?-trampoline + 131 (/opt/buildAgent/work/8d547b974a7be21f/ktor-shared/ktor-serialization/common/src/ContentConverter.kt:52:5)
at 11 Falcon 0x108480e43 kfun:io.ktor.serialization.object-1.$collect$lambda$0COROUTINE$1.invokeSuspend#internal + 635 (/opt/buildAgent/work/8d547b974a7be21f/ktor-shared/ktor-serialization/common/src/ContentConverter.kt:43:6)
at 12 Falcon 0x10848125f kfun:io.ktor.serialization.object-1.collect$lambda$0#internal + 419 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt:47:13)
at 13 Falcon 0x108481343 kfun:io.ktor.serialization.object-1.$collect$lambda$0$FUNCTION_REFERENCE$2.emit#internal + 147 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt:47:5)
at 14 Falcon 0x108718553 kfun:kotlinx.coroutines.flow.FlowCollector#emit#suspend(1:0;kotlin.coroutines.Continuation<kotlin.Unit>){}kotlin.Any-trampoline + 115 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt:31:5)
at 15 Falcon 0x1086c8d8f kfun:kotlinx.coroutines.flow.object-3.$collectCOROUTINE$2.invokeSuspend#internal + 799 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/flow/Builders.kt:157:55)
at 16 Falcon 0x1086c906f kfun:kotlinx.coroutines.flow.object-3.collect#internal + 307 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt:106:9)
at 17 Falcon 0x1087186cb kfun:kotlinx.coroutines.flow.Flow#collect#suspend(kotlinx.coroutines.flow.FlowCollector<1:0>;kotlin.coroutines.Continuation<kotlin.Unit>){}kotlin.Any-trampoline + 115 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/flow/Flow.kt:194:5)
at 18 Falcon 0x108480a3f kfun:io.ktor.serialization.object-1.collect#internal + 315 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt:107:13)
at 19 Falcon 0x1087186cb kfun:kotlinx.coroutines.flow.Flow#collect#suspend(kotlinx.coroutines.flow.FlowCollector<1:0>;kotlin.coroutines.Continuation<kotlin.Unit>){}kotlin.Any-trampoline + 115 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/flow/Flow.kt:194:5)
at 20 Falcon 0x1086ddf1b kfun:kotlinx.coroutines.flow.$firstOrNullCOROUTINE$7.invokeSuspend#internal + 887 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt:131:1)
at 21 Falcon 0x1086de2a7 kfun:kotlinx.coroutines.flow#firstOrNull#suspend__at__kotlinx.coroutines.flow.Flow<0:0>(kotlin.coroutines.SuspendFunction1<0:0,kotlin.Boolean>;kotlin.coroutines.Continuation<0:0?>){0§<kotlin.Any?>}kotlin.Any? + 307 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt:131:1)
at 22 Falcon 0x108480247 kfun:io.ktor.serialization.$deserializeCOROUTINE$0.invokeSuspend#internal + 783 (/opt/buildAgent/work/8d547b974a7be21f/ktor-shared/ktor-serialization/common/src/ContentConverter.kt:97:10)
at 23 Falcon 0x1084806d3 kfun:io.ktor.serialization#deserialize#suspend__at__kotlin.collections.List<io.ktor.serialization.ContentConverter>(io.ktor.utils.io.ByteReadChannel;io.ktor.util.reflect.TypeInfo;io.ktor.utils.io.charsets.Charset;kotlin.coroutines.Continuation<kotlin.Any>){}kotlin.Any + 387 (/opt/buildAgent/work/8d547b974a7be21f/ktor-shared/ktor-serialization/common/src/ContentConverter.kt:84:1)
at 24 Falcon 0x10887f22b kfun:io.ktor.client.plugins.contentnegotiation.$ContentNegotiation$lambda$3$convertResponseCOROUTINE$1.invokeSuspend#internal + 2771 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/src/io/ktor/client/plugins/contentnegotiation/ContentNegotiation.kt:234:41)
at 25 Falcon 0x10887f777 kfun:io.ktor.client.plugins.contentnegotiation.ContentNegotiation$lambda$3$convertResponse#internal + 619 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/src/io/ktor/client/plugins/contentnegotiation/ContentNegotiation.kt:202:5)
at 26 Falcon 0x10887fc1b kfun:io.ktor.client.plugins.contentnegotiation.ContentNegotiation$lambda$3$lambda$2#internal + 767 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/src/io/ktor/client/plugins/contentnegotiation/ContentNegotiation.kt:249:9)
at 27 Falcon 0x1088803cf kfun:io.ktor.client.plugins.contentnegotiation.$ContentNegotiation$lambda$3$lambda$2$FUNCTION_REFERENCE$4.invoke#internal + 235 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/src/io/ktor/client/plugins/contentnegotiation/ContentNegotiation.kt:245:5)
at 28 Falcon 0x1085eb587 kfun:kotlin.coroutines.SuspendFunction4#invoke#suspend(1:0;1:1;1:2;1:3;kotlin.coroutines.Continuation<1:4>){}kotlin.Any?-trampoline + 139 (/Users/teamcity/.gradle/daemon/8.8/[K][Suspend]Functions:1:1)
at 29 Falcon 0x10883efab kfun:io.ktor.client.plugins.api.TransformResponseBodyHook.$install$lambda$0COROUTINE$1.invokeSuspend#internal + 1115 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/api/KtorCallContexts.kt:104:30)
at 30 Falcon 0x10883f643 kfun:io.ktor.client.plugins.api.TransformResponseBodyHook.install$lambda$0#internal + 343 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/api/KtorCallContexts.kt:101:75)
at 31 Falcon 0x10883f737 kfun:io.ktor.client.plugins.api.TransformResponseBodyHook.$install$lambda$0$FUNCTION_REFERENCE$3.invoke#internal + 163 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/api/KtorCallContexts.kt:101:33)
at 32 Falcon 0x1085d7003 kfun:kotlin.coroutines.SuspendFunction2#invoke#suspend(1:0;1:1;kotlin.coroutines.Continuation<1:2>){}kotlin.Any?-trampoline + 123 (/Users/teamcity/.gradle/daemon/8.8/[K][Suspend]Functions:1:1)
at 33 Falcon 0x1087aec0b kfun:io.ktor.util.pipeline.DebugPipelineContext.$proceedLoopCOROUTINE$0.invokeSuspend#internal + 723 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:79:32)
at 34 Falcon 0x1087aee43 kfun:io.ktor.util.pipeline.DebugPipelineContext.proceedLoop#internal + 263 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:66:5)
at 35 Falcon 0x1087ae77b kfun:io.ktor.util.pipeline.DebugPipelineContext#proceed#suspend(kotlin.coroutines.Continuation<1:0>){}kotlin.Any + 355 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:57:16)
at 36 Falcon 0x1087c1a2f kfun:io.ktor.util.pipeline.PipelineContext#proceed#suspend(kotlin.coroutines.Continuation<1:0>){}kotlin.Any-trampoline + 71 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/PipelineContext.kt:41:5)
at 37 Falcon 0x10880da83 kfun:io.ktor.client.HttpClient.$<init>$lambda$3COROUTINE$2.invokeSuspend#internal + 431 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt:179:17)
at 38 Falcon 0x10880de6b kfun:io.ktor.client.HttpClient.<init>$lambda$3#internal + 343 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt:177:66)
at 39 Falcon 0x10880e32f kfun:io.ktor.client.HttpClient.$<init>$lambda$3$FUNCTION_REFERENCE$5.invoke#internal + 163 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt:177:26)
at 40 Falcon 0x1085d7003 kfun:kotlin.coroutines.SuspendFunction2#invoke#suspend(1:0;1:1;kotlin.coroutines.Continuation<1:2>){}kotlin.Any?-trampoline + 123 (/Users/teamcity/.gradle/daemon/8.8/[K][Suspend]Functions:1:1)
at 41 Falcon 0x1087aec0b kfun:io.ktor.util.pipeline.DebugPipelineContext.$proceedLoopCOROUTINE$0.invokeSuspend#internal + 723 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:79:32)
at 42 Falcon 0x1087aee43 kfun:io.ktor.util.pipeline.DebugPipelineContext.proceedLoop#internal + 263 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:66:5)
at 43 Falcon 0x1087ae77b kfun:io.ktor.util.pipeline.DebugPipelineContext#proceed#suspend(kotlin.coroutines.Continuation<1:0>){}kotlin.Any + 355 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:57:16)
at 44 Falcon 0x1087c1a2f kfun:io.ktor.util.pipeline.PipelineContext#proceed#suspend(kotlin.coroutines.Continuation<1:0>){}kotlin.Any-trampoline + 71 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/PipelineContext.kt:41:5)
at 45 Falcon 0x108870d87 kfun:io.ktor.client.plugins.logging.ReceiveHook.Context.proceed#internal + 179 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-plugins/ktor-client-logging/common/src/io/ktor/client/plugins/logging/Logging.kt:289:41)
at 46 Falcon 0x1088759b3 kfun:io.ktor.client.plugins.logging.$Logging$lambda$6$lambda$3COROUTINE$3.invokeSuspend#internal + 891 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-plugins/ktor-client-logging/common/src/io/ktor/client/plugins/logging/Logging.kt:209:13)
at 47 Falcon 0x10887615f kfun:io.ktor.client.plugins.logging.Logging$lambda$6$lambda$3#internal + 395 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-plugins/ktor-client-logging/common/src/io/ktor/client/plugins/logging/Logging.kt:203:21)
at 48 Falcon 0x108877b3f kfun:io.ktor.client.plugins.logging.$Logging$lambda$6$lambda$3$FUNCTION_REFERENCE$9.invoke#internal + 163 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-plugins/ktor-client-logging/common/src/io/ktor/client/plugins/logging/Logging.kt:203:5)
at 49 Falcon 0x1085d7003 kfun:kotlin.coroutines.SuspendFunction2#invoke#suspend(1:0;1:1;kotlin.coroutines.Continuation<1:2>){}kotlin.Any?-trampoline + 123 (/Users/teamcity/.gradle/daemon/8.8/[K][Suspend]Functions:1:1)
at 50 Falcon 0x10887103f kfun:io.ktor.client.plugins.logging.ReceiveHook.install$lambda$0#internal + 295 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-plugins/ktor-client-logging/common/src/io/ktor/client/plugins/logging/Logging.kt:297:13)
at 51 Falcon 0x10887112f kfun:io.ktor.client.plugins.logging.ReceiveHook.$install$lambda$0$FUNCTION_REFERENCE$5.invoke#internal + 163 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-plugins/ktor-client-logging/common/src/io/ktor/client/plugins/logging/Logging.kt:296:33)
at 52 Falcon 0x1085d7003 kfun:kotlin.coroutines.SuspendFunction2#invoke#suspend(1:0;1:1;kotlin.coroutines.Continuation<1:2>){}kotlin.Any?-trampoline + 123 (/Users/teamcity/.gradle/daemon/8.8/[K][Suspend]Functions:1:1)
at 53 Falcon 0x1087aec0b kfun:io.ktor.util.pipeline.DebugPipelineContext.$proceedLoopCOROUTINE$0.invokeSuspend#internal + 723 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:79:32)
at 54 Falcon 0x1087aee43 kfun:io.ktor.util.pipeline.DebugPipelineContext.proceedLoop#internal + 263 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:66:5)
at 55 Falcon 0x1087ae77b kfun:io.ktor.util.pipeline.DebugPipelineContext#proceed#suspend(kotlin.coroutines.Continuation<1:0>){}kotlin.Any + 355 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:57:16)
at 56 Falcon 0x1087c1a2f kfun:io.ktor.util.pipeline.PipelineContext#proceed#suspend(kotlin.coroutines.Continuation<1:0>){}kotlin.Any-trampoline + 71 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/PipelineContext.kt:41:5)
at 57 Falcon 0x108826c9b kfun:io.ktor.client.plugins.ReceiveError.$install$lambda$0COROUTINE$6.invokeSuspend#internal + 459 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpCallValidator.kt:149:17)
at 58 Falcon 0x1088271a7 kfun:io.ktor.client.plugins.ReceiveError.install$lambda$0#internal + 343 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpCallValidator.kt:147:58)
at 59 Falcon 0x10882729b kfun:io.ktor.client.plugins.ReceiveError.$install$lambda$0$FUNCTION_REFERENCE$3.invoke#internal + 163 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpCallValidator.kt:147:33)
at 60 Falcon 0x1085d7003 kfun:kotlin.coroutines.SuspendFunction2#invoke#suspend(1:0;1:1;kotlin.coroutines.Continuation<1:2>){}kotlin.Any?-trampoline + 123 (/Users/teamcity/.gradle/daemon/8.8/[K][Suspend]Functions:1:1)
at 61 Falcon 0x1087aec0b kfun:io.ktor.util.pipeline.DebugPipelineContext.$proceedLoopCOROUTINE$0.invokeSuspend#internal + 723 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:79:32)
at 62 Falcon 0x1087aee43 kfun:io.ktor.util.pipeline.DebugPipelineContext.proceedLoop#internal + 263 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:66:5)
at 63 Falcon 0x1087ae77b kfun:io.ktor.util.pipeline.DebugPipelineContext#proceed#suspend(kotlin.coroutines.Continuation<1:0>){}kotlin.Any + 355 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:57:16)
at 64 Falcon 0x1087ae86f kfun:io.ktor.util.pipeline.DebugPipelineContext#execute#suspend(1:0;kotlin.coroutines.Continuation<1:0>){}kotlin.Any + 167 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/DebugPipelineContext.kt:63:16)
at 65 Falcon 0x1087c1857 kfun:io.ktor.util.pipeline.PipelineContext#execute#suspend(1:0;kotlin.coroutines.Continuation<1:0>){}kotlin.Any-trampoline + 79 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/PipelineContext.kt:43:5)
at 66 Falcon 0x1087b0a53 kfun:io.ktor.util.pipeline.Pipeline#execute#suspend(1:1;1:0;kotlin.coroutines.Continuation<1:0>){}kotlin.Any + 331 (/opt/buildAgent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/Pipeline.kt:86:59)
at 67 Falcon 0x108811773 kfun:io.ktor.client.call.HttpClientCall.$bodyNullableCOROUTINE$3.invokeSuspend#internal + 1959 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/HttpClientCall.kt:87:50)
at 68 Falcon 0x108811c3f kfun:io.ktor.client.call.HttpClientCall#bodyNullable#suspend(io.ktor.util.reflect.TypeInfo;kotlin.coroutines.Continuation<kotlin.Any?>){}kotlin.Any? + 307 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/HttpClientCall.kt:76:5)
at 69 Falcon 0x10885849f kfun:io.ktor.client.plugins.sse.$serverSentEventsSession$lambda$1COROUTINE$1.invokeSuspend#internal + 1879 (/opt/buildAgent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/builders.kt:56:42)
at 70 Falcon 0x1085d62c7 kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any?-trampoline + 71 (/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:50:24)
... and 32 more common stack frames skipped
SSE: ClientSSESession doesn't behave like a CoroutineScope
While working on KTOR-8409 I noticed that the ClientSSESession is always canceled when the block parameter completes. This seems to go against the intended design of it being a CoroutineScope? It means that any incomplete child coroutines will be canceled instead of joined.
Here's a test I would assume should work but fails because the launched coroutine is never started:
@Test
fun testSseSessionCoroutineScope() = clientTests {
config {
install(SSE)
}
test { client ->
var size = 0
client.serverSentEvents("$TEST_SERVER/sse/hello?times=20") {
launch(start = CoroutineStart.LAZY) {
incoming.collectIndexed { i, it ->
assertEquals("${i * 2 + 1}", it.data)
size++
}
}
}
assertEquals(50, size)
}
}
If this is the intended design, I think some documentation explaining the difference in behavior from the standard coroutineScope { ... } behavior would be a good idea.
Docs
Documentation for test
Please provide the following details so the technical writing team can begin work on this issue.
Description
Describe what this feature or change does and what problem it solves.
Code example
Provide a code example or link to a working sample (if applicable).
You can also link to a repo, PR, or snippet.
Migration guide
Does this change require a migration guide for existing users?
- [ ] Yes
- [ ] No
Related Links (Optional)
Add any relevant references:
- Blog posts
- Design or product docs (e.g., Quip, KEEP)
- PRs or related issues (other than the parent issue)
Once the description is complete, please re-assign this issue to a Technical Writer.
Documentation for Test
Please provide the following details so the technical writing team can begin work on this issue.
Description
Describe what this feature or change does and what problem it solves.
Code example
Provide a code example or link to a working sample (if applicable).
You can also link to a repo, PR, or snippet.
Migration guide
Does this change require a migration guide for existing users?
- [ ] Yes
- [ ] No
Related Links (Optional)
Add any relevant references:
- Blog posts
- Design or product docs (e.g., Quip, KEEP)
- PRs or related issues (other than the parent issue)
Once the description is complete, please re-assign this issue to a Technical Writer.
Some paths to the source files contain KMM abbrev instead of KMP
What
https://ktor.io/docs/client-create-multiplatform-application.html
- https://github.com/ktorio/ktor-documentation/tree/3.3.1/codeSnippets/snippets/tutorial-client-kmp is not found
- Rename `tutorial-client-kmm` to `tutorial-client-kmp` snippets
- Also, some paths are still using `kmm` instead of `kmp`
Reference
The entire response body is read into memory in the streaming response example
I am trying to download a large (1 GB) file hosted in S3 using ktor's android engine and http client. I have used the exact code from the documentation:
val client = HttpClient(CIO)
val file = File.createTempFile("files", "index")
val stream = file.outputStream().asSink()
runBlocking {
client.prepareGet(LINK_TO_S3_FILE).execute { httpResponse ->
val channel: ByteReadChannel = httpResponse.body()
var count = 0L
stream.use {
while (!channel.exhausted()) {
val chunk = channel.readRemaining()
count += chunk.remaining
chunk.transferTo(stream)
println("Received $count bytes from ${httpResponse.contentLength()}")
}
}
}
println("A file saved to ${file.path}")
}
This throws an OutOfMemoryError after downloading around 400 MB. How to resolve this?
Please add a note to the client SSE about the session lifetime
Motivated by KTOR-8440
In KDoc, there is the next note:
Note: [ClientSSESession/ClientSSESessionWithDeserialization] is bound to the session lifetime.
Its scope is canceled when the `serverSentEvents { ... }` block returns or when the connection closes.
Add IP address discovery instructions for multiplatform development tutorial
Background
While reviewing the "Build a full-stack application with Kotlin Multiplatform" tutorial (Create the client section), I noticed there's already a comment indicating consideration for adding IP address discovery instructions:
<!-- should we include instructions on finding out the IP address?
`ipconfig getifaddr en0`or something -->
This appears in the step where users need to replace 1.2.3.4 with their machine's IP address for mobile simulator connectivity.
Proposal
I'd like to contribute by implementing this suggested improvement. Here's my proposed solution:
<p>
Note you should replace <code>1.2.3.4</code> with the IP address of your current machine. You will
not be able to make calls to <code>0.0.0.0</code> or <code>localhost</code> from code running on an
Android Virtual Device or the iOS Simulator.
</p>
<tip>
<p><b>Finding your IP address:</b></p>
<p>
Since mobile simulators cannot reach <code>localhost</code>, you need your machine's actual IP address.
Use these commands to find it:
</p>
<list>
<li><b>macOS:</b> <code>ifconfig | grep "inet " | grep -v 127.0.0.1</code></li>
<li><b>Linux:</b> <code>hostname -I | awk '{print $1}'</code></li>
<li><b>Windows:</b> <code>ipconfig</code> and look for "IPv4 Address"</li>
</list>
</tip>
Testing
I've tested these commands across different network configurations (Wi-Fi, mobile hotspot) to ensure reliability. The ifconfig approach for macOS works more consistently than interface-specific commands like ipconfig getifaddr en0.
File Location
- File:
full-stack-development-with-kotlin-multiplatform.topic - Lines: 602-619 (step 5 in "Create the client" section)
Benefits
This addition would:
- Help developers complete the tutorial without external research
- Provide reliable cross-platform IP discovery methods
Does this approach seem helpful? I'd be glad to contribute a PR if you think it would be a good addition.
Server
Support serving static resources within bootJar
I am using ktor server with a springboot application. I am trying to use ktor for serving static resources, which works fine locally, but when I package it into a fatjar using bootJar, an error occurs.
java.lang.IllegalArgumentException: Only local jars are supported (jar:file:)
at io.ktor.server.http.content.StaticContentResolutionKt.findContainingJarFile(StaticContentResolution.kt:118) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentResolutionKt.resourceClasspathResource(StaticContentResolution.kt:96) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentResolutionKt.resolveResource$lambda$5(StaticContentResolution.kt:65) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentResolutionKt.resolveResource(StaticContentResolution.kt:69) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentResolutionKt.resolveResource$default(StaticContentResolution.kt:51) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.PreCompressedKt.respondStaticResource(PreCompressed.kt:211) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentKt.respondStaticResource(StaticContent.kt:738) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentKt.access$respondStaticResource(StaticContent.kt:1) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentKt$staticResources$2.invokeSuspend(StaticContent.kt:253) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentKt$staticResources$2.invoke(StaticContent.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentKt$staticResources$2.invoke(StaticContent.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentKt$staticContentRoute$2$1$1$1.invokeSuspend(StaticContent.kt:619) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentKt$staticContentRoute$2$1$1$1.invoke(StaticContent.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.http.content.StaticContentKt$staticContentRoute$2$1$1$1.invoke(StaticContent.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingNode$buildPipeline$1$1.invokeSuspend(RoutingNode.kt:126) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingNode$buildPipeline$1$1.invoke(RoutingNode.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingNode$buildPipeline$1$1.invoke(RoutingNode.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.PipelineJvmKt.pipelineStartCoroutineUninterceptedOrReturn(PipelineJvm.kt:15) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:131) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:89) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:109) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:92) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingRoot$executeResult$$inlined$execute$1.invokeSuspend(Pipeline.kt:510) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingRoot$executeResult$$inlined$execute$1.invoke(Pipeline.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingRoot$executeResult$$inlined$execute$1.invoke(Pipeline.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.debug.ContextUtilsKt.initContextInDebugMode(ContextUtils.kt:19) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingRoot.executeResult(RoutingRoot.kt:208) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingRoot.interceptor(RoutingRoot.kt:71) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingRoot$Plugin$install$1.invokeSuspend(RoutingRoot.kt:154) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingRoot$Plugin$install$1.invoke(RoutingRoot.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.routing.RoutingRoot$Plugin$install$1.invoke(RoutingRoot.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.PipelineJvmKt.pipelineStartCoroutineUninterceptedOrReturn(PipelineJvm.kt:15) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:131) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:89) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.BaseApplicationEngineKt$installDefaultTransformationChecker$1.invokeSuspend(BaseApplicationEngine.kt:119) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.BaseApplicationEngineKt$installDefaultTransformationChecker$1.invoke(BaseApplicationEngine.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.BaseApplicationEngineKt$installDefaultTransformationChecker$1.invoke(BaseApplicationEngine.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.PipelineJvmKt.pipelineStartCoroutineUninterceptedOrReturn(PipelineJvm.kt:15) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:131) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:89) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:109) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:92) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1$invokeSuspend$$inlined$execute$1.invokeSuspend(Pipeline.kt:510) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.debug.ContextUtilsKt.initContextInDebugMode(ContextUtils.kt:19) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1.invokeSuspend(DefaultEnginePipeline.kt:129) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1.invoke(DefaultEnginePipeline.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1.invoke(DefaultEnginePipeline.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.PipelineJvmKt.pipelineStartCoroutineUninterceptedOrReturn(PipelineJvm.kt:15) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:131) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:89) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.BaseApplicationResponse$Companion$setupFallbackResponse$1.invokeSuspend(BaseApplicationResponse.kt:349) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.BaseApplicationResponse$Companion$setupFallbackResponse$1.invoke(BaseApplicationResponse.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.engine.BaseApplicationResponse$Companion$setupFallbackResponse$1.invoke(BaseApplicationResponse.kt) ~[ktor-server-core-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.PipelineJvmKt.pipelineStartCoroutineUninterceptedOrReturn(PipelineJvm.kt:15) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:131) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:89) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:109) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:92) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1$invokeSuspend$$inlined$execute$1.invokeSuspend(Pipeline.kt:510) ~[ktor-server-netty-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt) ~[ktor-server-netty-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt) ~[ktor-server-netty-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.util.debug.ContextUtilsKt.initContextInDebugMode(ContextUtils.kt:19) ~[ktor-utils-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:140) ~[ktor-server-netty-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt) ~[ktor-server-netty-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt) ~[ktor-server-netty-jvm-3.2.0.jar!/:3.2.0]
at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:27) ~[kotlinx-coroutines-core-jvm-1.8.1.jar!/:na]
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:90) ~[kotlinx-coroutines-core-jvm-1.8.1.jar!/:na]
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:123) ~[kotlinx-coroutines-core-jvm-1.8.1.jar!/:na]
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:52) ~[kotlinx-coroutines-core-jvm-1.8.1.jar!/:na]
at kotlinx.coroutines.BuildersKt.launch(Unknown Source) ~[kotlinx-coroutines-core-jvm-1.8.1.jar!/:na]
at io.ktor.server.netty.NettyApplicationCallHandler.handleRequest(NettyApplicationCallHandler.kt:41) ~[ktor-server-netty-jvm-3.2.0.jar!/:3.2.0]
at io.ktor.server.netty.NettyApplicationCallHandler.channelRead(NettyApplicationCallHandler.kt:33) ~[ktor-server-netty-jvm-3.2.0.jar!/:3.2.0]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.121.Final.jar!/:4.1.121.Final]
at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:61) ~[netty-transport-4.1.121.Final.jar!/:4.1.121.Final]
at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:425) ~[netty-transport-4.1.121.Final.jar!/:4.1.121.Final]
at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173) ~[netty-common-4.1.121.Final.jar!/:4.1.121.Final]
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166) ~[netty-common-4.1.121.Final.jar!/:4.1.121.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472) ~[netty-common-4.1.121.Final.jar!/:4.1.121.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569) ~[netty-transport-4.1.121.Final.jar!/:4.1.121.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998) ~[netty-common-4.1.121.Final.jar!/:4.1.121.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.121.Final.jar!/:4.1.121.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.121.Final.jar!/:4.1.121.Final]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
// ktor-server-core-jvm-3.2.0-sources.jar!/jvmMain/io/ktor/server/http/content/StaticContentResolution.kt:110
internal fun findContainingJarFile(url: String): File {
if (url.startsWith("jar:file:")) {
val jarPathSeparator = url.indexOf("!", startIndex = 9)
require(jarPathSeparator != -1) { "Jar path requires !/ separator but it is: $url" }
return File(url.substring(9, jarPathSeparator).decodeURLPart())
}
throw IllegalArgumentException("Only local jars are supported (jar:file:)")
}
the url params is jar:nested:/pathto/app.jar/!BOOT-INF/classes/!/static
Please add custom resourceClasspathResource function processing to StaticContentConfig or support Spring's NestedJar
https://docs.spring.io/spring-boot/3.5/specification/executable-jar/nested-jars.html
Netty: loadConfiguration missing enableHttp2 and enableH2c properties
There have been added a few config fields that are not mentioned in the loadConfiguration method.
Please add:
- enableHttp2: Boolean
- enableH2c: Boolean
Netty: EmbeddedServer.stop always blocks for twice of shutdownGracePeriod
To reproduce, execute the following code:
val server = embeddedServer(
factory = Netty,
environment = applicationEnvironment(),
configure = {
shutdownGracePeriod = 30_000
connector {
port = 8787
}
},
).start(wait = false)
println("Stopping...")
val time = measureTimeMillis {
server.stop()
}
println("Stopped in $time")
As a result, the server shuts down only after 60 seconds.
shutdownGracePeriod is used instead of shutdownTimeout in EmbeddedServer.stop()
Hello,
In the current Ktor version (3.2.3) there seems to be a typo in EmbeddedServer.stop() function implementation:
public fun stop(
gracePeriodMillis: Long = engineConfig.shutdownGracePeriod,
timeoutMillis: Long = engineConfig.shutdownGracePeriod // <--
)
This function is called from the shutdown hook registered upon the EmbeddedServer.start(..) invocation:
public actual fun start(wait: Boolean): EmbeddedServer<TEngine, TConfiguration> {
addShutdownHook { stop() }
. . .
And it impacts the apps that rely on this OOB graceful shutdown approach: as the default shutdownGracePeriod is 5 times smaller than shutdownTimeout, the app would have less time to gracefully complete its operations under the load.
Other
Expose Core dependencies in WebRTC Client
All currently available targets in the ktor-client-webrtc module have methods to access the inner implementation of entities, such as
public fun WebRtcPeerConnection.getNative(): PeerConnection {
return (this as AndroidWebRtcPeerConnection).peerConnection;
}
The PeerConnection is defined in the io.getstream:stream-webrtc-android which user should also install to use those methods. We can make such dependencies transitive to make it work out of the box and ensure version consistency.
Update Kotlin to 2.2.20
- [x] Update the Kotlin version badge
- [x] Fix compilation
3.3.0
released 12th September 2025
Client
SSE, OkHttp: Receiving duplicated events when `maxReconnectionAttempts` > 0
Summary
Server-Sent Events are delivered out of order when using sse() client in Ktor 3.3.1. Events that should arrive sequentially over a single HTTP stream are being reordered, breaking SSE protocol guarantees.
Versions
- Working: Ktor 3.3.0-eap-1390 (Kotlin 2.2.20-Beta2)
- Broken: Ktor 3.3.1 (Kotlin 2.3.0-Beta1)
Environment
- Platform: JVM (macOS, also reproduced in production Linux)
- SSE Provider: OpenAI streaming API
Expected Behavior
SSE events should arrive in the exact order sent by the server, as they are delivered over a single sequential HTTP stream.
Example expected order:
Event 1: tool_start (id="call_abc", name="search", index=0)
Event 2: tool_arguments (index=0, args='{"query"')
Event 3: tool_arguments (index=0, args=':"test"}')
Event 4: tool_end (index=0)
Actual Behavior (Ktor 3.3.1)
Events arrive reordered:
Event 1: tool_arguments (index=0, args='{"query"') ← BEFORE tool_start!
Event 2: tool_start (id="call_abc", name="search", index=0)
Event 3: tool_arguments (index=0, args=':"test"}')
Event 4: tool_end (index=0) ← Sometimes arrives BEFORE tool_start
Reproduction
Our production code that exhibits the issue:
data class ChatCompletionChunk(/* OpenAI streaming response */)
fun streamOpenAI(client: HttpClient, json: Json): Flow<ChatCompletionChunk> = flow {
client.sse(
request = {
url("https://api.openai.com/v1/chat/completions")
method = HttpMethod.Post
contentType(ContentType.Application.Json)
bearerAuth(apiKey)
setBody(/* chat completion request with tools, stream=true */)
}
) {
incoming
.mapNotNull { event ->
event.data?.takeIf { !it.trim().endsWith("[DONE]") }
}
.collect { data ->
emit(json.decodeFromString<ChatCompletionChunk>(data))
}
}
}
When streaming tool calls from OpenAI, events arrive out of sequence. The same code with Ktor 3.3.0-eap-1390 maintains correct order.
Impact
- Breaks stateful SSE processing (tool call tracking, argument accumulation)
- Violates SSE protocol guarantees about sequential delivery
- Causes crashes when code expects events in protocol order (e.g.,
requireNotNull(toolsInProgress[id]))
Suspected Root Cause
Likely introduced by https://youtrack.jetbrains.com/issue/KTOR-8440 fix. The fix may have introduced parallel event processing or incorrect buffering.
Error Examples
IllegalStateException: Received tool arguments for unknown index: 0
IllegalStateException: Tool call_xyz was not in progress, but ended
Both errors occur because events arrive before their prerequisite events.
Request
Please investigate SSE event ordering in 3.3.1, particularly changes introduced by KTOR-8440 fix.
SSE: Cannot read response body from SSEClientException
I'm consuming an SSE event stream as follows:
val http = HttpClient(Apache) {
install(SSE)
}
flow {
val session = http.serverSentEventsSession { request() }.incoming.mapNotNull { it.data }
emitAll(session)
}
.catch { err ->
if (err is SSEClientException) {
val response = err.response ?: throw MyException("No response")
val content = response.body<String>()
}
}
}
The call to response.body() in the error handler fails with the following exception:
io.ktor.client.call.DoubleReceiveException: Response already received: HttpClientCall[https://test.url/v56/messages, 404 Not Found]
at io.ktor.client.call.HttpClientCall.bodyNullable(HttpClientCall.kt:80)
I can read response.status, which is great. But I would also like to read the response body for logging and advanced error handling purposes.
SSE: "SSEClientException: Content-Length mismatch" on saving response body in DefaultResponseValidation
When using SSE to stream chat completions from OpenAI api, the request:
client.sse(
request = {
url(BaseUrl) // https://api.openai.com/v1/chat/completions
method = HttpMethod.Post
contentType(ContentType.Application.Json)
setBody(buildOpenAIRequest(request, streaming = true))
configureRequest() // auth header set
},
deserialize = { typeInfo, data ->
val serializer = json.serializersModule.serializer(typeInfo.kotlinType!!)
json.decodeFromString(serializer, data)
}
) {
incoming.map { event ->
requireNotNull(deserialize<ChatCompletionChunk>(event)) { "Failed to deserialize event: $event" }
}.collect { chunk ->
// ...
}
}
fails with:
Caused by: io.ktor.client.plugins.sse.SSEClientException: Content-Length mismatch: expected 288 bytes, but received 0 bytes
at io.ktor.client.plugins.sse.BuildersKt.mapToSSEException(builders.kt:1114)
at io.ktor.client.plugins.sse.BuildersKt.access$mapToSSEException(builders.kt:1)
at io.ktor.client.plugins.sse.BuildersKt$serverSentEventsSession-i8z2VEo$$inlined$processSession-rp2poPw$1.invokeSuspend(builders.kt:1098)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
... 6 more
Caused by: java.lang.IllegalStateException: Content-Length mismatch: expected 288 bytes, but received 0 bytes
at io.ktor.client.call.UtilsKt.checkContentLength(utils.kt:12)
at io.ktor.client.call.SavedHttpCall.<init>(SavedCall.kt:51)
at io.ktor.client.call.SavedCallKt.save(SavedCall.kt:37)
at io.ktor.client.plugins.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidation.kt:42)
at io.ktor.client.plugins.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invoke(DefaultResponseValidation.kt)
at io.ktor.client.plugins.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invoke(DefaultResponseValidation.kt)
at io.ktor.client.plugins.HttpCallValidatorKt.HttpCallValidator$lambda$2$validateResponse(HttpCallValidator.kt:110)
at io.ktor.client.plugins.HttpCallValidatorKt.access$HttpCallValidator$lambda$2$validateResponse(HttpCallValidator.kt:1)
at io.ktor.client.plugins.HttpCallValidatorKt$HttpCallValidator$2$2.invokeSuspend(HttpCallValidator.kt:129)
... 8 more
Here's my client config:
@Suppress("MagicNumber")
private fun provideHttpClient(json: Json, config: Config, log: Log) = HttpClient(OkHttp) {
install(Logging) {
logger = object : Logger {
override fun log(message: String) = log.invoke(Log.Level.Trace) { message }
}
level = if (config.debug) LogLevel.ALL else LogLevel.NONE
sanitizeHeader { it == HttpHeaders.Authorization }
}
install(ContentNegotiation) { json(json) }
install(SSE) {
reconnectionTime = 5.seconds
maxReconnectionAttempts = 3
}
install(HttpRequestRetry) {
retryOnExceptionOrServerErrors(3)
constantDelay(700)
}
install(HttpTimeout) {
requestTimeoutMillis = NetworkDefaults.RequestTimeout
connectTimeoutMillis = NetworkDefaults.ConnectTimeout
}
install(DataConversion)
install(ContentEncoding) {
mode = ContentEncodingConfig.Mode.All
deflate(1f)
gzip(0.8f)
identity(0.5f)
}
defaultRequest {
userAgent(NetworkDefaults.UserAgent)
}
install(HttpCache) {
isShared = true
}
addDefaultResponseValidation()
expectSuccess = true
followRedirects = true
engine {
pipelining = true
dispatcher = Dispatchers.Virtual.limitedParallelism(NetworkDefaults.MaxConnections)
config {
connectTimeout(NetworkDefaults.ConnectTimeout.milliseconds.toJavaDuration())
// handled by ktor
// callTimeout(NetworkDefaults.RequestTimeout.milliseconds.toJavaDuration())
retryOnConnectionFailure(true)
followRedirects(true)
followSslRedirects(true)
pingInterval(NetworkDefaults.KeepAliveTime.milliseconds.toJavaDuration())
// broken because of another issue in ktor
// if (config.debug) addInterceptor(
// HttpLoggingInterceptor().apply { setLevel(HttpLoggingInterceptor.Level.BODY) }
// )
}
}
}
This is not happening for other providers, but I'm pretty sure you at jetbrains solved this.
I couldn't find any documentation on how to disable this validation, skip it, fix the header value myself, uninstall plugins temporarily, what is wrong with openai api or any issues similar to this, so I need help because I don't know what is wrong and what to do. I'm sus of the save body plugin and compression, but I tried disabling compression and it didn't help much.
Performance regression when using ContentEncoding and HttpRequestRetry since 3.2.0
When using ContentEncoding and HttpRequestRetry at the same time, it takes about 15 seconds to get some responses.
When I use only ContentEncoding or HttpRequestRetry, it does not present the problem.
The problem appears with 3.2.0+, 3.1.3 works just fine.
minimal reproducible example:
val client = HttpClient(CIO) {
install(ContentEncoding) {
deflate()
gzip()
}
install(HttpRequestRetry)
}
client.get("https://samples.json-format.com/employees/minified-json/employees_5MB_min.json").bodyAsText()
HttpRedirect: The client is redirected when no Location header in response
If the response returns a redirect without a location, the current redirect will be redirected to localhost.
the body should be returned directly for the user to decide
HttpCache: all header values but first in HttpResponse.varyKeys() are ignored
Current code in HttpCache plugin looks like this:
internal fun HttpResponse.varyKeys(): Map<String, String> {
val validationKeys = vary() ?: return emptyMap()
val result = mutableMapOf<String, String>()
val requestHeaders = call.request.headers
for (key in validationKeys) {
result[key] = requestHeaders[key] ?: ""
}
return result
}
I think that we should not take first value of requestHeaders, but all of them and join into single string:
result[key] = requestHeaders.getAll(key).joinToString(";")
This would match "mergedHeadersLookup" method which is used when Ktor is trying to retrieve value from cache.
Right now if you call server with Accept: application/json ; application/cbor Ktor will never cache your response
HttpCache: plugin selects wrong cache entry when filtering Vary headers with different case
We are facing an issue where the Http Cache plugin does not correctly select the appropriate entry when multiple entries exist for a single URL due to differing Vary headers.
The current implementation seems to be non-compliant with the RFC standards (see: RFC 9111).
For instance, in UnlimitedCacheStorage at line 23:
override fun find(url: Url, varyKeys: Map<String, String>): HttpCacheEntry? {
val data = store.computeIfAbsent(url) { ConcurrentSet() }
return data.find {
varyKeys.all { (key, value) -> it.varyKeys[key] == value }
}
}
Several issues arise here:
• The key matching is not case-insensitive, which is required.
• The use of all is incorrect, as the varyKeys should not be treated as a subset.
• Furthermore, the find method always returns the first matching entry, whereas it should be selecting the most recent one. This is correctly handled for entries without Vary headers at line 332 of the HttpCache.
In summary, a thorough comparison with the RFC specifications is necessary to ensure compliance.
These same issues also affect the persistent cache implementation: FileCacheStorage.
PS: Will there be implementations for iOS/Android? With kotlinx-io, this should be relatively straightforward.
Upgrade OkHttp to version 5.0.0
Hello,
I noticed that Square has recently released a new stable version of OkHttp (5.0.0). Currently, the Ktor OkHttp engine (version 3.2.1) still uses OkHttp 4.12.0.
Would it be possible to update the OkHttp engine to use the latest 5.x version of OkHttp in an upcoming release? It would be great to take advantage of the improvements and changes in the new major version.
Thanks for all the great work on Ktor!
CIO: The engine ignores system proxy settings
When using latest Ktor CIO client, java.net.SocketException: Network is unreachable is thrown on the device that uses user-installed certificate.
The user certificates are correctly enabled via android:networkSecurityConfig application tag. The same request works with other (non-ktor) http clients and with the Ktor OkHttp client.
Compiler Plugin
Compiler plugin
Should we introduce a compiler plugin?
This is the issue to collect all potential benefits we could get from creating a compiler plugin.
Core
Add some missing image content types
Currently, there are a few decently common ContentTypes for images which are missing. Namely:
- apng:
image/apng(.apng) - avif:
image/avif(.avif) - bmp:
image/bmp(.bmp,.dib) - heic:
image/heic(.heic) - heif:
image/heif(.heif) - jxl:
image/jxl(.jxl) - tiff:
image/tiff(.tif,.tiff) - webp:
image/webp(.webp)
Further, if you are using functions like ContentType.Companion.defaultForFileExtension(extension: String), ContentType.Companion.defaultForFilePath(path: String), etc, the following file extensions will not be properly identified, and will instead return a content type of ContentType.Application.OctetStream (application/octet-stream):
.apng.dib(tbh I've never encountered.dibin the wild before, but according to the IANA, this file extension is used for bmp files.).jxl
Big number of simultaneous outbound web socket connections leads to a coroutine deadlock
Context
- Scope Ktor client, websocket
- Scale 1000+ connections
- Target JVM
- Hardware MBP M2 Pro, 16Gb
Issue
Ktor client is used for load testing, where a big number of outbound websocket connections are created simultaneously (no delay). If the number of connections is greater than the number of threads in IO dispatcher, neither connection progresses and all of them are stuck in blocking read of nonce value from the channel.
A global job nonceGeneratorJob calculates the nonce value and publishes the result via the channel, however, as the job is launched on IO dispatcher, at this point all threads are blocked by the blocking read from the channel. Thus the job never starts, which leads to a coroutine deadlock.
The problem does not manifest itself if the number of in-flight connections is lower than the number of threads in IO dispatcher.
Suggested solution
Launch nonceGeneratorJob on the Default dispatcher.
Add `image/bmp` to the ContentType
Api symbol: io.ktor.http.ContentType.Image:
Add image/bmp
CountedByteWriteChannel: autoFlush of the source channel doesn't make the channel auto flushing
Hello,
I've found an issue with CountedByteWriteChannel. See the following example
val tcpSocket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().connect(host, port)
val tcpCountedWrite = CountedByteWriteChannel(tcpSocket.openWriteChannel(autoFlush = true))
val tcpWrite = tcpSocket.openWriteChannel(autoFlush = true)
//Flushes
tcpWrite.writeFully(data, 0, data.size)
//Doesn't flush. Manual flush required, otherwise no packets are sent
tcpCountedWrite.writeFully(data, 0, data.size)
tcpCountedWrite.flush()
The issue comes from the following cast in ByteWriteChannel.kt which doesn't work for CountedByteWriteChannel
@InternalAPI
public suspend fun ByteWriteChannel.flushIfNeeded() {
rethrowCloseCauseIfNeeded()
//cast fails
if ((this as? ByteChannel)?.autoFlush == true || writeBuffer.size >= CHANNEL_MAX_SIZE) flush()
}
Docs
Ktor 3.3.0 docs not published
https://ktor.io/docs/welcome.html still only shows 3.2.3
Document limitations of auto-reload functionality
The Ktor "development" mode has some limitations that need to be documented for 3.3
There's a few ways to load modules, and some of them are not accounted for when the server refreshes:
- Lambda calls (
embeddedServer(port = 8080) { configureServer() }) - Blocking function references (
embeddedServer(port = 8080, module = Application::myBlockingModule)) - Suspend function references (
embeddedServer(port = 8080, module = Application::mySuspendModule)) - Configuration references (from the
ktor.application.modulesproperty)
There was a regression in 3.2.0 with the introduction of suspend module support that caused blocking function calls to no longer work with auto-reload.
So our support for auto-reload looks like this:
| ktor version | module type | supported |
|---|---|---|
| < 3.2 | Lamdas | |
| Blocking ref | ✅ | |
| Suspend ref | ||
| Config ref | ✅ | |
| >= 3.2 | Lambdas | |
| Blocking ref | ||
| Suspend ref | ✅ | |
| Config ref | ✅ |
We'd like to fix the blocking function use-case, but there's a Catch 22 problem where the fix would require re-introducing embeddedServer with the blocking function argument, which would break compilation for the lambda use-case.
Server
Autoreloading: JobCancellationException when app is reloaded in the debugger since 3.2.0
Hi, I've setup a completely new ktor empty project using ktor-cli.
I've also setup a new run configuration in the IDE that does gradlew -t build.
The ktor project is in development mode, which means it should reload when it sees the build directory changes, which in turn gets updated by the previous run configuration.
Everything works great when I run the ktor gradle configuration. However, if I run it with debug in order to be able to add break points, as soon as I do a change the gradlew -t build rebuilds, ktor starts throwing error 500 no matter what, and the console says:
2025-08-27 16:38:17.326 [eventLoopGroupProxy-4-1 @call-handler#14] DEBUG Application - Unhandled: GET - /. Exception class kotlinx.coroutines.JobCancellationException: Job was cancelled
kotlinx.coroutines.JobCancellationException: Job was cancelled
at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:1685)
at kotlinx.coroutines.Job$DefaultImpls.cancel$default(Job.kt:207)
at kotlinx.coroutines.JobKt__JobKt.cancelAndJoin(Job.kt:510)
at kotlinx.coroutines.JobKt.cancelAndJoin(Unknown Source)
at io.ktor.server.application.Application.disposeAndJoin(Application.kt:158)
at io.ktor.server.engine.EmbeddedServer$destroyBlocking$1$1.invokeSuspend(EmbeddedServerJvm.kt:257)
at io.ktor.server.engine.EmbeddedServer$destroyBlocking$1$1.invoke(EmbeddedServerJvm.kt)
at io.ktor.server.engine.EmbeddedServer$destroyBlocking$1$1.invoke(EmbeddedServerJvm.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
at io.ktor.server.engine.EmbeddedServer$destroyBlocking$1.invokeSuspend(EmbeddedServerJvm.kt:256)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith$$$capture(ContinuationImpl.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:263)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:94)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:70)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at io.ktor.server.engine.EmbeddedServer.destroyBlocking(EmbeddedServerJvm.kt:255)
at io.ktor.server.engine.EmbeddedServer.destroyApplication(EmbeddedServerJvm.kt:242)
at io.ktor.server.engine.EmbeddedServer.currentApplication(EmbeddedServerJvm.kt:120)
at io.ktor.server.engine.EmbeddedServer.access$currentApplication(EmbeddedServerJvm.kt:33)
at io.ktor.server.engine.EmbeddedServer$engine$1.invoke(EmbeddedServerJvm.kt:83)
at io.ktor.server.engine.EmbeddedServer$engine$1.invoke(EmbeddedServerJvm.kt:83)
at io.ktor.server.netty.http1.NettyHttp1Handler.prepareCallFromRequest(NettyHttp1Handler.kt:143)
at io.ktor.server.netty.http1.NettyHttp1Handler.handleRequest(NettyHttp1Handler.kt:116)
at io.ktor.server.netty.http1.NettyHttp1Handler.channelRead(NettyHttp1Handler.kt:68)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93)
at io.netty.handler.codec.http.HttpServerExpectContinueHandler.channelRead(HttpServerExpectContinueHandler.java:95)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:434)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:249)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1429)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:167)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.handle(AbstractNioChannel.java:445)
at io.netty.channel.nio.NioIoHandler$DefaultNioRegistration.handle(NioIoHandler.java:381)
at io.netty.channel.nio.NioIoHandler.processSelectedKey(NioIoHandler.java:575)
at io.netty.channel.nio.NioIoHandler.processSelectedKeysOptimized(NioIoHandler.java:550)
at io.netty.channel.nio.NioIoHandler.processSelectedKeys(NioIoHandler.java:491)
at io.netty.channel.nio.NioIoHandler.run(NioIoHandler.java:468)
at io.netty.channel.SingleThreadIoEventLoop.runIo(SingleThreadIoEventLoop.java:206)
at io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:177)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1073)
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:840)
Jetty engine: Upgrade Jetty dependencies to the latest version 12
The Jetty version currently bundled in ktor-server-jetty-jakarta is 11.x. An upgrade to Jetty 12 would allow leaning on its support for virtual threads, when running on Java 21+.
Using 3.0.0-beta-1
ReloadingException spamming test console when using application DSL
Ktor tests have started spamming the test console with the following error messages. This happened with an update. Note that the tests behave properly and are passing when they should. It does, however, make server test logs really hard to parse / read.
2025-09-18 09:27:02.202 DEBUG io.ktor.test - Failed to load module 'org.geobon.hpc.HPCConnectionTest$givenConfiguredWithErrors_whenPreparedManually_thenGetFailureMessage$1$1$1.invoke' by classpath reference, falling back to currently loaded value
io.ktor.server.engine.internal.ReloadingException: Module function cannot be found for the fully qualified name 'org.geobon.hpc.HPCConnectionTest$givenConfiguredWithErrors_whenPreparedManually_thenGetFailureMessage$1$1$1.invoke'
at io.ktor.server.engine.internal.CallableUtilsKt.executeModuleFunction(CallableUtils.kt:68)
at io.ktor.server.engine.EmbeddedServer$launchModuleByName$2.invokeSuspend(EmbeddedServerJvm.kt:443)
at io.ktor.server.engine.EmbeddedServer$launchModuleByName$2.invoke(EmbeddedServerJvm.kt)
at io.ktor.server.engine.EmbeddedServer$launchModuleByName$2.invoke(EmbeddedServerJvm.kt)
at io.ktor.server.engine.EmbeddedServer.avoidingDoubleStartupFor(EmbeddedServerJvm.kt:469)
at io.ktor.server.engine.EmbeddedServer.launchModuleByName(EmbeddedServerJvm.kt:442)
at io.ktor.server.engine.EmbeddedServer.access$launchModuleByName(EmbeddedServerJvm.kt:33)
at io.ktor.server.engine.EmbeddedServer$toDynamicModuleOrNull$1.invokeSuspend(EmbeddedServerJvm.kt:425)
at io.ktor.server.engine.EmbeddedServer$toDynamicModuleOrNull$1.invoke(EmbeddedServerJvm.kt)
at io.ktor.server.engine.EmbeddedServer$toDynamicModuleOrNull$1.invoke(EmbeddedServerJvm.kt)
at io.ktor.server.application.ApplicationModules_jvmKt$LoadSequentially$1.loadModules(ApplicationModules.jvm.kt:75)
at io.ktor.server.engine.EmbeddedServer$instantiateAndConfigureApplication$1$1.invokeSuspend(EmbeddedServerJvm.kt:385)
at io.ktor.server.engine.EmbeddedServer$instantiateAndConfigureApplication$1$1.invoke(EmbeddedServerJvm.kt)
at io.ktor.server.engine.EmbeddedServer$instantiateAndConfigureApplication$1$1.invoke(EmbeddedServerJvm.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:50)
at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:149)
at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:44)
at kotlinx.coroutines.TimeoutKt.withTimeout-KLykuaI(Timeout.kt:72)
at io.ktor.server.engine.EmbeddedServer$instantiateAndConfigureApplication$1.invokeSuspend(EmbeddedServerJvm.kt:384)
at io.ktor.server.engine.EmbeddedServer$instantiateAndConfigureApplication$1.invoke(EmbeddedServerJvm.kt)
at io.ktor.server.engine.EmbeddedServer$instantiateAndConfigureApplication$1.invoke(EmbeddedServerJvm.kt)
at io.ktor.server.engine.EmbeddedServer$avoidingDoubleStartup$1.invokeSuspend(EmbeddedServerJvm.kt:450)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:263)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:94)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:70)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at io.ktor.server.engine.EmbeddedServer.avoidingDoubleStartup(EmbeddedServerJvm.kt:449)
at io.ktor.server.engine.EmbeddedServer.instantiateAndConfigureApplication(EmbeddedServerJvm.kt:383)
at io.ktor.server.engine.EmbeddedServer.createApplication(EmbeddedServerJvm.kt:169)
at io.ktor.server.engine.EmbeddedServer.start(EmbeddedServerJvm.kt:314)
at io.ktor.server.engine.EmbeddedServer$startSuspend$2.invokeSuspend(EmbeddedServerJvm.kt:341)
at io.ktor.server.engine.EmbeddedServer$startSuspend$2.invoke(EmbeddedServerJvm.kt)
at io.ktor.server.engine.EmbeddedServer$startSuspend$2.invoke(EmbeddedServerJvm.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndspatched(Undispatched.kt:66)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
at io.ktor.server.engine.EmbeddedServer.startSuspend(EmbeddedServerJvm.kt:341)
at io.ktor.server.engine.EmbeddedServer.startSuspend$default(EmbeddedServerJvm.kt:340)
at io.ktor.server.testing.TestApplication.start(TestApplication.kt:103)
at io.ktor.server.testing.client.DelegatingTestClientEngine.execute(DelegatingTestClientEngine.kt:48)
at io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:183)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
This happens for any tests that starts with something like
@Test
fun givenConfiguredWithErrors_whenPreparedManually_thenGetFailureMessage() = testApplication {
application { scriptModule() }
application.yaml
ktor:
application:
modules:
- com.example.ApplicationKt.scriptModule
deployment:
port: 8080
Version info
kotlin("jvm") version "2.2.10"
ktorVersion=3.2.3
kotlinVersion=2.1.20
io.kotest:kotest-runner-junit5:6.0.3
Support for server side http2 without tls (h2c)
It it possible to add support for using http2 without TLS?
`var Route.staticRootFolder: File?` should be deprecated
Related: https://youtrack.jetbrains.com/issue/KTOR-5265
Version: 2.3.0
All usages of it are deprecated file/files/default are deprecated against staticFiles, but this "implementation detail" is still part of the public API and it always returns null once the migration happens.
Serve static resources with caching headers and ETag based on sha256 of content
Similar to what has been done for Webjars in https://youtrack.jetbrains.com/issue/KTOR-6073/Webjars-plugin-should-include-caching-headers-and-ETag-by-default , staticResources should be served with caching headers when the CachingHeaders and ConditionalHeaders plugins are present.
The ETag could be calculated on the fly for a resource the first time it is served and then cached for subsequent requests. It could for example be calculated based on the sha256 of the content of the resource.
Using 3.0.0-beta-1
SerializationException when Application.propertyOrNull() is called with type Map<String, Any?>
Application.propertyOrNull<T>(String) cannot read a value of Map<String, Any?> while Application.property<T>(String) can.
Minimal Reproducible Sample
Application.kt:
fun Application.propertySample() {
val data = property<Map<String, Any?>()
println("data = $data")
}
fun Application.propertyOrNullSample() {
val data = propertyOrNull<Map<String, Any?>()
println("data = $data")
}
application.conf:
ktor {
application {
modules = [
my.package.ApplicationKt.propertySample
my.package.ApplicationKt.propertyOrNullSample
]
}
}
data {
foo = bar
}
Expected
When provided with the above config, it does not fail.
Actual
When provided with the above config, it will fail with an error that looks roughly like the following:
Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.
Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
at kotlinx.serialization.internal.Platform_commonKt.serializerNotRegistered(Platform.common.kt:90)
at kotlinx.serialization.internal.PlatformKt.platformSpecificSerializerNotRegistered(Platform.kt:33)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:153)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
at kotlinx.serialization.SerializersKt__SerializersKt.serializersForParameters(Serializers.kt:295)
at kotlinx.serialization.SerializersKt.serializersForParameters(Unknown Source)
at kotlinx.serialization.SerializersKt__SerializersKt.serializerByKTypeImpl$SerializersKt__SerializersKt(Serializers.kt:253)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:152)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:77)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
at io.ktor.util.reflect.TypeKt.serializer(Type.kt:74)
at io.ktor.server.config.HoconApplicationConfig$HoconApplicationConfigValue.getAs(HoconApplicationConfig.kt:102)
at my.package.ApplicationKt.propertyOrNullSample(Application.kt:7)
If you inline the Application.propertyOrNull<T>(String) function, you will see the following:
val data = environment.config.propertyOrNull("data")?.getAs<E?>()
this is because in the Application.propertyOrNull<T>(String) function, ApplicationConfigValue.getAs<E>() infers the type based on the return type, which is T?.
going to PR a fix in a few, this is a single line fix.
staticFiles: when directory is requested, file listing or an error message should be returned
this code is not working
fun Application.download(iDownload: IShare) {
routing {
staticFiles(
"/download/statics",
iDownload.downloadDir()
)
route("/download") {
get("/apk") {
call.respondFile(iDownload.myApk())
}
}
}
}
this code is not working too
fun Application.module(app: IApp) {
mainModule(app.handler)
download(app.share)
api(app.handler)
fun static() {
routing {
staticFiles("/d", File("files"))
}
}
}
tested with
runBlocking {
val response01 = client.get("/d")
assertEquals(HttpStatusCode.OK, response01.status)
}
runBlocking {
val response02 = client.get("/download/statics")
assertEquals(HttpStatusCode.OK, response02.status)
}
all return 404
Static content: Support a custom respond logic if the file is not found
KTOR 3.1.3
Description of the issue:
KTOR staticFiles() and friends have a default() option to define content to send if the requested path does not exist.
I would like to see the option for other defaults, such as a 302 redirect to a specific path, rather than serving that content as though it were original.
This may seem like additional overhead since it results in an additional request that must be processed but, in reality, the vast majority of 404 requests are bots looking for vulnerabilities and so serving a default file turns out to be significantly more wasteful. I'd rather just redirect to the main page since it'll be ignored by the bot anyway and still be safe for the rare real request.
"Failed resolution of: Ljava/lang/management/ManagementFactory" on Android when JvmGcMetrics are initialized
When using the ktor-server-metrics-micrometer plugin in an Android environment, the server fails to start due to a NoClassDefFoundError:
{width=70%}
This happens during plugin initialization in MicrometerMetricsConfig, even when I explicitly configure the plugin with an empty meterBinders list:
fun Application.configureMicrometerMetrics() {
val appMicrometerRegistry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT)
install(MicrometerMetrics){
registry = appMicrometerRegistry
meterBinders = emptyList()
}
routing {
get("/metrics") {
call.respond(appMicrometerRegistry.scrape())
}
}
}
It seems that JvmGcMetrics is still being instantiated internally, which leads to a crash on Android, as java.lang.management.ManagementFactory is not available in the Android runtime.
Suggested Fix: Guard the usage of JvmGcMetrics and other JVM-only binders behind an explicit condition, or better yet, avoid instantiating them unless explicitly added to meterBinders.
Environment:
• Ktor version: 3.2.2
• Micrometer version: 1.15.2
• Android API level: 35
• Runtime: Android (via app_process)
Other
WebRTC Client, Android + WASM
As a multi-platform Kotlin developer, I would like to use Ktor for connecting with other clients through WebRTC, so that I can develop networked clients using a single codebase.
OpenAPI generation build extension preview
Introduce the OpenAPI extension to the Ktor gradle plugin, and incorporate a new compiler plugin that will visit Ktor route function calls and parse the preceding documentation to supply the details of the OpenAPI specification.
- [x] Resolve paths
- [x] Nested route functions are merged
- [x] Local extension functions are merged
- [x] Function arguments are traced to string literals in paths
- [x] KDoc parameters are merged along the stack
- [x] Support
Resourcepaths
- [x] KDoc fields:
- [x]
@path - [x]
@query - [x]
@header - [x]
@cookie - [x]
@body - [x]
@response - [x]
@deprecated - [x]
@description - [x]
@security - [x]
@externalDocs
- [x]
- [x] Type references in KDoc
- [x] Primitive types
[Type] - [x] Optional types
[Type]? - [x] Arrays
[Type]+ - [x] Maps
:[Bar]
- [x] Primitive types
- [x] Request / response bodies inference
- [x]
call.receive() - [x]
call.respond() - [x]
call.respondText/Redirect/OutputStream()etc
- [x]
- [x] Json schema attributes for parameters / responses
- [x] authentication / authenticated DSL
- [x]
@ignore
Sample Prototype for WebRTC client
Implementation for WebRTC client API
Translate Typescript WebRTC API to Kotlin
Upgrade to Kotlin 2.2
- [x] Update kotlinx-io to v0.8.0+
- [x] Update atomicfu to v0.29.0+
- [x] Update serialization to v1.9.0+
- [x] Update Kotlin version badge in readme
Bump Kotlin API level to 2.2
We need at least 2.1 API level to be able to use Instant and Clock from kotlin.time.