Multithreaded Exception Handling in Java
Joe De Russo III and Peter Haggar
Multithreaded programming has been with us for many years and is considered to be a feature
that many robust applications utilize. Exception handling is another feature of many languages
considered to be necessary for proper and complete error handling. Each of these technologies
stands very well on their own. In fact, you cannot pick up a book about Java programming without
finding a chapter devoted to each of these topics. Many of these books do a good job defining
and describing how to use them properly in your programs. What is missing is the information on
how to use these two technologies together effectively. After all, how effective is writing a
multithreaded Java program if it is incapable of properly handling exceptions occurring on secondary
threads? We will present a solution for effectively dealing with this problem.
To solve this problem, we introduce two new classes and two new interfaces to be used when writing
multithreaded Java programs. These classes are small, easy to use and understand, and effectively
enable you to handle exceptions occurring on secondary threads.
Writing robust code implies many things. One of them is the proper and effective way in which
your program deals with error situations. The approach you take can vary from doing nothing to handling
any and all problems. The approach you choose is more than likely dictated by the type of application
you are writing. For example, if you are writing an application that must run uninterrupted for many
hours, days, or months at a time, you will need to effectively employ an error handling strategy that
will ensure your software can run uninterrupted when errors occur. Even if you are not writing such
a program, effectively dealing with errors is just good programming practice.
One of the main areas of importance for dealing with exceptions is what we call state management.
This involves ensuring that when an exception occurs, the state of the object the exception occurs
in remains valid such that if the code recovers from the exception, the object can be reliably used
again. Doing so in single-threaded applications is challenging enough without introducing multiple
threads of execution.
The core problem that must be dealt with is how to manage concurrent threads of execution when
one, or many, of those threads may terminate abnormally. We need a way to be notified of any errors
occurring on secondary threads, and a solution that enables us to terminate gracefully while
optionally preserving object state. Java is currently being considered as a platform for deploying
mission-critical applications. Multithreaded exception handling is a reality in this environment.
While much documentation exists on the virtues of using threads of execution and exceptions in Java,
there is little documentation on how to integrate the two. One such solution to these problems is
presented here. We designed our solution to be as generic as possible. Our design goals were to
provide a solution that:
• Does not tightly couple the objects and code running on secondary threads with the objects
and code that need to know if an exception occurs. For example, we do not implement a simple callback
interface.
• Requires a minimum amount of maintenance when the code is changed to throw additional
exceptions from the secondary threads.
• Minimizes the number of try/catch blocks.
• Works even if the code to be executed on secondary threads is not owned by the developer
calling it. This could occur if you are implementing a run() method that calls code you obtained
from a third party, and you want to catch any exception thrown from it.
• Works for all exceptions, both checked and unchecked, that may be thrown from within a
secondary thread.
Note: Throughout this article we will be using the notion of checked and unchecked exceptions.
Checked exceptions are generally related to conditions that are specific to an operation being
performed, such as trying to construct an invalid URL. The compiler requires that you take action
on all checked exceptions that may occur in your method in one of two ways: either by handling them
yourself with a try/catch block or by advertising to your callers that your method throws this
exception by listing them in that method’s throws clause. In contrast, unchecked exceptions could
occur anywhere in your program, such as an out-of-memory condition. Although it may be useful to be
aware of these problems in certain situations, the compiler does not require you to address unchecked
exceptions.
The solution identified in this article satisfies all of the afforementioned design goals and is
straightforward and generic enough to be used in production systems. All of the code presented here
was compiled and run using the Sun JDK 1.1.4 on the Windows NT 4.0 Operating System.
Multithreaded
Exception Handling
We present our solution to this problem in the following manner: Appendix A (posted at Java
Report Online) contains a complete multithreaded program that attempts to open separate files on
two threads. Listings 1-4 examine this code, point out the problems contained in it, and offer some
initial attempts at solving them. Listings 5-8 introduce two new classes and two new interfaces to
provide a solution to these problems. Appendix B (see Java Report Online) contains the program in
Appendix A modified with the classes and interfaces introduced in Listings 5-8 such that it correctly
deals with exceptions occurring on secondary threads. Listings 9-11 examine, more closely, Appendix B
and offer commentary on how it was developed to solve these problems.
The Initial Code
The code in Appendix A contains a user interface that has two buttons and two listboxes. The
listboxes are used to display the files and the buttons are used to fill the first listbox on the main
thread and start the secondary thread to attempt to fill the second listbox. The code uses a FileOpener
class to attempt to open the file on the secondary thread. The main thread will open the file and fill
the first listbox without any errors. The second listbox will not fill up due to the exceptions occurring
on the secondary thread.
Listing 1
contains some relevant code fragments from this program. Pressing the first button will result in an invalid
filename at //4, being sent to the FileOpener class causing the secondary thread to generate a checked
exception, FileNotFoundException at //1. Pressing the other button will result in a null pointer at //5,
being sent to the FileOpener class at making the secondary thread throw an unchecked, NullPointerException
at //2.
A key point to note is the primary thread must be able to determine the status of the secondary thread.
This can be difficult particularly when the secondary thread may terminate due to an exception. What if
you were writing a mission-critical application? How would you report failures in secondary threads to
the calling code? After all, the calling code may be able to recover from the problem and try again. At
a minimum, the calling code can inform the user that there is an unrecoverable problem and advise them
to take some appropriate action. The worst thing that can happen is the calling code will continue as
if the secondary thread completed successfully. This will result in errors occurring later, that will
be much more difficult to track down.
So, what do we do? You may notice at //3 we are catching the FileNotFoundException generated at //1.
Why not catch it and let it pass through our run() method? The answer to this requires some explanation.
Why Not Use The Traditional Try/Catch Technique?
Our first attempt at solving the multithreaded exception handling problem was to devise a solution
using traditional exception handling techniques. We simply placed a try/catch block around the start()
method. After all, start() instantiates the secondary thread and calls its run() method and the use of
try/catch is the natural way of dealing with exceptions. If the code in run() throws any exceptions we
should be able to catch them. Let’s see what happens if we try to solve our problem this way.
Listing 2
shows some of the code from
Listing 1
modified with this dubious idea.
Looking at this code we notice at //1 and //2 we are trying to catch exceptions thrown from the
secondary thread by attempting to catch exceptions thrown from the call to start(). Because this
code compiles cleanly, your next step may be to get the exception to propagate from the run() method
so it can be caught at //2.
Listing 3
shows our Runnable class modified with the changes you may make to accomplish this.
Instead of catching the FileNotFoundException in run() as we did in
Listing 1,
we have removed the try/catch block to let the caller of the run() method handle it. Because the
FileNotFoundException is a checked exception, we are required to advertise the fact that our
run() method throws this exception by specifying it in the method’s throws clause at //3.
On closer examination, Listings 2 and 3 are ill-fated for two reasons. First, the code in
Listing 3
will not even compile because you are not allowed to throw checked exceptions from the run() method.
The reason for this is because an override method can only throw exceptions of the same type of the
method being overridden or specializations of those types. In other words, because the run() method
of the Runnable class does not specify that it throws any checked exceptions, you cannot throw any
checked exceptions from your overridden version of run().
The second problem is at //1 and //2 in
Listing 2.
Even though this code compiles cleanly, it is doomed to fail. Remember that start() instantiates
a secondary thread and calls the run() method of the Runnable object asynchronously. Exceptions
signal back only to some point on the stack of the affected thread. Because start() creates a new
stack, the primary thread will never be notified of any exceptions that occur. What the code in
Listing 2
is actually doing is catching exceptions that occur when calling start(), not exceptions that
occur in the run() method, which is what you are trying to accomplish.
Making Try/Catch
Around start() "Work"
One way to attempt to solve these problems is by creating a special class that acts like a thread,
but also enables us to employ the try/catch model around the start() method for handling exceptions
occurring on secondary threads. To accomplish this, we introduce a new class, shown in
Listing 4,
which will extend java.lang.ThreadGroup. ThreadGroup contains a key method, uncaughtException(), which is
called on a thread when an exception occurs that is not handled, that is, caught. When the uncaughtException()
method ends, the thread is terminated.
To make our try/catch around start() scheme work, one may attempt to extend the ThreadGroup class at //1,
provide a custom version of the start() method at //2, and override the uncaughtException() method at //4.
We called this new class a ThreadWrapper. The steps outlined seem necessary so we can intercept the
exception occurring on the secondary thread and then have our custom start() method throw it. This will
enable the try/catch code from
Listing 2
to actually catch the exception that occurred in the secondary thread.
There is one major drawback to the code in
Listing 4.
This is the use of the join() method at //3. The call to join() is needed in order to support the try/catch
technique around the call to start(). The big problem with this is the use of join() effectively makes
your code single-threaded again. The join() method will block the main thread until the secondary thread
has finished. This completely defeats the purpose of multithreaded programming but was necessary to make
the try/catch around start() technique work.
There does not exist a way in Java to use try/catch around your start() method to catch the exceptions
thrown from a secondary thread and remain multithreaded. There does, however, exist an elegant way to
handle exceptions thrown from secondary threads, that is derived from some of what we have seen so far.
A new paradigm for dealing with exceptions is used which builds on the ideas of the JDK 1.1 event model.
Listening For Exceptions
As we have seen, the try/catch model does not extend well into a multithreaded scenario. We need
a generic mechanism that allows a main thread to have an arbitrary number of concurrently running secondary
threads, each with the ability to communicate exceptions to objects that can deal with these exceptions.
This mechanism must allow us to catch and propagate both checked and unchecked exceptions. Unchecked
exceptions are relatively straightforward, as the compiler does not force us to either catch these or
explicitly declare that we are passing them on to our clients. Checked exceptions are more challenging.
Although we are required to handle these programmatically, it still may be desirable to pass this
information on for possible recovery (or at least soft shutdown).
The Java 1.1 Event Model introduces the notion of listener classes that can register with GUI components
to be notified when events occur on those components. If we abstractly consider exceptions to be events,
we can extend this paradigm to address our multithreaded exception handling issues. When an exception
occurs in a secondary thread, notification could be sent to one or more other objects that are registered
as listeners on that thread. Next, we discuss our approach and introduce the classes used to achieve our
goal. For a complete solution, we have three fundamental requirements:
• We need a type of thread that is capable of intercepting ALL of its exceptions, both checked and
unchecked. This will allow us to consistently and comprehensively alert listeners of exceptions.
• We need a conduit between the secondary thread and one or more listener objects through which we
can pass exception information.
• We need a mechanism that allows one or more listener objects to communicate back to the Runnable
object on the thread where the exception occurred. This could be used to attempt recovery, preserve object
state, or to perform some cleanup for soft termination.
The SmartThread Class...A Better Thread Than Thread
To address the first fundamental requirement, we introduce the SmartThread class in
Listing 5.
Like the ThreadWrapper class previously discussed, our SmartThread class extends ThreadGroup at //1. By
overriding the ThreadGroup’s uncaughtException() method at //6, the SmartThread is able to intercept all
unhandled, unchecked, and checked exceptions. The SmartThread can then notify all registered listener
objects of the exception.
In order for SmartThread to notify listener objects of exceptions, it needs to provide an interface
for listener objects to register interest. This is done via addThreadExceptionListener() at //4. This
method will support multiple listener objects because it is implemented with a Java Vector. When the
uncaughtException() method is called, it will iterate over the Vector of listeners at //7 calling
their exceptionOccurred() methods. (The astute reader may observe that addThreadExceptionListener()
could also be implemented with a Multicaster. We have chosen to use a Vector here for simplicity of
illustration. However, the final working program implements a Multicaster.)
Each registered listener’s exceptionOccured() method will be called from //8 with the Runnable
object the exception occurred in, the thread the exception occurred on, and the exception thrown
that was not handled. It is important to pass the thread because one listener could be listening
to multiple threads.
Note that SmartThread is not truly a thread because it extends ThreadGroup by necessity.
It does, however, compose a thread at //2 and //3. In order to avoid the pain of reimplementing
the entire thread interface, but still give programmatic access to the composed thread, SmartThread
provides a thread getter, getThread(), at //5. For example, the following would allow you to change
the priority of the thread composed by the SmartThread to 1.
//...
SmartThread smartThread = new
SmartThread(someRunnable,
"My Smart Java Thread");
smartThread.getThread().setPriority(1);
//...
The ThreadExceptionListener Interface
Exceptions in secondary threads are communicated to listeners through the exceptionOccurred()
method, which is defined in the ThreadExceptionListener interface in
Listing 6.
This addresses our second fundamental requirement.
Classes that wish to be informed of exceptions occurring in other threads should implement this
interface. In addition, they should utilize a SmartThread, rather than a regular Java Thread, because
the SmartThread class knows how to notify the ThreadExceptionListener of exceptions.
The ThreadExceptionCleanup interface
A ThreadExceptionListener that wishes to communicate back to the Runnable in which the exception
occurred, can call its cleanupOnException() method, which is defined in the ThreadExceptionCleanup
interface in Listing 7.
This addresses our third fundamental requirement. Runnables should implement this interface to participate
in cleanup requests originating from a ThreadExceptionListener.
This enables reentry into the Runnable object. When an exception is thrown and not handled, and the uncaughtException() method called, you have left your Runnable object. This class gives you a way to get back into your Runnable object to perform any cleanup or state preservation.
The CheckedThreadException Class
This class allows checked exceptions occurring in a secondary thread to be propagated to listeners
in the same manner as unchecked exceptions. Recall that the occurrence of an unhandled unchecked exception
results in a direct call to the SmartThread’s implementation of uncaughtException(). From here it is
propagated to listeners via our exceptionOccurred() conduit. Because checked exceptions must be explicitly
caught (we can’t rethrow them in the run() implementation for reasons discussed earlier), by definition
they will never find their way directly to uncaughtException(). We need an indirect way to accomplish this.
CheckedThreadException, shown in
Listing 8,
extends the unchecked RuntimeException class. CheckedThreadException is a special exception that allows us
to throw checked exceptions from a method without requiring a throws clause. When we want to alert our
listeners of the occurrence of a checked exception in a secondary thread, we wrapper this exception in
a CheckedThreadException and rethrow it. This will result in a call to uncaught
Exception(), allowing us to propagate both checked and unchecked exceptions through the same interface.
The CheckedThreadException class also provides access to the original checked exception at //2 and the
thread the exception occurred on at //1. This addresses our final fundamental requirement.
Putting It All Together
Using the classes we have introduced, we have modified our initial multithreaded program from
Appendix A to effectively deal with the exceptions generated on its secondary threads. The entire
modified program is provided in Appendix B with the changes indicated in bold font. Let’s now examine
the specific areas we modified utilizing our new classes.
Listing 9
shows the modified FileOpener class. At //1 you will notice that our FileOpener class is implementing our
ThreadExceptionCleanup interface. This also requires us to implement the cleanupOnException() method at //5.
The cleanupOnException() method will be called if an exception is thrown from our Runnable object and not
handled by that object. We are also utilizing the CheckedThreadException class at //4. As discussed,
this enables us to throw checked exceptions out of our run() method. We must first catch the checked
exception at //3, then wrapper it as an unchecked exception via the CheckedThreadException class.
The "decoding" of this exception will occur later in the exceptionOccurred() method.
The cleanupOnExeption() method proves to be very useful at //2. Depending on where the exception
was thrown from in the run() method, a BufferedReader object may have been left open and need to be
closed. If the exception was due to a file not found, then the BufferedReader would not yet have been
opened, as this happens after it is determined that the file was found. However, what if the exception
was thrown after //2? If an exception is thrown after //2, the BufferedReader is left open and we have
created a resource leak. The cleanupOnException() method at //5 is called by exceptionOccurred() in
this case to close the BufferedReader at //6, prior to this thread shutting down.
This particular example could have been accomplished through the proper use of a finally clause,
however, in other implementations you may not have a try/catch block to work with because you may not
be dealing with checked exceptions, only unchecked exceptions. Therefore, you would not have a try
block in which to attach a finally clause.
Listing 10
shows the initial changes made to the PrimaryFrame class. PrimaryFrame now implements the
ThreadExceptionListener interface at //1. This enables the PrimaryFrame class to register itself as a
listener of uncaught exceptions occurring on its secondary threads via the addThreadExceptionListener()
method shown at //3. However, for all of this to work, you must use the SmartThread class instead of the
standard Java Thread class. This is accomplished at //2.
The last area to examine is the exceptionOccurred() method.
Listing 11
shows the implementation of this method and what we do when we are notified of an exception. This method
is called after:
• Creating a SmartThread class.
• Adding a ThreadExceptionListener object.
• Overriding the exceptionOccurred() method in your ThreadExceptionListener object.
• An exception is thrown from your Runnable object.
We see at //1 the exceptionOccurred() method is provided the necessary information to properly deal with
the problem being reported. This method knows the Runnable object and the thread the exception occurred on,
along with the actual exception thrown. First, we want to know if the exception was really a checked or
unchecked exception. At //2 we are checking if the exception is wrappered in a CheckedThreadException,
indicating the exception we caught is a checked exception. We then "decode" our
CheckedThreadException to see what type of checked exception it is at //3. If we determine the exception
was a FileNotFoundException, we attempt to open another file at //4.
Notice here that we don’t register another exception listener. You can register another one, and may
want to do so depending on what you would like to happen if this second file is not found. However, be
warned that if you register this same class as the listener, you can get the code into an infinite loop
fairly easily. It is suggested that in cases like this you register another object as the listener, not
the same object. Registering the same object would require some special case code to avoid the infinite
loop scenario.
If the exception caught here was not a checked exception, we know it was an unchecked exception.
Therefore, we fall to the else clause and call back to our Runnable object via cleanupOnException()
at //5. This is done to free the BufferedReader resource allocated in our Runnable object. This was
discussed earlier in reference to
Listing 9.
Figure 1 represents the flow of control through the final version of our program. Note that when
execution returns from the uncaughtException method, the thread will terminate immediately.
Alternate Solution
In the course of our research for this article, we uncovered an alternate solution for
multithreaded exception handling in Java published in CurrentProgramming with Java (Lea, D.,
Addison–Wesley, 1997) This method is called Completion Callbacks. Completion Callbacks involve a
predefined interface which an object implements. Secondary threads then must call this interface
to indicate success or failure. This allows the object implementing the interface to know whether
the secondary thread completed successfully or with an exception.
Completion Callbacks are an excellent approach to the problem addressed in this article but they
do require a more tightly coupled relationship between the primary object(s) and the secondary
thread(s). In cases where you don’t own the code to be executed in the secondary thread, this could
be a problem. The Completion Callback solution also requires the developer to take explicit actions
to address both checked and unchecked exceptions. By extending ThreadGroup and overriding the
uncaughtException() method the developer is relieved of some of this work, and is also provided
with a uniform and generic way of routing exception information to an object able to process it.
Summary
We have shown a solution to enable Java programs to effectively deal with exceptions occurring
in a multithreaded environment. We have done so, in part, by using the listener paradigm devised by
the JDK 1.1 event model. We have met all of our design goals with our solution and solved all of
the problems we have identified relating to completion callbacks. Our solution solves many of the
problems of dealing with exceptions in a multithreaded environment. This solution allows you to
throw checked and unchecked exceptions from run() methods at will, while ensuring these exceptions
will be caught and communicated to predefined listener objects.
This solution is more involved than a callback solution. The callback solution will work fine
for certain cases but does not solve all of the problems associated with multithread exception
handling. The solution we have provided is complete, robust, and follows the common listener
paradigm familiar to Java developers. We have also provided a complete running code example of
our final implementation utilizing our classes.
Acknowledgments
The authors wish to thank Gary Craig, Art Jolin, and Bob Love for their efforts reviewing the
first draft of this article and for their many useful comments and suggestions. (Appendices A and B
are available at Java Report Online—www.javareport.com) n
Joe DeRusso is a Senior Systems Developer with SAS Institute Inc. in Cary, NC. Joe applies
existing and emerging Web technologies, such as Java, JavaScript, DHTML, and ActiveX, to prototype
and develop Web clients for SAS Software products. He can be contacted at [email protected].
Peter Haggar is an Advisory Software Engineer with IBM in Research Triangle Park, NC. Peter works
on emerging Java and Internet technology, focusing on embedded Java and real-time operating systems.
He can be contacted at [email protected].