Introduction
In a previous article on Using exceptions correctly, exception propagation was explained: use runtime exceptions for situations in which the code is incorrect, and use checked exceptions for situations for which there is a valid recovery path internal to the application.
This article deals with two other things you will have to do with exceptions: catching them and implementing them. And in order to do both, you have to know about exception classes themselves…
Java’s Exception Classes
At the core of all exceptions is a class called Throwable
. It is the superclass for both Exception
 and Error
. (OutOfMemoryError
is an example of an Error
.)
The truth of the matter is you should probably never encounter code which directly handles (or throws!) a Throwable
which is not also an Exception
, but you should know that it exists. You never know: some inexperienced programmer might add it to his or her exception handling, and then you’d have to clean up their code.
The Exception
class itself is the base class for all checked exceptions. When you want to create custom exceptions, you will probably want to extend this one (or a more specialized version: IOException
, for instance).
The RuntimeException
class Is the base class for all unchecked exceptions. If you browse the JDK javadoc, you will notice that all such niceties as the infamous NullPointerException
(NPE for short), but also IllegalArgumentException
, IndexOutOfBoundsException
, etc., are all subclasses of RuntimeException
.
But there’s a catch (pun intended): RuntimeException is also a subclass of Exception. This has an often overlooked consequence on catch statements, see below.
Catching, and the importance of exception inheritance
Since exceptions are Java classes like any other, exception classes which you can catch may inherit from other exception classes. This has an influence on how you catch them. For starters…
Catch more specific exceptions first
Let us take an example of a method doing filesystem operations using the java.nio.file
API. Such operations can fail at two levels:
- Filesystem-level errors; the exception defined by the JDK for such errors is
FileSystemException
.
- I/O errors; in this case you will probably get an
IOException
instead.
From the javadoc links above, you may have noticed that FileSystemException
inherits IOException
. Provided you want to treat both exceptions differently, you must catch FileSystemException
first:
try {
something();
} catch (FileSystemException e) {
// deal with filesystem level errors
} catch (IOException e) {
// deal with I/O errors
}
As an added benefit, you get to access all methods defined in FileSystemException
(getFile()
, etc) which IOException
does not define, allowing you to (for instance) construct much more detailed error messages.
If, instead, your code was:
try {
something();
} catch (IOException e) {
// deal with it
} catch (FileSystemException e) {
// NEVER REACHED!
}
As explained, you will never get to deal with FileSystemException
s separately – the more general IOException
will match and its exception-handling block called, instead.
Catching Exception is dangerous…
Remember what was said earlier? RuntimeException
inherits Exception
. As such, if you simply catch Exception, you also get to catch all unchecked exceptions!
You don’t want to do that, of course… But just in case, if you want to (or, preferrably, need to) have a “one size fits all” catch block, here is an idiom you can use which will “correctly” propagate all unchecked exceptions (reminder: since they are unchecked, you needn’t declare that your method throws them):
try {
// some exception-heavy code throwing many exceptions
} catch (RuntimeException oops) {
// Unchecked exception: rethrow!
throw oops;
}
// deal with specific exceptions if possible...
// then:
catch (Exception e) {
// One size fits all
}
Well, that’s one idiom; a better way to deal with exception-heavy code is to simply reduce the amount of code in your try blocks so that the amount of exceptions you have to deal with is limited.
Alternatively, you can use multicatch.
“Multicatch” (since Java 7)
Editor’s Note: if you’re not on Java 7 by now, you should attempt to upgrade. Java 6 has been end-of-lifed, and it has a support lifecycle that’s not likely to be available for most users. Java 7’s been out for years, people. It’s time.
Again, in the situation where you have to deal with “exception-heavy” try blocks, you have another tool since Java 7 allowing you to treat a given set of exceptions with the same code. So, instead of, for instance, writing:
try {
// something
} catch (E1 e) {
// do x with e
} catch (E2 e) {
// do x with e
}
// etc
You can instead write:
try {
// something
} catch (E1 | E2 e) { // | E3 | ...
// do x with e
}
Implementing your own exceptions: some rules
Exceptions are regular Java classes. This means that there is more to exception classes than .getMessage()
, as we saw with FileSystemException
.
Therefore, if you feel the need to, do not hesitate to add methods to your custom exception classes.
However, try to avoid extending Exception
directly. The JDK defines plenty of exception classes which can serve as a useful basis for your own exception classes; for instance, if you want to relay an I/O error of your own, you will probably want to extend IOException
instead.
Double check whether the exception class you extend inherits RuntimeException… If it does, you have just created an unchecked exception class. Is this what you want?
Side note: implementing “throwing methods”…
When implementing a method which is declared to throw an exception class, the implementation can choose to throw a subclass of this exception. So, for instance, if you have an abstract method to implement declared as:
// Reminder: all declared methods in an interface are "public abstract" by default
void foo() throws SomeException;
… and you have a custom exception MyException
which extends SomeException
, then you can implement it as such:
// Note the declared exception...
@Override
void foo() throws MyException { /* whatever */ }
This is demonstrated by the JDK with AutoCloseable
and Closeable
: Closeable
extends AutoCloseable
. The close()
method of AutoCloseable
is declared to simply throw Exception
, whereas its override in Closeable
is declared to throw IOException
.
Final words…
Hopefully, this article and the quoted article in the introduction should have provided you with enough tools, knowledge and recipes so that you are confident when it comes to dealing with exceptions in Java — whether that be in your own code, or when using other people’s code.
Happy (and fruitful) coding…