Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

java.lang.ClassCastException: class kotlin.Result cannot be cast to class java.lang.String (kotlin.Result is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap') #3403

Closed
papo2608 opened this issue Aug 11, 2022 · 7 comments

Comments

@papo2608
Copy link

papo2608 commented Aug 11, 2022

With following sample code I get above error when kotlin.Result is used with a suspend function and a runtime exception is thrown in the Retrofit call factory - this works though with e.g. sealed or data classes.

class CrashingResultCallAdapterTest {

  @get:Rule val server = MockWebServer()

  @Test
  fun crash(): Unit = runBlocking {
    val retrofit = Retrofit.Builder()
      .baseUrl(server.url("/"))
      .addConverterFactory(StringConverterFactory())
      .addCallAdapterFactory(SuspendResultCallAdapterFactory())
      .callFactory { throw IllegalStateException("some error") }
      .build()
    val service = retrofit.create(ResultService::class.java)
    server.enqueue(
      MockResponse()
        .setResponseCode(200)
        .setHeader("content-type", "text/plain")
        .setBody("bar")
    )

    assertThat(service.fooBarResult().getOrThrow()).isEqualTo("bar")
  }

  @Test
  fun success(): Unit = runBlocking {
    val retrofit = Retrofit.Builder()
      .baseUrl(server.url("/"))
      .addConverterFactory(StringConverterFactory())
      .addCallAdapterFactory(SuspendResultCallAdapterFactory())
      .build()
    val service = retrofit.create(ResultService::class.java)
    server.enqueue(
      MockResponse()
        .setResponseCode(200)
        .setHeader("content-type", "text/plain")
        .setBody("bar")
    )

    assertThat(service.fooBarResult().getOrThrow()).isEqualTo("bar")

    server.enqueue(
      MockResponse()
        .setResponseCode(404)
        .setHeader("content-type", "text/plain")
        .setBody("barerror")
    )

    assertThat(service.fooBarResult().exceptionOrNull()).isInstanceOf(HttpException::class.java)

    server.enqueue(MockResponse().setSocketPolicy(DISCONNECT_AFTER_REQUEST))

    assertThat(service.fooBarResult().exceptionOrNull()).isInstanceOf(SocketTimeoutException::class.java)
  }

  private interface ResultService {

    @GET("/")
    suspend fun fooBarResult(): Result<String>

  }

  private class StringConverterFactory : Converter.Factory() {

    override fun requestBodyConverter(
      type: Type, parameterAnnotations: Array<out Annotation>, methodAnnotations: Array<out Annotation>,
      retrofit: Retrofit
    ): Converter<*, RequestBody> {
      return Converter<String, RequestBody> { value -> value.toRequestBody("text/plain".toMediaType()) }
    }

    override fun responseBodyConverter(
      type: Type, annotations: Array<out Annotation>, retrofit: Retrofit
    ): Converter<ResponseBody, *> {
      return Converter<ResponseBody, String> { value -> value.string() }
    }

  }

  private class SuspendResultCallAdapterFactory : CallAdapter.Factory() {

    override fun get(
      returnType: Type,
      annotations: Array<out Annotation>,
      retrofit: Retrofit
    ): CallAdapter<*, *>? {

      if (getRawType(returnType) != Call::class.java) return null

      if (returnType !is ParameterizedType) return null

      val resultType: Type = getParameterUpperBound(0, returnType)
      if (getRawType(resultType) != Result::class.java || resultType !is ParameterizedType
      ) return null

      val delegate: CallAdapter<*, *> = retrofit.nextCallAdapter(this, returnType, annotations)

      return CatchingCallAdapter(delegate)
    }

    private class CatchingCallAdapter(
      private val delegate: CallAdapter<*, *>,
    ) : CallAdapter<Any, Call<Result<*>>> {

      override fun responseType(): Type = delegate.responseType()

      override fun adapt(call: Call<Any>): Call<Result<*>> = CatchingCall(call)

    }

    private class CatchingCall(
      private val delegate: Call<Any>,
    ) : Call<Result<*>> {

      override fun enqueue(callback: Callback<Result<*>>) = delegate.enqueue(object : Callback<Any> {
        override fun onResponse(call: Call<Any>, response: Response<Any>) {
          if (response.isSuccessful) {
            val body = response.body()
            callback.onResponse(this@CatchingCall, Response.success(Result.success(body)))
          } else {
            val throwable = HttpException(response)
            callback.onResponse(this@CatchingCall, Response.success(Result.failure<Any>(throwable)))
          }
        }

        override fun onFailure(call: Call<Any>, t: Throwable) {
          callback.onResponse(this@CatchingCall, Response.success(Result.failure<Any>(t)))
        }
      })

      override fun clone(): Call<Result<*>> = CatchingCall(delegate)

      override fun execute(): Response<Result<*>> =
        throw UnsupportedOperationException("Suspend function should not be blocking.")

      override fun isExecuted(): Boolean = delegate.isExecuted

      override fun cancel(): Unit = delegate.cancel()

      override fun isCanceled(): Boolean = delegate.isCanceled

      override fun request(): Request = delegate.request()

      override fun timeout(): Timeout = delegate.timeout()

    }
  }

}

I'm not sure what the root cause here is.

@qwwdfsad
Copy link
Member

qwwdfsad commented Aug 11, 2022

Could you please specify Kotlin version you are using and provide the whole stacktrace?

@papo2608
Copy link
Author

Could you please specify Kotlin version you are using and provide the whole stacktrace?

Sry, here we go:
Kotlin: 1.7.10
Kotlin coroutines: 1.6.4

(Retrofit 2.9.0 and OkHttp 4.10.0)

@ivakub
Copy link

ivakub commented Aug 11, 2022

@papo2608 Can you attach a sample project and the whole stacktrace?

@papo2608
Copy link
Author

@ivakub sure

class kotlin.Result cannot be cast to class java.lang.String (kotlin.Result is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
java.lang.ClassCastException: class kotlin.Result cannot be cast to class java.lang.String (kotlin.Result is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
	at com.example.myapplication.CrashingResultCallAdapterTest$crash$1.invokeSuspend(CrashingResultCallAdapterTest.kt:48)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
	at com.example.myapplication.CrashingResultCallAdapterTest.crash(CrashingResultCallAdapterTest.kt:33)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:54)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

MyApplication.zip

@ivakub
Copy link

ivakub commented Aug 11, 2022

@papo2608 Thank you! This is a bug in the backend. I created an issue — https://youtrack.jetbrains.com/issue/KT-53559/JVM-ClassCastException-class-kotlinResult-cannot-be-cast-to-class-javalangString-with-Retrofit. Feel free to follow it.

@rohangaurat
Copy link

how to fix this the issue

@wakaztahir
Copy link

wakaztahir commented Nov 22, 2023

In my code I was getting this issue, My functions were suspend inline functions present in another module, After removing inline My code works now !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants