35

In Java, methods that throw checked exceptions (Exception or its subtypes - IOException, InterruptedException, etc) must declare throws statement:

public abstract int read() throws IOException;

Methods that do not declare throws statement can't throw checked exceptions.

public int read() { // does not compile
    throw new IOException();
}
// Error: unreported exception java.io.IOException; must be caught or declared to be thrown

But catching checked exceptions in safe methods is still legal in java:

public void safeMethod() { System.out.println("I'm safe"); }

public void test() { // method guarantees not to throw checked exceptions
    try {
        safeMethod();
    } catch (Exception e) { // catching checked exception java.lang.Exception
        throw e; // so I can throw... a checked Exception?
    }
}

Actually, no. It's a bit funny: compiler knows that e is not a checked exception and allows to rethrow it. Things are even a bit ridiculous, this code does not compile:

public void test() { // guarantees not to throw checked exceptions
    try {
        safeMethod();
    } catch (Exception e) {        
        throw (Exception) e; // seriously?
    }
}
// Error: unreported exception java.lang.Exception; must be caught or declared to be thrown

The first snippet was a motivation for a question.

Compiler knows that checked exceptions can't be thrown inside a safe method - so maybe it should allow to catch only unchecked exceptions?


Returning to the main question - are there any reasons to implement catching checked exceptions in this way? Is it just a flaw in the design or am I missing some important factors - maybe backward incompatibilities? What could potentially go wrong if only RuntimeException were allowed to be catched in this scenario? Examples are greatly appreciated.

AdamSkywalker
  • 11,408
  • 3
  • 38
  • 76
  • 1
    Regarding the main question: It is not a flaw in the design per se, RuntimeExceptions are subclasses of Exception, therefore catching Exception also includes unchecked exceptions. That being said, there is no reason do to it like this, it might even confuse people reading the code because they might think safeMethod() might throw an Exception. I think just catching RuntimeException here is a better choice. – MartinS Feb 03 '16 at 18:03
  • 1
    [Relevant section of the JLS on the throw statement.](https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.18) – Makoto Feb 03 '16 at 18:06
  • You can even catch `Throwable` too. What's wrong with catching a more general type? – ZhongYu Feb 03 '16 at 18:22
  • @bayou.io it's the same as why to use generics if we could use raw types? And the answer is stronger compiler checks. Compiler can tell that Exception can't be thrown but only RuntimeException, so why catch it? – AdamSkywalker Feb 03 '16 at 18:33
  • 1
    @AdamSkywalker We know the many problems raw types cause. What problem does catching a wider type cause? This is why your metaphor breaks down. By your argument `final Object ob = "foo";` should result in a compiler error too, because we know at compile time that the runtime type of `ob` will be `String`. – biziclop Feb 03 '16 at 18:37
  • @biziclop I agree with this. – AdamSkywalker Feb 03 '16 at 19:09
  • 1
    Since `safeMethod()` is, well, safe, that means that the caught `Exception e` **must be** a `RuntimeException`. If it is left as-is (as in the first snippet) all is well. But when you explicitly cast to `Exception` in the second snippet you make the compiler forget what it knows and believe that it could be any `Exception`, which of course is not ok. – Erick G. Hagstrom Feb 03 '16 at 19:11

3 Answers3

19

Quoting the Java Language Specification, §11.2.3:

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.

I'm guessing that this rule originated long before Java 7, where multi-catches did not exist. Therefore, if you had a try block that could throw a multitude of exceptions, the easiest way to catch everything would be to catch a common superclass (in the worst case, Exception, or Throwable if you want to catch Errors as well).

Note that you may not catch an exception type that is completely unrelated to what is actually thrown - in your example, catching any subclass of Throwable that is not a RuntimeException will be an error:

try {
    System.out.println("hello");
} catch (IOException e) {  // compilation error
    e.printStackTrace();
}


Edit by OP: The main part of the answer is the fact that question examples work only for Exception class. Generally catching checked exceptions is not allowed in random places of the code. Sorry if I confused somebody using these examples.
Aasmund Eldhuset
  • 37,289
  • 4
  • 68
  • 81
  • 4
    Exactly, this rule existed long before try-multi-catch, probably since 1.0 (but certainly since 1.2). – biziclop Feb 03 '16 at 18:04
  • Catching `Exception` does not catch everything. Catching `Throwable` catches everything. – Tim Bender Feb 03 '16 at 18:09
  • You give an example with multiple exceptions. I'm okay with catching common parent - Exception if multiple exceptions are thrown and at least one of them is checked. But for all unchecked exceptions there is a common parent - RuntimeException. So I could still handle multiple unchecked exceptions with a single catch clause – AdamSkywalker Feb 03 '16 at 18:11
  • 1
    @AdamSkywalker The rule is simple: if exception `E` can be thrown, any superclass of `E` can be caught. Why that rule was historically necessary has been made clear in the answers. The trouble with what you're suggesting is that the rules would become far more confusing and complicated to solve a problem that doesn't even exist. – biziclop Feb 03 '16 at 18:36
  • 1
    AdamSkywalker: Yes, but language designers / compiler authors also need to keep things from getting more complicated than necessary. As @biziclop suggests, your proposed modification would solve a _very_ minor problem, yet complicate the rule and its implementation. – Aasmund Eldhuset Feb 03 '16 at 18:59
  • 1
    @TimBender: I wanted to focus on exceptions, but I agree that my original phrasing made it sound like exceptions are the only thing that can be thrown. I've edited my answer. – Aasmund Eldhuset Feb 03 '16 at 19:02
  • 1
    @AdamSkywalker: Also, since anything could throw an `Error`, catching `Throwable` must always be allowed, and so the rule would look even more complicated: "You can catch any subclass of, and up to and including the lowest common ancestor of, all the exception types that might be thrown, and in addition, you can catch `Throwable`." - vs. "You can catch any subclass or superclass of what might be thrown." – Aasmund Eldhuset Feb 04 '16 at 01:52
11

Java 7 introduced more inclusive exception type checking.

However, in Java SE 7, you can specify the exception types FirstException and SecondException in the throws clause in the rethrowException method declaration. The Java SE 7 compiler can determine that the exception thrown by the statement throw e must have come from the try block, and the only exceptions thrown by the try block can be FirstException and SecondException.

This passage is talking about a try block that specifically throws FirstException and SecondException; even though the catch block throws Exception, the method only needs to declare that it throws FirstException and SecondException, not Exception:

public void rethrowException(String exceptionName)
 throws FirstException, SecondException {
   try {
     // ...
   }
   catch (Exception e) {
     throw e;
   }
 }

This means that the compiler can detect that the only possible exception types thrown in test are Errors or RuntimeExceptions, neither of which need to be caught. When you throw e;, it can tell, even when the static type is Exception, that it doesn't need to be declared or re-caught.

But when you cast it to Exception, this bypasses that logic. Now the compiler treats it as an ordinary Exception which needs to be caught or declared.

The main reason for adding this logic to the compiler was to allow the programmer to specify only specific subtypes in the throws clause when rethrowing a general Exception catching those specific subtypes. However, in this case, it allows you to catch a general Exception and not have to declare any exception in a throws clause, because no specific types that can be thrown are checked exceptions.

rgettman
  • 176,041
  • 30
  • 275
  • 357
  • I understand why first code compiles and second does not. I think I should wrap the main question with bold font. – AdamSkywalker Feb 03 '16 at 17:54
  • The main question is in the title - why even allow to catch Exception if we know it cant be thrown – AdamSkywalker Feb 03 '16 at 17:55
  • I'm not convinced this fully explains what's going on, at a semantic level. Yes, it describes behavior, but I'm not sold that this sort of thing is the true answer. – Makoto Feb 03 '16 at 17:58
  • 4
    @AdamSkywalker It has always been possible to catch a superclass of all the possible exceptions that can be thrown, imagine a file manipulation method for example, which wraps code that can throw `FileNotFoundException` and `IIOException`. Before `try-multi-catch` the only way to deal with both was either to have two identical `catch` clauses, or catch a superclass of it, `IOException` for example. Or `Exception`. Both solutions are bad in a way, but the second one is slightly less bad. – biziclop Feb 03 '16 at 18:03
  • The type `Exception` can be thrown. There is plenty of bad code out there that has methods which simply declare `throws Exception`. Exception itself could be constructed and thrown without further sub-classing it. As could `Throwable`, the super-type of all things that can be thrown. – Tim Bender Feb 03 '16 at 18:07
  • @biziclop I wrote my thoughts about superclass catch under Aasmund answer – AdamSkywalker Feb 03 '16 at 18:12
  • 1
    @TimBender And there are a few fairly legitimate cases, like in reflection. – biziclop Feb 03 '16 at 18:32
7

The issue here is that checked/unchecked exception limitations affect what your code is allowed to throw, not what it's allowed to catch. While you can still catch any type of Exception, the only ones you're allowed to actually throw again are unchecked ones. (This is why casting your unchecked exception into a checked exception breaks your code.)

Catching an unchecked exception with Exception is valid, because unchecked exceptions (a.k.a. RuntimeExceptions) are a subclass of Exception, and it follows standard polymorphism rules; it doesn't turn the caught exception into an Exception, just as storing a String in an Object doesn't turn the String into an Object. Polymorphism means that a variable that can hold an Object can hold anything derived from Object (such as a String). Likewise, as Exception is the superclass of all exception types, a variable of type Exception can hold any class derived from Exception, without turning the object into an Exception. Consider this:

import java.lang.*;
// ...
public String iReturnAString() { return "Consider this!"; }
// ...
Object o = iReturnAString();

Despite the variable's type being Object, o still stores a String, does it not? Likewise, in your code:

try {
    safeMethod();
} catch (Exception e) { // catching checked exception
    throw e; // so I can throw... a checked Exception?
}

What this means is actually "catch anything compatible with class Exception (i.e. Exception and anything derived from it)." Similar logic is used in other languages, as well; for example, in C++, catching a std::exception will also catch std::runtime_error, std::logic_error, std::bad_alloc, any properly-defined user-created exceptions, and so on, because they all derive from std::exception.

tl;dr: You're not catching checked exceptions, you're catching any exceptions. The exception only becomes a checked exception if you cast it into a checked exception type.

  • 4
    @AdamSkywalker Please don't be flippant with people who only try to help you. The polymorphism argument is perfectly valid because at the heart of it `catch (Exception ex)` is pretty much like `Exception ex = ...`. – biziclop Feb 03 '16 at 18:40