14

I am working on a small helper that is supposed to invoke arbitrary code (passed in as lambda). The helper should catch certain exceptions, and throw them inside some wrapper. My "own" exceptions should not be wrapped but just re-thrown.

I came up with this code:

@FunctionalInterface
interface Processable<T, X extends Throwable> {
    public T apply() throws X;
}

class MyCheckedException extends Exception { ... }
class MyCheckedExceptionWrapper extends MyCheckedException { ... }

public class MyExceptionLogger<T, X extends Throwable> {
    public T process(Processable<T, X> processable) throws MyCheckedException {
        try {
            return processable.apply();
        } catch (MyCheckedException thrown) { // this line isn't accepted
            throw thrown;
        } catch (Exception | LinkageError thrown) {
            throw new MyCheckedExceptionWrapper(thrown);
        } catch (Throwable thrown) {
           ... just log
          return null; 
        }
    }
}

The above gives a compile error:

Unreachable catch block for MyCheckedException. This exception is never thrown from the try statement body MyExceptionLogger ...

In other words: although apply() is defined to throw some X extends Throwable I can't catch a specific checked exception when invoking that method.

I know that I can get to working code by catching Throwable, to then use instanceof checks - but I would like to understand why it is not possible to have a try/catch as outlined above.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • 2
    I'm pretty sure the spec mandates that caught checked exceptions need to be known to be potentially thrown by the try-block - and since `X` doesn't extend `MyCheckedException` the compiler doesn't know that exception would ever be thrown. Would changing `MyExceptionLogger` to `X extends MyCheckedException` be an option? – Thomas Aug 22 '17 at 11:16
  • @Thomas Not really. I want to use this for very different invocation scenarios. There can be *all kinds* of checked exceptions. Some of them derive from "my" own base exceptions, others do not. – GhostCat Aug 22 '17 at 11:18
  • 1
    During compile time Java doesn't know about any specific type of Exception that an implementation of `Processable` might throw, the only thing that is known, is that it is some subclass of `Throwable`. In this [answer](https://stackoverflow.com/questions/5899849/java-unreachable-catch-block-compiler-error) the respective parts of the Java spec are cited. But the spec is not too concrete regarding this issue. That is from reading the spec catching any checked exception should be fine, if a method declares `Throwable`. – dpr Aug 22 '17 at 11:26
  • 1
    [JLS 11.2.3](https://docs.oracle.com/javase/specs/jls/se8/html/jls-11.html#jls-11.2.3) states: "It is a compile-time error if a catch clause can catch checked exception class E1 and it is not the case that the try block corresponding to the catch clause can throw a checked exception class that is a subclass or superclass of E1, unless E1 is Exception or a superclass of Exception." As I read that the compiler doesn't know that your body could throw `MyCheckedException` or even `Exception`. – Thomas Aug 22 '17 at 11:26
  • @Thomas - you should have written down that in an answer ;-) – GhostCat Aug 22 '17 at 11:27
  • 1
    You're right, but I'll leave it a comment now as to not compete with blinkenlight's almost exact same answer ;) – Thomas Aug 22 '17 at 11:29
  • What you could do (though not that pretty): use `instanceof` and casts to throw `MyCheckedException` out of the `catch( Exception | LingkageError )` block. – Thomas Aug 22 '17 at 11:31
  • @Thomas All fine, I think we managed. And in case that answer gets even more upvotes, I will compensate further. And yes, the code I am currently looking at is doing that kind of catching/instanceof-ing. – GhostCat Aug 22 '17 at 11:37
  • Nah, you don't have to compensate ;) I'm here to help and learn, not to collect points. – Thomas Aug 22 '17 at 11:39
  • @Thomas I think people should upvote much more ... so I try to lead by example ;-) – GhostCat Aug 22 '17 at 12:11

2 Answers2

6

This behavior is specified in section 11.2.3 of JLS:

It is a compile-time error if a catch clause can catch checked exception class E1 and it is not the case that the try block corresponding to the catch clause can throw a checked exception class that is a subclass or superclass of E1, unless E1 is Exception or a superclass of Exception.

MyCheckedException fits the description of E1 class above, because it is not declared in the generic declaration of processable.apply(), and it is not Exception or one of its superclasses. The compiler knows only that the method can throw a Throwable, so MyCheckedException is not declared.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Actually by declaring `Throwable` the try-block could throw a sub-class of `MyCheckedException`. That is according to the spec it should be fine to catch any checked exception, if `Throwable` is declared in the try-block. Correct me, if I'm wrong. – dpr Aug 22 '17 at 11:30
  • 1
    @dpr `Throwable` is not a checked exception. – Sergey Kalinichenko Aug 22 '17 at 11:32
  • 1
    @dpr To expand on blinkenlight's comment, the spec says "...a _checked exception_ class that is a subclass or superclass of E1", i.e. if `Exception` were used instead of `Throwable` it should work according to that definition - but not with `Throwable` itself. – Thomas Aug 22 '17 at 11:33
  • Yes, I got this. But by `Exception` is subclass of `Throwable`. That is by declaring `Throwable` the try-block can throw any type of exception including `MyCheckedException` – dpr Aug 22 '17 at 11:35
  • @Thomas I tried and changed my code to use `` - but the error messages sticks. When I change it to `` - then I can add that catch. But as written above - I can't do that because my lambdas will be throwing other stuff as well. – GhostCat Aug 22 '17 at 12:15
  • 1
    [11.1.1](https://docs.oracle.com/javase/specs/jls/se8/html/jls-11.html#jls-11.1.1) appears to state that `Throwable` is a checked exception. *"The checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are Throwable and all its subclasses other than RuntimeException and its subclasses and Error and its subclasses."* – Radiodef Aug 23 '17 at 02:02
  • 1
    Yes, `Throwable` is a checked exception. If you write `throw new Throwable("Catch me");` the compiler will require you to handle `Throwable`. – dpr Aug 23 '17 at 13:12
2

I think there is - based on the JSL - no good reason why you should not be able to catch your custom checked exception in your example.

If you read the cite from the JLS

It is a compile-time error if a catch clause can catch checked exception class E1 and it is not the case that the try block corresponding to the catch clause can throw a checked exception class that is a subclass or superclass of E1, unless E1 is Exception or a superclass of Exception.

A catch-clause should be allowed to catch any checked Exception, if a method in the corresponding try-block declares Throwable. In your example the try-block can throw a checked exception class that is a subclass or superclass of MyCheckedException namely Throwable and MyCheckedException is obviously not Exception or a superclass of Exception

This can easily be verified by removing the generics from the above example and see it compile without issues:

@FunctionalInterface
interface Processable<T> {
    public T apply() throws Throwable;
}


private <T> T process(Processable<T> aProcessable) {
    try {
        return aProcessable.apply();
    } catch (MyCheckedException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    } catch (Throwable e) {
        e.printStackTrace();
    }

    return null;
}

That is this problem somehow has to be related to the use of generics in combination with exceptions. Maybe this is related to type erasure, but with erased types your example works fine as well:

@FunctionalInterface
interface Processable {
    public Object apply() throws Throwable;
}


private Object process(Processable aProcessable) {
    try {
        return aProcessable.apply();
    } catch (MyCheckedException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    } catch (Throwable e) {
        e.printStackTrace();
    }

    return null;
}
dpr
  • 10,591
  • 3
  • 41
  • 71
  • 1
    I have to agree. In this case the solution is *really* about dropping the actually *pointless* generics for X. And for the record: I changed my code this morning and dropped that whole interface completely. Using util.Callable instead ... and that gets the job done too (and I actually do not need to have "throws Throwable" anywhere). – GhostCat Aug 23 '17 at 12:59