retrofit: UndeclaredThrowableException when using "suspend" modifier

Retrofit crashes with UndeclaredThrowableException when using suspend modifier and making multiple requests which are supposed to throw IOException.

Retrofit version: 2.6.0, 2.6.1-SNAPSHOT (20190612)

class RetrofitUndeclaredThrowableExceptionBug {

   interface Service {
        @GET("/")
        suspend fun download()
    }

    @Test
    fun test() = runBlocking {
        val retrofit = Retrofit.Builder()
            .baseUrl("https://unresolved-host.com/") // An unresolved host to simulate IOException
            .build()

        val service = retrofit.create<Service>(Service::class.java)

        // First attempt works fine
        try {
            service.download()
        } catch (e: IOException) {
            println("Catched 1: $e")
        }

        // Second attempt sometimes crashes
        try {
            service.download()
        } catch (e: IOException) {
            println("Catched 2: $e")
        }

        // And this will crash for sure (sometimes sooner, sometimes later)
        repeat(Integer.MAX_VALUE) {
            try {
                service.download()
            } catch (e: IOException) {
                println("Catched 3: $e")
            }
        }
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 9
  • Comments: 17 (10 by maintainers)

Most upvoted comments

The same happens when throwing an exception from an interceptor

There should be a fat warning on the release notes, that using suspend modifier for retrofit is not production ready.

Network calls can throw checked IOExceptions so you need to declare @Throws(IOException::class) on every single function on your interface or it will blow up at runtime.

I’m still getting the UndeclaredThrowableExceptions within my app. Our Retrofit services return Responses instead of POJOs. My hunch (that I’m about to try and verify) is that you applied the new yieldAndThrow logic only to SuspendForBody but didn’t apply the same logic to SuspendForResponse, and our services are using the currently unprotected SuspendForResponse

What’s the difference between a wrapping and a non-wrapping adapter? I’d like to test the difference in exceptions handling.

On Wed, Jul 24, 2019, 02:07 Jake Wharton notifications@github.com wrote:

This happens when using any non-wrapping call adapter, not just suspend. Despite the bytecode not caring about checked exceptions, they’re verified at proxy boundaries for better or worse.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/square/retrofit/issues/3128?email_source=notifications&email_token=ABVG6BK3RQ2XAMY34L24YVTQA6MKVA5CNFSM4HZCS6VKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2UYUSA#issuecomment-514427464, or mute the thread https://github.com/notifications/unsubscribe-auth/ABVG6BLUZLY2PQBVKCG4XF3QA6MKVANCNFSM4HZCS6VA .