0

I am working on a function which takes a CompletableFuture<Object> and needs to handle its result (or its exception). I am modifying it so that under a certain condition, I need to throw an exception.

However, the compiler tells me that I'm not handling this new exception (Unhandled exception ...).

The function looks like this:

  • In the line //<-- THIS IS NOT OK is what I'm just adding
  • In the line //<-- THIS IS OK is what was already there

Code:

public void myFunction(CompletableFuture<Object> resultSupplier, boolean someCondition) {
    resultSupplier.handleAsync((result, throwable) -> {
        if (throwable != null) {
            //do something with the throwable
        } else {
            if (someCondition) {
                throw new Throwable("some throwable"); //<-- THIS IS NOT OK ("Unhandled exception: java.lang.Throwable")
            }
            try {
                //do something with the result which may raise an exception
            } catch (Throwable ex) {
                //do something in the catch
                throw ex; //<-- THIS IS OK
            } finally {
                //do something to finalize
            }
        }
        return null; //I don't actually need the future, just to execute the code above
    });
}

I am having some troubles understanding this. Why the compiler is ok rethrowing the caught throwable inside the try block, but it's not ok with re-throwing the throwable that I've added?

I must say that I understand more the compile error (I'm inside a BiFunction<> so I can't throw checked exceptions) rather than the compiler's happiness over the throw ex inside the catch block, but mostly I would just like to understand what's going on here and why there is a difference between the two.

P.s. you can copy-paste the code snippet into an IDE to easily reproduce the issue.

Matteo NNZ
  • 11,930
  • 12
  • 52
  • 89
  • 1
    Possible duplicates/similar questions: [Why is throwing a checked exception type allowed in this case?](https://stackoverflow.com/questions/24981736/) and [Why is catching checked exceptions allowed for code that does not throw exceptions?](https://stackoverflow.com/questions/35184092/). – Slaw Jul 01 '21 at 05:55
  • @Slaw it is indeed an exact duplicate of the first link you posted. I just didn't type the right words when I did my first research. I voted to close my own question as duplicate – Matteo NNZ Jul 01 '21 at 09:09

1 Answers1

0

Please refer to answer\comment from @Slaw for answer.

INCORRECT ANSWER The reason is Throwable is base class of Exception. If catching code only handles Exception it won't catch it. The other one where you are rethrowing, may be caught as Throwable but thrown as probably it's original type.

Usually, a good static code analysis would tell you both things are no-no - don't throw explicit Throwable instance and don't rethrow caught instance; instead just throw; to use it polymorphically.

EDIT

This is what I found:

If I change the line you mentioned from

throw new Throwable("some throwable");

To:

throw new Exception("some throwable");

compiler still cribs.

So does if I change code to:

            try {
                 throw new Exception(); <=== change here
            } catch (Throwable ex) {
                //do something in the catch
                throw ex; //<-- THIS IS OK
            } finally {
                //do something to finalize
            }

The only way I can get the code to get compiled is when I use

throw new CompletionException(throwable);

It seems that CompletableFuture always deals with CompletionException so the only way for it to make happy is to wrap your throwable in CompletionException.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
  • I don't think this is the answer, even if I throw a very specific exception (instead of Throwable), I still get the same issue. Also, even if it was true that I catch the second Exception as Throwable but I re-throw it as its own type, the compiler has no way to know what I'm throwing so it should complain as well as it complains for the first throwable. – Matteo NNZ Jun 30 '21 at 13:53
  • P.s. you're right for the static code analysis, but there are some situations where catching and re-throwing Throwable is the only way to make sure your DB / Cache is consistent no matter what happens. That's one of these cases (it is the cache of a distributed service, I cannot afford to re-throw up or I'd leave inconsistencies inside the cache, and I cannot either control at all what the different methods called inside will ever throw). – Matteo NNZ Jun 30 '21 at 13:55
  • Thanks a lot for your time, but unfortunately it still doesn't answer my question :) I still feel the behavior of the compiler is inconsistent between the two cases while I would expect it to be consistent. – Matteo NNZ Jun 30 '21 at 14:19
  • 1
    @MatteoNNZ Inside your try-catch block, what exception is being raised? Because if there's no way a checked exception is being thrown inside the `try` block then the compiler can deduce that `throw ex` inside the `catch` block is valid. Even if you catch `Throwable` the compiler knows the only possible type is an _unchecked_ exception and therefore allows the throw. But if you were to throw a checked exception, or call a method capable of throwing a checked exception, inside the `try` block then the compilation would fail in this case (because `BiFunction` can't throw checked exceptions). – Slaw Jun 30 '21 at 14:55
  • @Slaw you're actually right, that's the reason (just tested). You can write it as an answer and I'll accept it. It's weird though that the compiler does the assumption (even if the assumption makes sense), I would have thought it was statically deducing the type from the catch block declaration (meaning Throwable) – Matteo NNZ Jun 30 '21 at 15:00