Uncaught Java Thread Exceptions
I recently encountered an issue where a worker thread was terminating quietly in some code I hadn't written, and couldn't really change. In this case, the thread was terminating due to an exception it had encountered. I wanted to robustly know when this thread terminated so that I could log it, and then restart it to continue the processing it was doing. I'll explain how I resolved this, but first, let's examine some code that illustrates the problem:
public class MyApp { static int count = 0; public class MyWorker extends Thread { public void run() { while ( true ) { try { // Do this work every second forever unless interuppted doWork(); Thread.sleep(1000); } catch ( InterruptedException e ) { return; } } } private void doWork() { // Simulate work that sometimes results in NullPointerException StringBuffer sb = new StringBuffer("My Work counter: "); if ( count++ >= 5 ) { sb = null; //oops! count = 0; } sb.append(count); System.out.println(sb.toString()); } } public MyApp() { MyWorker worker = new MyWorker(); worker.start(); } public static void main(String[] args) { MyApp te = new MyApp(); } }
To summarize, we have a main class, MyApp
, with a nested class, MyWorker
, which extends Thread
. In the run method, a call is made to the doWork
method every second forever unless the thread is interrupted. However, the code has a bug, simulated by the creating of a NullPointerException
, which is never handled, thus terminating the Thread
. The issue is that the application code never really knows that it happens and the periodic processing that the Thread
was doing has now ceased.
Thread.UncaughtExceptionHandler
According to the Java API documentation, when a thread is about to terminate due to an uncaught exception, the Java Virtual Machine will query the thread for its UncaughtExceptionHandler
using Thread.getUncaughtExceptionHandler()
and will invoke the handler's uncaughtException
method, passing the thread and the exception as arguments. If a thread has not had its UncaughtExceptionHandler
explicitly set, then its ThreadGroup
object acts as its UncaughtExceptionHandler
. If the ThreadGroup
object has no special requirements for dealing with the exception, it can forward the invocation to the default uncaught exception handler.
Java allows you to install a default exception handler for threads, for just this very reason. You have three options in doing so:
- You can set it for a specific
Thread
- You can set it for a
ThreadGroup
(which means for allThread
s in that group) - You can set it VM wide for all
Thread
s regardless
In our example, where the Exception
occurs in code we cannot change, we'll need to go with option 3. Doing so is very easy, and allows us to log the Exception
that has occurred, get the full stack trace, and then restart the worker Thread
since it will have terminated by the time the exception handler is notified. Here is the code modified to install a default exception handler (only the modified code is shown):
public class MyApp { ... public MyApp() { Thread.setDefaultUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println(t.getName()+": "+e); MyWorker worker = new MyWorker(); worker.start(); } }); MyWorker worker = new MyWorker(); worker.start(); } public static void main(String[] args) { MyApp te = new MyApp(); } }
With this change when the unhanded NullPointerException
occurs, the default UncaughtExceptionHandler
we installed is invoked, which logs the thread name and the exception, and then restarts the worker thread to continue its processing. Although the original problem of the thread terminating still occurs, at least we now have insight as to when it happens, and can take action. In this example, we simply restart the worker thread to ensure the processing still occurs.
Executing this code now shows the following:
My Work counter: 1 My Work counter: 2 My Work counter: 3 My Work counter: 4 My Work counter: 5 Thread-0: java.lang.NullPointerException My Work counter: 1 My Work counter: 2 My Work counter: 3 My Work counter: 4 My Work counter: 5 Thread-1: java.lang.NullPointerException My Work counter: 1 My Work counter: 2 My Work counter: 3 My Work counter: 4 My Work counter: 5 Thread-2: java.lang.NullPointerException My Work counter: 1 My Work counter: 2 ...
This output continues indefinitely, as it was intended.
Alternatively, we can install the uncaught exception handler for just a specific thread. Using our example, since MyWorker
extends Thread
, we can call its setDefaultUncaughtExceptionHandler()
method and install the same handler code, as shown here:
MyWorker worker = new MyWorker(); worker.setDefaultUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println(t.getName()+": "+e); MyWorker worker = new MyWorker(); worker.start(); } }); worker.start();
Perhaps you'll find this useful in your own code, as I have, to help ensure the robust handling of exceptions, especially when it comes to concurrent processing in worker threads.
Happy coding!
-EJB