Throwable
is a checked error and actually it should be impossible to throw such errors, except of cases when it is a RuntimeException or an Error.
But actually it is possible. Here is an example.
Here is a utility that throws any unchecked exception as checked:
package org.mentallurg;
public class ExceptionUtil {
private static class ThrowableWrapper extends Throwable {
private Throwable throwable;
public ThrowableWrapper(Throwable throwable) {
super();
this.throwable = throwable;
}
@SuppressWarnings("unchecked")
public <T extends Throwable> T throwNested() throws T {
throw (T) throwable;
}
}
private static <T extends Throwable> T throwThis(T throwable) throws T {
throw throwable;
}
public static <T extends Throwable> void throwUnchecked(T throwable) {
new ThrowableWrapper(throwable).throwNested();
}
}
Here is an example of usage:
package org.mentallurg;
public class Test {
private static void doSomething() {
Exception checkedException = new Exception("I am checked exception");
ExceptionUtil.throwUnchecked(checkedException);
}
public static void main(String[] args) {
doSomething();
}
}
Pay attention that there is no throws Throwable
clause, neither in main
not in doSomething
. And there is no compile error. In case of RuntimeException
it would be understandable, but not in case of Throwable
.
If we execute it, we get following:
Exception in thread "main" java.lang.Exception: I am checked exception
at org.mentallurg.Test.doSomething(Test.java:6)
at org.mentallurg.Test.main(Test.java:11)
How it works?
The most important part is this one:
new ThrowableWrapper(throwable).throwNested();
Actually method throwNested
here can throw a Throwable
. That's why Java compiler should raise an error and should require that this line of code is either surrounded with try/catch or Throwable
clause should be added. But it doesn't. Why? I believe this is a flaw in Java compiler. Some comments on SO in other threads refer to type erasure, but they are incorrect, because type erasure is relevant during the run time, where as we are talking about the compile time.
It is interesting that the decompiled code shows that here a Throwable
(not RuntimeException
, not Error
) will be thrown:
public static <T extends java.lang.Throwable> void throwUnchecked(T);
Code:
0: new #28 // class org/mentallurg/ExceptionUtil$ThrowableWrapper
3: dup
4: aload_0
5: invokespecial #30 // Method org/mentallurg/ExceptionUtil$ThrowableWrapper."<init>":(Ljava/lang/Throwable;)V
8: invokevirtual #32 // Method org/mentallurg/ExceptionUtil$ThrowableWrapper.throwNested:()Ljava/lang/Throwable;
11: pop
12: return
This behaviour is not specific for Throwable
. We can replace it with a checked Exception
and will get the same result:
public <T extends Exception> T throwNested() throws T {
In all such cases if we replace generics with particular classes, then Java compiler will report an error, which is correct. In cases with generics Java compiler ignores checked exceptions and doesn't reports any error. That's why I believe that this is a bug in Java compiler.