3

I want to write a custom Throwable that is unchecked.

There are ways to trick the compiler at throw time (e.g. Utility class that re-throws a throwable as unchecked?), which I have implemented thus:

public class CustomThrowable extends Throwable {
    public CustomThrowable(String message) {
        super(message);
    }
    @SuppressWarnings("unchecked")
    public <T extends Throwable> T unchecked() throws T {
        throw (T) this;
    }
}

but I'm running into issues at "catch time":

try { 
    throw new CustomThrowable("foo").unchecked();
}
catch (CustomThrowable t) { } // <- compiler error because the try block does not "throw" a CustomThrowable

Is there a way to simply implement an unchecked Throwable, or is RuntimeException the only way to do that? I had wanted to avoid inheriting from RuntimeException because my throwable is not an exception, but instead a yield instruction.

Update: Another reason to avoid extending RuntimeException is that my CustomThrowable will get caught by generic catch (Exception ex) { } blocks. Thus, if I want to communicate information from within the stack, each layer needs to potentially be aware that CustomThrowable might come through and explicitly catch-rethrow it; this awareness is a large part of what one is trying to avoid when using the Throwable design.

Gordon Bean
  • 4,272
  • 1
  • 32
  • 47
  • 2
    You know that RuntimeException extends Throwable? – Thorbjørn Ravn Andersen Sep 18 '19 at 18:39
  • Yes - I was avoiding extending `RuntimeException` because 1) my throwable is not an exception, and 2) I want to avoid my throwable getting caught by `catch(Exception e) { }` blocks. – Gordon Bean Sep 18 '19 at 18:42
  • Throwables are unchecked (or rather do not need to be declared). The link you refer to, is about fooling the compiler about checked _exceptions_ which does not apply to Throwables (and Word of God explicitly says it is a Bad Idea (TM)). Have you tried just throwing CustomThrowable directly? – Thorbjørn Ravn Andersen Sep 18 '19 at 18:50
  • 4
    I cannot reconcile your statement "Throwables are unchecked" with the compiler errors I see "Unhandled exception". Unless there is a bizarre bug in IntelliJ, I have empirical evidence that Throwables are checked. – Gordon Bean Sep 18 '19 at 19:03
  • Show your complete code. Please make it as brief as possible while stil showing your problem. – Thorbjørn Ravn Andersen Sep 18 '19 at 19:15
  • 3
    @ThorbjørnRavnAndersen: Throwable are checked. See Java Doc: *For the purposes of compile-time checking of exceptions, Throwable and any subclass of Throwable that is not also a subclass of either RuntimeException or Error are regarded as **checked** exceptions.* – mentallurg Sep 18 '19 at 19:22
  • 1
    It appears I got Throwable mixed up with Error (which is unchecked) in my mind. My bad for not actually checking before sounding knowledgeable. – Thorbjørn Ravn Andersen Sep 18 '19 at 19:41
  • What is the point of this whole exercise? – Robert Sep 18 '19 at 20:33
  • 1
    "I had wanted to avoid inheriting from RuntimeException because my throwable is not an exception, but instead a yield instruction." - Using exceptions/throwables for cases that are _expected to occur during normal operation_, rather than to handle errors and other exceptional circumstances, is not recommended. This includes flow control and "returning from multiple methods in one go". One of the reasons why is because throwing a Throwable is considerably slower than, say, returning a value. – BambooleanLogic Sep 18 '19 at 20:46

4 Answers4

1

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.

mentallurg
  • 4,967
  • 5
  • 28
  • 36
  • The problem is not in the Java compiler, but that you explicitly suppress the unchecked warning. – Thorbjørn Ravn Andersen Sep 18 '19 at 22:34
  • 2
    @ThorbjørnRavnAndersen: No. Suppressed it type casting. You cannot suppress exception. It is just *warning, not a compiler error". We can remove this Suppress and the code will still compile, i.e. .class file will be created and can be executed. Where as if there were unlatched exception, then there would be compiler error and .class file would not be created and it would be impossible to executed anything. So your comment does not explain anything, unfortunately. – mentallurg Sep 18 '19 at 22:38
  • Try removing `@SuppressWarnings("unchecked")`and see what happens. – Thorbjørn Ravn Andersen Sep 18 '19 at 22:43
  • 1
    After removing the result is the same: Java compiler has created .class file. If you open the source in IDE, it shows warning when I have default severity settings. But the code is *successfully* compiled, .class file was created and I successfully executed it. Have *you* tried it? ;-) – mentallurg Sep 18 '19 at 23:38
  • Of course removing a suppress warning results in a warning, not a compilation error. The warning explicitly tells you there is something wrong with your code (but not that the code is illegal). This is not a bug in the Java compiler because it strictly adheres to the JLS but is a design decision when adding Generics. This is just a very clever way to explicitly trick the compiler. – Thorbjørn Ravn Andersen Sep 19 '19 at 06:20
  • 2
    No. This warning has nothing to do with the effect we are talking about. – mentallurg Sep 19 '19 at 16:22
1

You can extend Error rather than Throwable. The Java Error class is an unchecked Throwable.

Nathaniel Jones
  • 1,829
  • 3
  • 29
  • 27
  • 1
    The question is rather about throwing any `Throwable`as unchecked exception, without having to use try/catch and without having to declare it in the `throws` clause. – mentallurg Sep 18 '19 at 22:03
  • This is not a bad idea, as it will avoid `catch (Exception ex) { }` blocks - if I can't find anything more direct, I may give this a try. – Gordon Bean Sep 18 '19 at 22:41
0

Per Why runtime exception is unchecked exception? (and the unquestionable authority of Jon Skeet ;), it appears that unchecked-exception support is built into the java compiler, and is probably not something I can plug into.

Quoting from Jon's post:

It's explicitly in the specification, section 11.1.1:

RuntimeException and all its subclasses are, collectively, the runtime exception classes.

The unchecked exception classes are the runtime exception classes and the error classes.

The checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are all subclasses of Throwable other than RuntimeException and its subclasses and Error and its subclasses.

So yes, the compiler definitely knows about RuntimeException.

I'm still hoping that someone else will come up with a "yes you can" answer, so I'll wait a few more days before closing this question.

Gordon Bean
  • 4,272
  • 1
  • 32
  • 47
  • Yes, you cannot define a subclass of Throwable that you can throw without declaring it in the `throws` clause. But there is a way how to throw *any* checked exception without having to declare it in the `throws` clause. See my example above: You can call `ExceptionUtil.throwUnchecked(checkedException);` *without* having to declare `throws`. That's why it is not necessary to look for some exceptions that you could throw as unchecked. With the approach that I described you can throw *any* checked exception as unchecked, without declaring it. – mentallurg Sep 18 '19 at 22:49
  • Yes - you'll see in my question that I already use a similar tactic to throw the exception. However, when I try to catch the exception, I get a compiler error because it "knows" that I am trying to catch a checked exception, and therefore there should be a `throws` declaration somewhere in the `try` block (which there isn't, because I convert it to an unchecked exception). So, if you are just trying to throw checked exceptions, you are fine, but if you want to catch them too you have a problem. – Gordon Bean Sep 18 '19 at 23:19
  • In the comment below you say that you want to avoid `catch`. Perfect. Look at my approach. In the method `throwUnchecked` there is no `throws` clause and you don't need to catch anything. See the usage in the class `Test`: Methods `doSomething` just throws checked exception as unchecked and uses no try/catch, no `throws` clause. Why doesn't it fit your expectation? – mentallurg Sep 18 '19 at 23:35
0

This approach is fundamentally the same as mentallurg's but it avoids the unnecessary wrapper class.

public final class ThrowUtil {
    @SuppressWarnings("unchecked")
    private static <T extends Throwable> void throwUnchecked0(Throwable t) throws T {
        // because `T` is a generic it will be erased at compile time
        // the cast will be replaced with `(Throwable) t` which will always succeed
        throw (T) t;
    }

    public static void throwUnchecked(Throwable t) {
        throwUnchecked0(t);
    }

    private ThrowUtil() {}
}

In your code:

// no throws clause
public void foo() {
    ThrowUtil.throwUnchecked(new IllegalAccessException("you are not allowed to call foo"));
}

And for the sake of completeness, here's the generated bytecode for comparison:

  private static <T extends java.lang.Throwable> void throwUnchecked0(java.lang.Throwable) throws T;
    Code:
       0: aload_0
       1: athrow

  public static void throwUnchecked(java.lang.Throwable);
    Code:
       0: aload_0
       1: invokestatic  #1                  // Method throwUnchecked0:(Ljava/lang/Throwable;)V
       4: return

If throwUnchecked0 is made to take a parameter of type T instead of any Throwable then the requirement for either a throws clause or a try/catch statement will be propagated to callers1.

Interestingly, this is very different behaviour from generics in most contexts. For example, if the code is changed so that the internal method returns T instead of throwing T and then the public method throws that return value, the type of T is inferred to be Throwable and the throws clause requirement will be propagated. Therefore it does seem likely that this is a bug in the Java compiler, albeit one that I would like to see remain since it would still be possible directly in bytecode (and other JVM languages like Kotlin use this extensively) and can be useful in some cases.

The unchecked cast to T is completely2 safe because T is erased at compile time and replaced with Throwable, which will always succeed because the parameter is a Throwable.

Hiding the real implementation from the public api is not strictly necessary. It simply serves to make clear to readers (and enforce) that the generic type is intended to be completely ignored and omitted from call-sites.


1 The caller could also explicitly specify a type for the generic parameter and cause the requirement for a throws clause to propagate. That's part of the reason for the public wrapper method.

2 Well, only in the sense that it will never fail at runtime. After all, we are throwing unchecked exceptions.

Michael Pfaff
  • 1,178
  • 1
  • 14
  • 24