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.



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 all Thread 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