The justification for this is actually contained within the Java Language Specification in section 11.2. Here is the relevant exerpt:
11.2. Compile-Time Checking of Exceptions
The unchecked exception classes (§11.1.1) are exempted from compile-time checking.
Of the unchecked exception classes, error classes are exempted because they can occur at many points in the program and recovery from them is difficult or impossible. A program declaring such exceptions would be cluttered, pointlessly. Sophisticated programs may yet wish to catch and attempt to recover from some of these conditions.
Of the unchecked exception classes, run-time exception classes are exempted because, in the judgment of the designers of the Java programming language, having to declare such exceptions would not aid significantly in establishing the correctness of programs. Many of the operations and constructs of the Java programming language can result in exceptions at run time. The information available to a Java compiler, and the level of analysis a compiler performs, are usually not sufficient to establish that such run-time exceptions cannot occur, even though this may be obvious to the programmer. Requiring such exception classes to be declared would simply be an irritation to programmers.
For example, certain code might implement a circular data structure that, by construction, can never involve null references; the programmer can then be certain that a NullPointerException cannot occur, but it would be difficult for a Java compiler to prove it. The theorem-proving technology that is needed to establish such global properties of data structures is beyond the scope of this specification.
As developers we can, at least to a certain extent, control whether or not an unchecked exception can be thrown by the code that we write. For instance, we can avoid encountering a NullPointerException
by either checking for null
before we attempt the operation which would result in the exception, or we can write our code such that that variable can never be null
in the first place. Similarly, we can avoid a NumberFormatException
by implementing a sanity check to ensure that our input definitely is a number before we try and parse it as one, etc...
If you've been sensible writing your code, you should rarely (if ever) encounter a RuntimeException
(or subclass thereof), so requiring you to put try-catch
blocks around every piece of code that might throw one would result in a horiffic mess of hundreds of small try-catch
blocks in even simple classes, or one giant catch-everything block, neither of which are particularly desirable, and add lots of largely unnecessary bloat to your code.
Being forced to catch all unchecked exceptions would make even 'simple' operations like using System.out.println()
much more verbose.
Let's see what we would have to write to print a blank line to the console if we were forced to catch all unchecked exceptions (Note: this is something of a worst-case scenario, where exceptions are simply propagated instead of handled within the API):
System.out.println();
// ^ Theoretically this could throw a NullPointerException
So, we'd have to account for that:
try {
System.out.println();
} catch (NullPointerException e) {
// In practice this won't happen, but we're forced to deal with
// it all the same...
}
We're not done yet. We need to see what out
is, and how println()
works to see if there's anything else we need to handle. out
is actually a PrintStream
object, so what can we tell about its println()
method?
Here's println()
:
public void println() {
newLine();
}
This means we now need to see what newLine()
does...
private void newLine() {
try {
synchronized (this) {
ensureOpen();
textOut.newLine();
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush)
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
Lots more sources of NullPointerException
s in here, but we've already caught that. interrupt()
can (eventually) throw a SecurityException
, and can also cause an InterruptedException
, so we'll have to handle those two as well.
try {
System.out.println();
} catch (NullPointerException e) {
} catch (SecurityException e) {
} catch (InterruptedException e) {
}
textOut.newLine()
eventually ends up in Writer#write(String, int, int)
, which deals with a char[]
, so we immediately have a source of an ArrayIndexOutOfBoundsException
as well. It also calls String#getChars(int, int, char[], int)
, which can itself throw a StringIndexOutOfBoundsException
. Gotta handle that too...
try {
System.out.println();
} catch (NullPointerException e) {
} catch (SecurityException e) {
} catch (InterruptedException e) {
} catch (ArrayIndexOutOfBoundsException e) {
} catch (StringIndexOutOfBoundsException e) {
}
It also calls BufferedWriter#write(char[], int, int)
, which can throw an IndexOutOfBoundsException
...
try {
System.out.println();
} catch (NullPointerException e) {
} catch (SecurityException e) {
} catch (InterruptedException e) {
} catch (ArrayIndexOutOfBoundsException e) {
} catch (StringIndexOutOfBoundsException e) {
} catch (IndexOutOfBoundsException e) {
}
We're now a six separate Runtime Exceptions from this one method call, and this doesn't include all of the potential exceptions that can pop out of the various native
method calls along the way (of which there are several), and doesn't include any subclass of Error
either.
If we genuinely were forced to catch all exceptions, Java would be the worse for it.