-
Notifications
You must be signed in to change notification settings - Fork 3
Java Exceptions Gotcha
The Java Programming Language features static signature checking that includes the exceptional (error) conditions that can arrise as a result of calling a method. However, many developers tend to engage in some somewhat unsafe practices when it comes to exceptions. This little essay might just help you avoid those little potholes and pitfalls.
Exceptions in Java are just "throwable" objects, and there are two kinds:
- Checked exceptions
- Unchecked exceptions
Checked exceptions are exceptional (that is, not normal) conditions in a program, but usually recoverable conditions, such as:
- File not found
- Unknown host name
They're often the result of user interaction:
- File not found? The filename was mistyped.
- Unknown host name? DNS hasn't updated with the new host name.
In Java, checked exceptions must be handled. There are two ways to handle a checked exception:
- By catching the exception and taking some kind of recovery action (in the
catch
part of atry
statement) - Or by declaring that your method potentially throws the class of the checked exception ("passing the buck")
The compiler enforces the handling of checked exceptions by generating compile time errors. In Java, you always know what to expect when you call a method, which is kind of nice ๐
Unchecked exceptions are also exceptionalโnot normalโconditions during the execution of a program. But they usually indicate a fault in the program or its environment. Some examples are:
- Division by zero (fault in the program)
- Out of memory (fault in the environment)
How do you handle unchecked exceptions? You don't have to!
Unchecked exceptions neither have to be caught nor declared. A correct program generally won't raised unchecked exceptions.
Having to declare or handle unchecked exceptions would be incredibly onerous:
- Every object deference can throw
NullPointerException
,IllegalAccessError
, amongst others. - Every object construction can throw
OutOfMemoryError
,NoClassDefFoundError
, and more. - Every calculation can throw
ArithemticException
. - Every statement can throw
VirtualMachineError
,InternalError
, and still more.
Unchecked exceptions indicate bugs in the program:
- Array index out of bounds is a bug!
- Fix the bugโthe program is now correct.
- Division by zero is undefined!
- Fix the arithmeticโthe program is now correct.
- Running out of memory is a problem!
- Fix the object allocation loop.
- Buy more memory.
- The program is now correct.
There are two kinds of unchecked exceptions:
-
Runtime exceptions
- These indicate faults in the program's logic, such as
- An illegal argument passed to a method
- A string index out of bounds
- And so forth
- A correct program should not need to handle these cases
- But there are "exceptions" ๐
- For example, division by zero when a user types a zero in an input box is a condition you can do something about
- But there are "exceptions" ๐
- These indicate faults in the program's logic, such as
-
Errors
- These indicate faults in the program's environment
- Out of memory
- Stack overflow
- Class circularity
- Internal error in the Java Virtual Machine
-
A "reasonable" application shouldn't catch these errors!
- It should be terminated instead.
- These indicate faults in the program's environment
Given the above, the logical class hierarchy for Java exceptions is:
โผ Throwable (anything that can be thrown)
โถ Checked exceptions (which must be handled)
โผ Unchecked exceptions (which don't have to be handled)
โถ Runtime exceptions (faults in the program)
โถ Errors (faults in the environment)
But in the actual class hierarchy in the Java language, it's different:
โผ class Throwable (checked)
โถ class Error (unchecked)
โผ class Exception (checked)
โถ class RuntimeException (unchecked)
In other words, all Throwable
objects are checked, except for objects subclassed from Error
; and anything derived from Exception
is checked, except for objects subclassed from RuntimeException
๐คฏ
before();
try {
something();
} catch (Exception ex) {
recover();
}
after();
๐ฎ This is bad, because it catches RuntimeException
s!
Remember: generally correct programs should not raise runtime exceptions. But because many people catch Exception
, they also end up catching RuntimeException
:
- This is like continuing after an assertion fails in C
- Or writing data at the end of a null pointer
- Or reading past the end of a file
- Or using the result of division by zero
Incorrect programs should instead be terminated (with extreme prejudice).
Instead of catching Exception
, always try to catch a more specific exception relevant to the program. But when that can't be done, let RuntimeException
s pass through and then catch the more general Exception
. Here's the above example, rewritten:
before();
try {
something();
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
recover();
}
after();
This works because even though the Exception
has a wider scope than RuntimeException
, Java processes the catch
clauses in listed order. Of course, you should strive to catch more specific exceptions germane to your code.
If you're just developing little toy examples and the like, you might think there's no need to go through the extra handling of catching RuntimeException
s to re-throw and then handling the more general Exception
.
True! But in that case, don't bother with Exception
โjust use Throwable
!
class Throwaway {
public void somethingElse() {
try {
something();
} catch (Throwable ex) { // ๐ Throwable!
System.err.println("It foobar'd");
}
}
public static void main(String[] argv) throws Throwable { // ๐ Throwable!
doSomethingEvenMoreDrastic();
}
But beware: one developer's trash is another developer's treasure. Some form of that throwaway code might make it into production ๐
Copyright ยฉ 2021-2024 California Institute of Technology.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.