I have an extension
method that converts string to Date in Kotlin.
fun String.convertToDate() : Date {
var pattern: String = \"dd-mm-yyyy\"
val
The UndeclaredThrowableException
is caused by kotlin. why? we know kotlin does not have checked exceptions.
The documentation of UndeclaredThrowableException says:
Thrown by a method invocation on a proxy instance if its invocation handler's invoke method throws a checked exception
On the other hand, Kotlin can throw any exceptions but in java them will are: unchecked/checked exception and error.
we know almost all of the popular frameworks is created base on java.reflect
package includes java.reflect.Proxy.
In short, when the kotlin function throw a java checked exception and don't declare the exception that it will maybe throwing. then call a java Proxy you maybe receive such a UndeclaredThrowableException.
In java you can declare a checked-exception will be throwing as below:
// v--- it is a checked exception in java
int read() throws IOException{/**/}
Thanks for @glee8e to points my mistake.
you also can throws an exception in kotlin, since kotlin don't have throws
keyword so you must using @Throws
to declare an exception will be throwing:
@Throws(IOException::class)
fun read():Int{/**/}
let's reproduce the UndeclaredThrowableException in kotlin:
//throws a UndeclaredThrowableException takes the original IOException as its cause
// because java.lang.Runnable don't declare any checked exception at all
// |
// v
Runnable::class.proxying(::throwsAJavaCheckedException).run()
// throws the original IOException directly because java.util.concurrent.Callable
// has declared that it will be throwing a checked Exception
// |
// v
Callable::class.proxying(::throwsAJavaCheckedException).call()
fun throwsAJavaCheckedException(proxy:Any, method:Method, args:Array<Any>?): Any? {
throw IOException();
}
typealias Invocation = (Any, Method, Array<Any>?) -> Any?;
fun <T:Any> KClass<T>.proxying(handler:Invocation) = cast(Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
arrayOf(java),
handler
));
If the function is wrote by yourself the solution is so simpler. yes, declare the function will be throwing a checked exception. for example:
@Throws(ParseException::class)
fun convertToDate(){/**/}
OR write some gradle-plugin like as allopen
, I named it allthrows
here.
But you also can make some compromises. If you are not sure what will be happens in the frameworks like as spring, you should wrap your invocation into a helper method. for example:
val task = Runnable::class.proxying(::throwsAJavaCheckedException)
// v-- the result return by catch-block immediately if no exception occurs
val result : Unit = catch(task::run).by { actual: Throwable ->
val exceptional: Unit = Unit;
// v--- you can choose return an exceptional value or rethrow the exception
when (actual) {
is RuntimeException -> exceptional
is ParseException -> logger.info(acutal)
else -> throw actual
}
}
val result : Unit? = catch(task::run).only { actual:Throwable ->
// only handle the exception don't return the exceptional value
logger.info(actual);
}
inline fun <T> catch(crossinline block: () -> T): () -> T {
return { block(); };
}
inline fun <T> (() -> T).by(exceptionally: (Throwable) -> T): T {
return only { exceptionally(it) }!!
}
inline fun <T : R, R> (() -> T).only(exceptionally: (Throwable) -> R): R? {
try {
return invoke();
} catch(e: UndeclaredThrowableException) {
return exceptionally(e.cause ?: e);
} catch(e: Exception) {
return exceptionally(e);
}
}
It seems that your service is running in a different invocation context than your controller. As the service is throwing the exception you cannot catch it in the controller; it looks like you're calling the service directly, but due to code injection you really aren't. So what happens is that the invocation context (usually a thread) ends with an exception. This gets translated into a UndeclaredThrowableException
with the original exception as cause.
There are two ways of dealing with this:
UndeclaredThrowableException
in a separate try/catch and then re-throw the cause.The first option should be preferred but requires you to handle the exception in the service. The second one looks too much like a hack to me, but it doesn't require setting up the exception handling in the service instead of the controller.