Quarkus升级后Kotlin协程中使用REST客户端遇ClassNotFoundException
Quarkus 3.15.2中Kotlin协程结合REST客户端触发ClassNotFoundException问题
问题背景
将Quarkus从3.8.4升级至3.15.2(最新LTS版本)后,使用Kotlin协程(suspend函数+withContext(Dispatchers.IO))调用REST客户端时触发ClassNotFoundException,移除协程相关代码后可正常运行。本次升级涉及Quarkus依赖重构(RESTEasy-Reactive更名为REST),同时同步升级了Kotlin版本(1.9.21→2.0.21)及编译器插件版本(3.11.0→3.12.1)。
原可运行代码(Quarkus 3.8.4)
@Inject @RestClient lateinit var myRemoteService: MyRemoteService @GET @Produces(MediaType.TEXT_PLAIN) suspend fun hello(): String { println(doSomething().toString()) return "Hello from Quarkus REST" } suspend fun doSomething(): Set<MyRemoteService.Extension>? { var restClientExtensions: Set<MyRemoteService.Extension>? = null withContext(Dispatchers.IO) { restClientExtensions = myRemoteService.getExtensionsById("io.quarkus:quarkus-rest-client") } return restClientExtensions }
升级后抛出的异常
2024-12-13 12:01:05,768 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (vert.x-eventloop-thread-1) HTTP Request to /hello failed, error id: d40a9779-93d1-4b50-ab05-d3adb047da96-3: jakarta.enterprise.inject.CreationException: java.lang.ClassNotFoundException: org.acme.MyRemoteService at org.acme.MyRemoteService$$CDIWrapper_Bean.create(Unknown Source) at org.acme.MyRemoteService$$CDIWrapper_Bean.create(Unknown Source) at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:119) at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:38) at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:35) at io.quarkus.arc.generator.Default_jakarta_enterprise_context_ApplicationScoped_ContextInstances.c1(Unknown Source) at io.quarkus.arc.generator.Default_jakarta_enterprise_context_ApplicationScoped_ContextInstances.computeIfAbsent(Unknown Source) at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:35) at io.quarkus.arc.impl.ClientProxies.getApplicationScopedDelegate(ClientProxies.java:21) at org.acme.MyRemoteService$$CDIWrapper_ClientProxy.arc$delegate(Unknown Source) at org.acme.MyRemoteService$$CDIWrapper_ClientProxy.getExtensionsById(Unknown Source) at org.acme.GreetingResource$doSomething$2.invokeSuspend(GreetingResource.kt:32) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:101) at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:113) at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:589) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:823) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:720) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:707) Caused by: java.lang.ClassNotFoundException: org.acme.MyRemoteService at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:542) at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:517) at java.base/java.lang.Class.forName0(Native Method) at java.base/java.lang.Class.forName(Class.java:467) at org.acme.MyRemoteService$$CDIWrapper.<init>(Unknown Source) at org.acme.MyRemoteService$$CDIWrapper_Bean.doCreate(Unknown Source) ... 20 more
移除协程后可正常运行的代码
@GET @Produces(MediaType.TEXT_PLAIN) fun hello(): String { println(doSomething().toString()) return "Hello from Quarkus REST" } fun doSomething(): Set<MyRemoteService.Extension>? { var restClientExtensions: Set<MyRemoteService.Extension>? = null //withContext(Dispatchers.IO) { // Commented out restClientExtensions = myRemoteService.getExtensionsById("io.quarkus:quarkus-rest-client") //} return restClientExtensions }
问题原因分析
类加载器上下文不匹配:
Dispatchers.IO使用JVM默认线程池,其类加载器并非Quarkus专属的QuarkusClassLoader。而REST客户端的CDI代理类由Quarkus在应用类加载器中生成,当在非Quarkus管理的线程中调用代理方法时,无法找到目标类。REST模块重构的影响:Quarkus 3.9+将RESTEasy-Reactive重构为REST模块,协程与REST客户端的集成逻辑发生变更,原有的协程上下文传递机制失效,导致自定义调度器线程无法正确加载CDI代理类。
Kotlin版本升级兼容性问题:Kotlin 2.0.x对协程的字节码生成、上下文传递逻辑做了调整,与Quarkus 3.15.x的类加载和CDI代理机制存在兼容性冲突,进一步触发类加载失败。
解决方案建议
- 移除
withContext(Dispatchers.IO):Quarkus的REST客户端基于Vert.x实现非阻塞调用,无需手动切换到IO调度器,直接在Quarkus管理的协程上下文中执行即可。 - 检查REST客户端配置:确保接口添加
@RegisterRestClient注解,且application.properties中对应的客户端配置完整。 - 对齐扩展版本:确保
quarkus-kotlin、quarkus-coroutines等扩展版本与Quarkus平台版本3.15.2完全匹配,避免版本不一致导致的兼容性问题。
内容的提问来源于stack exchange,提问作者Nitish
相关产品推荐
相关产品推荐

