Abstract: Java 7 removes the Swing Event Dispatch Thread (EDT) hack that allowed us to specify an uncaught exception handler for the EDT using a system property sun.awt.exception.handler.
Welcome to the 196th issue of The Java(tm) Specialists' Newsletter, sent to you from the beautiful Island of Crete in Greece. We have now lived on this rock in the middle of the Mediterranean for the last five years. They have been good years, for which we are grateful. A lot of the material possessions that we had to leave behind in South Africa when we moved here, have been miraculously replaced with equivalent or even better goodies. Those of you who have moved to a new continent will understand. Emigration is not for whimps! What we miss most are our parents, siblings and friends. Those cannot be replaced.
Tomorrow is the 11th anniversary since I sent out my first The Java(tm) Specialists' Newsletter. Over the years, we have had some wonderful feedback from Java enthusiasts around the world. Think of this: There are apparently ten million Java programmers out there. Of those, only 50.000 are reading this publication. That is only 0.5%. However, I think this is the TOP 0.5% of Java developers, the creme-de-la-creme, the elite! I am honoured that you take the time to read my writing :-)
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
As you probably know, in Swing we have the Event Dispatch Thread (EDT) that processes all of the GUI events. If an exception occurs, the default behaviour is to write the stack trace to the console.
There are several ways that we can catch the exception and do something else instead:
Thus, if you want to show an error dialog for any throwable that happens in the AWT event dispatch thread or that causes a thread to terminate, then simply set a default uncaught exception handler.
Here is an example of a SwingExceptionHandler, that opens up a dialog showing the entire stack trace. Most users would probably get scared if they see all those details, so you should probably show a human-friendly message instead.
import javax.swing.*; import java.awt.*; import java.io.*; import java.lang.reflect.*; public class SwingExceptionHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(final Thread t, final Throwable e) { if (SwingUtilities.isEventDispatchThread()) { showMessage(t, e); } else { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { showMessage(t, e); } }); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } catch (InvocationTargetException ite) { // not much more we can do here except log the exception ite.getCause().printStackTrace(); } } } private String generateStackTrace(Throwable e) { StringWriter writer = new StringWriter(); PrintWriter pw = new PrintWriter(writer); e.printStackTrace(pw); pw.close(); return writer.toString(); } private void showMessage(Thread t, Throwable e) { String stackTrace = generateStackTrace(e); // show an error dialog JOptionPane.showMessageDialog(findActiveOrVisibleFrame(), stackTrace, "Exception Occurred in " + t, JOptionPane.ERROR_MESSAGE); } /** * We look for an active frame and attach ourselves to that. */ private Frame findActiveOrVisibleFrame() { Frame[] frames = JFrame.getFrames(); for (Frame frame : frames) { if (frame.isActive()) { return frame; } } for (Frame frame : frames) { if (frame.isVisible()) { return frame; } } return null; } }
We can demonstrate this with a DemoFrame that has two timers and a button. If you press the button, it causes a NullPointerException. The first timer to fire is a Swing timer, meaning it will run in the EDT. This timer causes an IllegalStateException after three seconds. The second timer runs in an ordinary thread after six seconds and causes an IllegalArgumentException.
import javax.swing.*; import java.awt.event.*; import java.util.*; public class DemoFrame extends JFrame { public DemoFrame() { super("DemoFrame"); JButton button = new JButton("Cause Exception"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { throw new NullPointerException("brain is null"); } }); add(button); // This timer will run in the EDT javax.swing.Timer timer1 = new javax.swing.Timer(3000, new ActionListener() { public void actionPerformed(ActionEvent e) { throw new IllegalStateException("forgotten name"); } }); timer1.start(); // This timer will run in a normal thread java.util.Timer timer2 = new java.util.Timer(); timer2.schedule(new TimerTask() { public void run() { throw new IllegalArgumentException("stop arguing!"); } }, 6000); } public static void create() { SwingUtilities.invokeLater(new Runnable() { public void run() { DemoFrame frame = new DemoFrame(); frame.setDefaultCloseOperation(EXIT_ON_CLOSE); frame.setSize(500, 200); frame.setLocationByPlatform(true); frame.setVisible(true); } }); } }
One way to set up the Swing based exception handler is to make it the default uncaught exception handler, like this:
public class SwingExceptionFrameWithDefaultHandlerTest { public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler( new SwingExceptionHandler()); DemoFrame.create(); } }
In this case, even the timer task that causes the timer thread to die will appear as a popup dialog. If you want only the events from the Swing thread to appear, prior to Java7 you could use the sun.awt.exception.handler "hack", which took preference over the other exception handling mechanisms. Since Java7, they have disabled that "hack", so you could register an uncaught exception handler for the EDT specifically:
import javax.swing.*; public class SwingExceptionFrameWithEDTHandlerTest { public static void main(String[] args) throws Exception { SwingUtilities.invokeAndWait(new Runnable() { public void run() { Thread.currentThread().setUncaughtExceptionHandler( new SwingExceptionHandler() ); } }); DemoFrame.create(); } }
However, you need to be careful that the Event Dispatch Thread does not get replaced with a new thread when an exception occurs. Some older versions of Java created a new EDT every time an exception occurred. (Update 30th Dec 2011: Gerard Davison sent me some code to demonstrate that on some versions of Java, the EDT is replaced when an exception occurs. However, since Java 7, it seems that my trick works on all the platforms that use OpenJDK.)
The safest approach would be to create a default uncaught
exception handler that filters the thread on which the
exception happened with
SwingUtilities.isEventDispatchThread()
.
Again, thank you very much for reading this newsletter. I hope you enjoy it as much as I enjoy writing it :-) Kind regards from Crete
Heinz
We now have a Facebook page for the Java Specialists. If you've enjoyed reading this newsletter, please take a moment to "like" our group.
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.