¿Cuál es la causa más probable de que las excepciones escapen misteriosamente de un bloque try-catch en este caso?

Estoy usando Spring WebClient en un proyecto de Kotlin como este:

data class DTO(val name: String) @Component class Runner: ApplicationRunner { override fun run(args: ApplicationArguments?) { try { val dto = get<DTO>() } catch (e: Exception) { println("ERROR, all exceptions should have been caught in 'get' ") } } } inline private fun<reified TResult: Any> get(): TResult? { var result: TResult? = null try { result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting") .get() .retrieve() .bodyToMono<TResult>() .block() } catch (e: Exception) { println("WORKS AS EXPECTED!!") } return result } 

El cliente arrojará una exception, porque la API devolverá un 404. Sin embargo, la exception no se detecta donde debería estar, concretamente en el cuerpo de la function get , sino que se propaga al controller externo de excepciones.

Es interesante observar que esto sucede solo si el WebClient lanza la exception. Si reemploop el código en la cláusula try con un simple throw Exception("error") , la exception queda atrapada donde debería estar.

Del mismo modo, cuando cambio la firma de get a una inline private fun get(): DTO? no genérica inline private fun get(): DTO? el problema también desaparece

Una exception para escaping del bloque try-catch parece una falla fundamental en las herramientas de Kotlin. Por otro lado, el hecho de que esto ocurra solo con la class WebClient indica que se trata de un problema de Spring. O bien, puede ser solo yo, usando las herramientas de una manera incorrecta.

Estoy realmente desconcertado aquí y no tengo idea de cómo proceder. Cualquier idea sobre por qué esto podría estar pasando es bienvenida. Para completar, esto es lo que parece en el depurador:

enter image description here

EDITAR

El problema desaparece después de actualizar Spring Boot a 2.0.0.M6, todavía está presente en M5.

Parece que esto fue un problema de spring y no un problema de Kotlin. Por otro lado, sería agradable entender cómo una biblioteca que usted incluye puede aparentemente hacer que el progtwig viole las leyes del lenguaje de progtwigción en el que está escrito.

2.0.0.M5 el código con Spring Boot versión 2.0.0.M5 y 2.0.0.M6 , y parece que el comportamiento del siguiente bloque es diferente entre esas 2 versiones:

 result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting") .get() .retrieve() .bodyToMono<TResult>() .block() 

en algún punto de la cadena, en Spring Boot 2.0.0.M5 , se devuelve la WebClientResponseException , en Spring Boot 2.0.0.M6 se lanza .

Si agrega un e.printStackTrace() a su captura externa, notará que el seguimiento de la stack es:

java.lang.ClassCastException: org.springframework.web.reactive.function.client.WebClientResponseException no se puede convertir en com.example.demo.DTO en com.example.demo.Runner.run (Test.kt: 18) en org. springframework.boot.SpringApplication.callRunner (SpringApplication.java:780) at org.springframework.boot.SpringApplication.callRunners (SpringApplication.java:770) at org.springframework.boot.SpringApplication.afterRefresh (SpringApplication.java:760) at org .springframework.boot.SpringApplication.run (SpringApplication.java:328) at org.springframework.boot.SpringApplication.run (SpringApplication.java:1245) at org.springframework.boot.SpringApplication.run (SpringApplication.java:1233) at com.example.demo.DemoApplicationKt.main (DemoApplication.kt: 10)

Entonces, en realidad, el problema es que la WebClientResponseException devuelta se intenta convertir a la class DTO en el momento del retorno de la llamada val dto = get<DTO>() . Esto significa que, cuando asigna result = ... , todavía no se ha realizado ninguna comprobación de tipo. Entonces, si cambia su código a, por ejemplo, llame a get<Object>() lugar de get<DTO>() , no golpeará ningún bloque catch.

Si lo convierte en bytecode en IntelliJ Idea, y luego lo descomstack en Java, puede ver este bloque:

 public class Runner implements ApplicationRunner { public void run(@Nullable ApplicationArguments args) { try { Object result$iv = null; try { ResponseSpec $receiver$iv$iv = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting").get().retrieve(); Mono var10000 = $receiver$iv$iv.bodyToMono((ParameterizedTypeReference)(new Runner$run$$inlined$get$1())); Intrinsics.checkExpressionValueIsNotNull(var10000, "bodyToMono(object : Para…zedTypeReference<T>() {})"); result$iv = var10000.block(); } catch (Exception var7) { String var5 = "WORKS AS EXPECTED!!"; System.out.println(var5); } DTO var2 = (DTO)result$iv; } catch (Exception var8) { String var3 = "ERROR, all exceptions should have been caught in 'get' "; System.out.println(var3); } } } 

Aquí puede observar que la conversión a DTO se realiza en el punto de devolución del método (que ya no es una devolución porque está en línea ), después del bloque catch interno: DTO var2 = (DTO)result$iv; . Parece que ese es el comportamiento de los methods integrados con parameters de tipo reificado.

Esto se debe a SPR-16025 (consulte la confirmación relacionada ) ya que la extensión de Kotlin está utilizando internamente la variante ParameterizedTypeReference , que se ha corregido en Spring Framework 5.0.1 y de forma transitiva en Spring Boot 2.0.0.M6.

Tenga en count que si usa bodyToMono(TResult::class.java) con Spring Boot 2.0.0.M5, funcionará como se espera.