Abstract: In this article, we look at exception handling in Java. We start with the history of exceptions, looking back at the precursor of Java, a language called Oak. We see reasons why Thread.stop() should not be used and discover the mystery of the RuntimeException name. We then look at some best practices that you can use for your coding, followed by some worst practices, in the form of exception anti-patterns.
Welcome to the 162nd issue of The Java(tm) Specialists' Newsletter, sent to you from the delightful Island of Crete. We went for a drive to the south of the island yesterday and saw the most amazing sights. Unfortunately I dropped my camera on Sunday evening, so you will have to come and see the sights personally - no pictures to tempt you :-) In two weeks time, I am speaking at Jazoon 2008 in Switzerland, so please let me know if you will be there.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
Java was originally invented for embedded systems, such as you would find in a video game console, a handheld GPS or a cellular telephone. Faults in embedded systems can lead to serious problems, even resulting in death. In order to deal with faults properly, Java included a standard mechanism for notifying threads of problems that occurred, in the form of exceptions.
To understand some of the architectural choices in Java, it is useful to take a step back and look at the precursor to Java, a language called Oak. The manual for Oak gives us hints as to why RuntimeException has such a strange name.
During the last 10 years, I have taught advanced Java courses to thousands of professional programmers and have communicated with tens of thousands through conferences and my Java Specialists' Newsletter. Up to now, not a single one has been able to correctly answer the simple question: "What does the name RuntimeException mean?"
Almost without fail, the answer I'm given is: "It is an exception thrown at runtime."
However, aren't all exceptions thrown at runtime?
The answer can be found in the Oak manual, the precursor to Java. Here we read: "Some exceptions are thrown by the Oak runtime system." And in another paragraph: "If the object being cast to a subclass is not an instance of the subclass (or one of its subclasses), the runtime system throws an InvalidClassCastException." Thus the name "RuntimeException" originally meant that this was an exception thrown by the Runtime to protect itself against damage.
The name "runtime" can also be seen in the class java.lang.Runtime, which represents the Java Virtual Machine, allowing us to find out information about memory usage, manually invoke the garbage collector and launch external processes.
In Oak, all exceptions were unchecked by the compiler. The idea with checked exceptions was only introduced in Java.
My personal opinion is that checked exceptions are not as useful as we would hope. For example, the class java.io.IOException has 74 subclasses in Java 6. So whenever you catch IOException, it could have been any one of 74 different error conditions. None of our code would cope with all of them every time we catch IOException.
Another "feature" in Oak was the asynchronous exception. This allowed a thread to cause an exception in another thread, asynchronously. Thus in the middle of its work, a thread would suddently experience an exception. In Oak, you could "protect" code that was not safe to interrupt with such an asynchronous exception. In the margin, we read the comment: "The default will probably be changed to not allow asynchronous exceptions except in explicitly unprotected sections of code." When Sun moved from Oak to Java, they kept asynchronous exceptions, but left out the ability to protect regions of our code. Thus the default became that everything was unprotected.
You have not heard of this asynchronous exception mechanism in Java? Let me illustrate. Consider the following code:
import java.sql.SQLException; public class AsynchronousException { public static void main(String[] args) { Thread t = new Thread() { public void run() { try { while (true) { System.out.println("Running"); } } catch (SQLException ex) { // compiler error System.err.println("We experienced a SQL Exception!"); } } }; t.start(); } }
The reason this does not compile is because SQLException is a
checked exception, meaning that the compiler
checks that it could occur in this code. Since
while(true);
does not throw any checked
SQLException, the compiler recognizes the catch clause as
dead code. Whilst this compile check is nice, it is not
entirely correct, due to asynchronous exceptions. Here is how
we could rewrite the try block:
try { while (true) { System.out.println("Running"); } } catch (Exception ex) { try { throw ex; } catch (SQLException ex2) { System.err.println("We experienced a SQL Exception!"); } catch (Exception ex2) { throw new RuntimeException(ex2); } }
We can now prove that the SQLException can happen by causing an asynchronous exception:
import java.sql.SQLException; public class AsynchronousException { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { public void run() { try { while (true) { System.out.println("Running"); } } catch (Exception ex) { try { throw ex; } catch (SQLException ex2) { System.out.println("We experienced a SQL Exception!"); } catch (Exception ex2) { throw new RuntimeException(ex2); } } } }; t.start(); Thread.sleep(100); // asynchronous SQLException t.stop(new SQLException("Database is down!")); Thread.sleep(100); } }
We can see some interesting effects if we run this program. Our output can be the following:
Running Running Running Running RunningWe experienced a SQL Exception!
The reason for this we can find inside the
System.out.println(String)
method:
public void println(String x) { synchronized (this) { print(x); newLine(); } }
Since Java does not have a protect keyword, the
asynchronous exception can literally happen anywhere
in your program, even inside atomic operations such as
println()
. The
synchronized(this)
is supposed to
protect multiple threads from calling the
println()
method at the same time. Since it
synchronizes on "this", it is possible for
System.err
and System.out
output to be interleaved, since they would be synchronizing
on different monitors.
On my machine, I sometimes saw this output:
Running Running Running Running RunningRunningWe experienced a SQL Exception!
I am not sure why this is, but it is not of great importance to know exactly why this happens. As you have seen asynchronous exceptions are a dangerous toy and should be avoided. They break class invariants with no way to protect yourself against them. With the interrupt() mechanism, at least we can allow it to affect us in well-defined places. Asynchronous exceptions have been deprecated for a reason, so we advise against using them.
Beginners in Java are often unsure of how to use exceptions. Common mistakes in code are blank catch clauses, a blanket use of unchecked exceptions and the abuse of exceptions for flow control. In this section, I will look at six "best practices" for using exceptions in your Java code.
When I first started using Java, I wrote my own custom exceptions for everything. I would have a CallDroppedException and an InsufficientFundsException. I used exceptions when a simple return value would have been preferable. In my latest Java project, I managed to create not a single new exception class, but to rather reuse what was there already.
Here are some useful exceptions that you could reuse, instead of creating your own:
IllegalStateException UnsupportedOperationException IllegalArgumentException NoSuchElementException NullPointerException
You obviously need to ensure that the exception you are throwing is representative of your error condition. Don't throw an UnsupportedException when the real error condition is that the database has shut down.
There are occasions where we break our first best practice. For example, if we want to have a more embracing exception that can signify that any one of several problems has occurred.
When we do need to write our own exceptions, we should follow the naming convention of always ending all our classes with "Exception". Our fields should be final, which is a good rule for most classes to make concurrency easier.
Our own exception classes should contain more information than just a simple String. For example, if you want to define an exception for stating that a value is out of range, you could define an exception class that not only contains the incorrect value, but also the range that would have been good.
For example, consider the following class:
public class OutOfRangeException extends IllegalArgumentException { private final long value, min, max; public OutOfRangeException(long value, long min, long max) { super("Value " + value + " out of range " + "[" + min + ".." + max + "]"); this.value = value; this.min = min; this.max = max; } public long getValue() { return value; } public long getMin() { return min; } public long getMax() { return max; } }
We could use that to define a person class that only allows an age of between zero and 150. For example:
public class Person { public static final int MIN_AGE = 0; public static final int MAX_AGE = 150; private final int age; private final String name; public Person(int age, String name) { this.age = age; this.name = name; if (this.age < MIN_AGE || this.age > MAX_AGE) { throw new OutOfRangeException(this.age, MIN_AGE, MAX_AGE); } } public String toString() { return "Person " + name + " is " + age + " years old"; } }
Exceptions should be thrown as early as possible. As soon as you detect the error condition, the exception needs to be generated. If you wait for too long, it becomes more difficult to analyze the problem.
Let's take as a counter-example the
ConcurrentModificationException
. A thread is
happily iterating through a collection, when it encounters
this exception. The thread that was iterating did not cause
the exception; it was caused by another thread modifying the
collection concurrently. In order to fix the problem, we need
to consider all the places in our code that are modifying the
collection. It is usually easier to change the collection to
be threadsafe, such as
CopyOnWriteArrayList
,
rather than to try to fix the actual problem. Another
alternative is to synchronize around the entire iteration,
but that comes at a great cost to concurrency.
Here we see two threads, one is reading from a collection, the other is modifying it. The reader thread dies with a ConcurrentModificationException, whereas the writer happily carries on:
import java.util.*; public class ConcurrentExceptionTest { private static volatile boolean running = true; public static void main(String[] args) throws InterruptedException { final Collection shared = new ArrayList(); Thread reader = new Thread("Reader") { public void run() { while (running) { // ConcurrentModificationException happens here for (Object o : shared) { } } } }; reader.start(); Thread writer = new Thread("Writer") { public void run() { while (running) { // the thread modifying the collection does // not see any exception shared.add("Hello"); shared.remove("Hello"); } } }; writer.start(); Thread.sleep(2000); System.out.println("reader alive? " + reader.isAlive()); System.out.println("writer alive? " + writer.isAlive()); running = false; } }
This particular article is about exceptions, rather than thread safety. However, here is one (of many) solutions that fixes the problem without reducing concurrency on the read:
We defined a shared ReadWriteLock called lock. The reading thread then changes to:
while (running) { lock.readLock().lock(); try { for (Object o : shared) { } } finally { lock.readLock().unlock(); } }
The writing thread changes to:
while (running) { lock.writeLock().lock(); try { shared.add("Hello"); shared.remove("Hello"); } finally { lock.writeLock().unlock(); } }
Similarly, exceptions should be caught in those contexts where some corrective action can be taken. Otherwise, pass them up the hierarchy until you have enough information so that you can effectively deal with them. Thus we can say "throw exceptions early" and "catch exceptions late". We need to defined our interfaces carefully to allow future users to throw exceptions from methods.
Methods and constructors that might throw exceptions should be clearly documented, including hints for the user as to what exceptions can be thrown under given circumstances. This makes it easier for clients to deal with the exceptions correctly.
I usually include both unchecked and checked exception comments. The unchecked exceptions are actually more important to document, since you cannot easily figure out what might be thrown by looking at the method signature.
For example, here is my Person constructor with JavaDoc comments:
/** * Constructs a person with the given age and name. * * @param age The age must fit into a range, specified by * MIN_AGE and MAX_AGE * @param name The name should not be null, nor an empty * string * @throws OutOfRangeException if the age is out of range. * The age may not be less * than the constant MIN_AGE * and may not be more than * the constant MAX_AGE. * @throws IllegalArgumentException if the name is null or * empty. * @see #MIN_AGE, #MAX_AGE */ public Person(int age, String name) { this.age = age; this.name = name; if (this.age < MIN_AGE || this.age > MAX_AGE) { throw new OutOfRangeException(this.age, MIN_AGE, MAX_AGE); } if (this.name == null || this.name.equals("")) { throw new IllegalArgumentException( "name parameter should not be null nor empty"); } }
When writing code that may throw exceptions, it is a good idea to also unit test them. The unit tests should be written both for the producer and the consumer of the exception. For example, for the Person class, we could write the following unit test:
import junit.framework.TestCase; public class PersonTest extends TestCase { public void testExceptions() { new Person(36, "Heinz"); new Person(Person.MIN_AGE, "Heinz"); new Person(Person.MAX_AGE, "Heinz"); try { new Person(Person.MIN_AGE - 1, "Heinz"); fail("Allowed setting of out of range age"); } catch (OutOfRangeException success) { } try { new Person(Person.MAX_AGE + 1, "Heinz"); fail("Allowed setting of out of range age"); } catch (OutOfRangeException success) { } try { new Person(Integer.MAX_VALUE, "Heinz"); fail("Allowed setting of out of range age"); } catch (OutOfRangeException success) { } try { new Person(Integer.MIN_VALUE, "Heinz"); fail("Allowed setting of out of range age"); } catch (OutOfRangeException success) { } } }
In JUnit 4, it is a bit easier to test whether exceptions occur in our code. Here is the same unit test, but this time with JUnit 4 (thanks to Al Scherer for pointing this out):
import org.junit.Test; public class PersonTestJUnit4 { @Test public void correctAges() { new Person(36, "Heinz"); new Person(Person.MIN_AGE, "Heinz"); new Person(Person.MAX_AGE, "Heinz"); } @Test(expected = OutOfRangeException.class) public void tooYoung() { new Person(Person.MIN_AGE - 1, "Heinz"); } @Test(expected = OutOfRangeException.class) public void tooOld() { new Person(Person.MAX_AGE + 1, "Heinz"); } @Test(expected = OutOfRangeException.class) public void muchTooOld() { new Person(Integer.MAX_VALUE, "Heinz"); } @Test(expected = OutOfRangeException.class) public void muchTooYoung() { new Person(Integer.MIN_VALUE, "Heinz"); } }
It is not always easy to test the code that needs to catch the exceptions, since the framework might not cause them at the appropriate time. For example, how do you simulate a FileNotFoundException? Would you let your unit test remove or rename the file? That might work, but perhaps this might be a file that is critical to your system, then removing it would not be an option. A better approach, though not always possible, is to replace the real objects with mock objects.
Assertions were introduced in Java 1.4 as an alternative to plain exception handling. However, instead of adding a completely new mechanism, they patched it onto the current exception mechanism. This has several implications for how we can use them in our code.
When an assertion fails, an AssertionError is thrown. Since this is a subclass of Error, it will blast through our standard exception handling code and usually kill our thread. Remember that Error is an unchecked exception.
The failed assertion would not cause the system to exit completely, but just the thread that caused it. You can actually catch AssertionError and ignore the error, but of course there is no good reason for doing this.
public class FailedAssertion { public static void main(String[] args) { try { assert 4 == 5 : "4 is not 5, we thought it was"; } catch (AssertionError ae) { System.out.println("We are ignoring this: " + ae); } System.out.println("The main thread happily carries on ..."); } }
When we run this with -enableassertions (or -ea for short), we see the following output:
We are ignoring this: AssertionError: 4 is not 5, we thought it was The main thread happily carries on ...
Assertions in C need to be set at compile time. In Java they can be enabled and disabled on a per-class basis at startup. This in effect increases the possible state space of our program exponentially. Every time we encounter an assertion, the program could go in two directions: either the assertion is evaluated, which might result in an AssertionError or it is not evaluated, in which case the thread simply carries on.
Every time a class contains an assertion, our state space doubles. Thus, if we have 20 classes with assertions, we would have 2^20 = 1 million times more states. To completely test the system, we would need to enable and disable the assertion status of each of the classes in the system.
In practice, we usually enable either all or none of the assertions. We would thus only double the state space, so we need to qualify our system with assertions on and then redo the qualification with them turned off.
In the book Pragmatic Programmer, the authors make a strong point for keeping assertions turned on all the time, even in production. Since they could be turned off at runtime, we need to assume that they may not be evaluated at all.
Instead of using the assert
mechanism, I sometimes throw the AssertionError directly,
thus removing any confusion:
import java.util.*; import java.util.concurrent.*; public class FailedAssertion2 { public static <T> List<T> makeRandomList() { switch ((int) (Math.random() * 3)) { case 0: return new ArrayList<T>(); case 1: return new LinkedList<T>(); case 2: return new CopyOnWriteArrayList<T>(); default: throw new AssertionError("Impossible case"); } } public static void main(String[] args) { for (int i = 0; i < 10; i++) { System.out.println( makeRandomList().getClass().getSimpleName()); } } }
No discussion on exceptions would be complete without considering anti-patterns that have been adopted by programmers.
We should never cause an exception that is otherwise
preventable. I have seen code where instead of checking
bounds, it is assumed that the data will be correct and
then RuntimeException
s are caught:
Here is an example of bad code (please don't code like this):
public class Antipattern1 { public static void main(String[] args) { try { int i = 0; while (true) { System.out.println(args[i++]); } } catch (ArrayIndexOutOfBoundsException e) { // we are done } } }
The second anti-pattern started appearing as a misguided
attempt to optimize Java code. The argument was that the
try-catch
statement costs less than
the if
when the condition is mostly
true anyway. The code then became completely unreadable, due
to deep levels of try-catch. Again, this is an anti-pattern,
so I do not recommended that you implement your code this
way:
public class Antipattern2 { private final int val1; private final String val2; private final long val3; public Antipattern2(int val1, String val2, long val3) { this.val1 = val1; this.val2 = val2; this.val3 = val3; } public boolean equals(Object o) { if (this == o) return true; try { Antipattern2 that = (Antipattern2) o; return val1 == that.val1 && val3 == that.val3 && (val2 == that.val2 || val2.equals(that.val2)); } catch(ClassCastException ex) { return false; } catch(NullPointerException ex) { return false; } } public int hashCode() { int result; result = val1; result = 31 * result + val2.hashCode(); result = 31 * result + (int) (val3 ^ (val3 >>> 32)); return result; } } }
We started off this article with some historical discussion that explained where the name RuntimeException comes from. We then looked at some best practices for using exceptions correctly, followed by a brief look at assertions. Lastly, we ended off with two anti-patterns for how programmers abuse exception handling.
Kind regards
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.