Abstract: Java 5 introduced the UncaughtExceptionHandler. We can set it globally or per thread. This helps us to manage uncaught exceptions better.
Welcome to the 89th edition of The Java(tm) Specialists' Newsletter. Ours is probably the most elite Java newsletter in the world, so if you are a member, you are a part of that elite! If you know of people who are really good at Java, please let them know about this newsletter.
Today was an exceptionally beautiful day in Cape Town. No wind, nice warm weather, as if it were summer. My friend Herman Lintvelt (who has authored a few newsletters) and I had a great lunch dining on huge slabs of meat and fine South African red wine, to celebrate the "good life". I am sitting outside on my balcony at 23:00 enjoying a mild evening at 25 degrees celsius :-) It will probably rain tomorrow.
A small change in my newsletter structure is that from now on, the heading will show which version of Java I was working with when I wrote the newsletter. Since The Java(tm) Specialists' Newsletter explores interesting features, we sometimes stumble across "features" that are actually bugs and that are removed in the next release. This has caused confusion in the past, especially when readers look at older newsletters.
I would like to thank all those who sent me their quotes of what they thought about the newsletter. I was touched, and have new motivation and energy to write these newsletters :-)
Last week I presented a Java
Course at a South African company. During the course,
one of the C++ programmers questioned me about generics in
Java. I try to stay away from beta versions for production
code, but curiosity got the better of me, so I tried playing
with it. IntelliJ IDEA 4.0 was not too happy with the new
for
construct, so I tried IDEA 4.1,
which worked fine.
I must admit that generics take some getting used to, and
changing your code is not always straightforward. For example,
I could not find a way of using generics in a static context.
In a future newsletter, I will write about some of the
experiences of migrating my existing code to generics.
A Google on "generics java" gave me approximately 50'000 hits,
so I won't bore you with "yet another how-to-do Java Generics
newsletter" until I have something interesting to write about them :-)
Instead, like most topics in this newsletter series, I will write about something that I discovered by chance, whilst I was glancing at the source code of Sun JDK 5 beta. Google did not reveal any newsletters about this topic, so here goes...
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
In my experience, all Java projects contain poorly written exception handling code. Let's take a simple example, and make it complicated:
import java.sql.*; import java.util.List; /** * You'll have to compile with JDK 5 and use the switch * javac -source 1.5 */ public class DatabaseQueryCode { private final Connection con; public DatabaseQueryCode(Connection con) { this.con = con; } /** * Take a list of Strings and execute all the queries in one * transaction. */ public void executeQueries(List<String> queries) throws SQLException { con.setAutoCommit(false); Statement st = con.createStatement(); for(String s : queries) { // I love this construct :-) st.execute(s); } con.commit(); st.close(); con.setAutoCommit(true); } }
That code is obviously not as correct as it could have been.
If we fail halfway through the method, we won't set the
auto-commit to be true
, so let's change that:
public void executeQueries(List<String> queries) throws SQLException { con.setAutoCommit(false); Statement st = con.createStatement(); try { for(String s : queries) { st.execute(s); } con.commit(); st.close(); } finally { con.setAutoCommit(true); } }
This is better, but also not ideal. If any of the queries fail, we want to roll back and still close the statement, and we want to make sure that we do not close the statement if it was not open, so let's change it again.
public void executeQueries(List<String> queries) throws SQLException { con.setAutoCommit(false); Statement st = con.createStatement(); try { for(String s : queries) { st.execute(s); } con.commit(); } catch(SQLException ex) { con.rollback(); } finally { st.close(); con.setAutoCommit(true); } }
Good, this is better, but what happens if one of the Strings
is null
and we get a NullPointerException
?
What happens if we run out of memory and get an
OutOfMemoryError
? What happens if we get an
OOME and at the same time the connection does not work
anymore? Then the finally
would cause an exception, which
would mask the OOME, and make it disappear. There are lots
of possibilities, and if we try to cater for all
eventualities (excuse the pun) then we will go crazy trying
and our code will look rather complicated.
My point with this example was not to show you how to write the perfect database exception handling. Truth is, I don't know how to make it bullet proof. Maybe water balloon proof, but not bullet proof.
So in the real world, how are exceptions handled? Frequently, exceptions are stubbed out and ignored, because the writer of the code did not know how to handle the error (and was going to go back and fix it, one day, but the project manager was breathing down his neck and the release had to go out that afternoon). This is bad, since you then do not know that something has gone awry. On the other hand, if the exception bubbles up the call stack, it may kill the thread, and you may never know that there was an exception.
I have witnessed production code do things like this (I kid you not):
try { // do something } catch(Exception ex) { // log to some obscure log file, maybe return ""; }
The effect was that the webpage showed empty strings as values when something went wrong with the code.
My approach to exceptions is to have a central mechanism that deals with any exceptions that I am not 100% sure of how to handle. Whenever something goes wrong, this central place is notified. However, what happens when you are using someone else's code and their threads die without warning?
An amusing example was an early version of Together/J. I enjoyed using Together/J, even though it was rather memory hungry. Instead of starting with 512MB as default maximum old generation memory size, I set it to only use 92MB. This made Together work faster and save resources. However, occasionally random threads would simply die, so you could perhaps not print anymore or some other functionality would vanish.
In newsletter 81, I described a way that you could catch unhandled exceptions in your GUI code, by starting up your GUI in a special thread group. I had assumed that this was the way that uncaught exceptions should be handled in future. The old way of catching these exceptions was to set a system property, but in the JDK code comments that was described as a temporary workaround.
If you look at the java.lang.Thread JavaDocs, you will notice some new methods that can help us, specifically setDefaultUncaughtExceptionHandler() and setUncaughtExceptionHandler(). With these two methods, you can specify an exception handler for an individual thread (setUncaughtExceptionHandler()) or you can set a default handler for all threads that do not have their own UncaughtExceptionHandler (setDefaultUncaughtExceptionHandler()).
To contrast this with the earlier newsletter, please use the Gui class of newsletter 81 and compile it together with these two classes, DefaultExceptionHandler and EvenBetterGui:
import javax.swing.*; import java.awt.*; // did you know that you could import inner classes? import java.lang.Thread.*; public class DefaultExceptionHandler implements UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { // Here you should have a more robust, permanent record of problems JOptionPane.showMessageDialog(findActiveFrame(), e.toString(), "Exception Occurred", JOptionPane.OK_OPTION); e.printStackTrace(); } private Frame findActiveFrame() { Frame[] frames = JFrame.getFrames(); for (int i = 0; i < frames.length; i++) { if (frames[i].isVisible()) { return frames[i]; } } return null; } }
import javax.swing.*; public class EvenBetterGui { public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler( new DefaultExceptionHandler()); Gui gui = new Gui(); gui.pack(); gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); gui.setVisible(true); } }
We can now catch all unhandled exceptions by calling
Thread.setDefaultUncaughtExceptionHandler()
and passing in our own exception handler (subject to security
manager permissions of course).
In my opinion, this is a great addition to the Java Programming Language, and I am looking forward to finding more nuggets that will convince me to switch over to Java 5 permanently. A nice resource for finding differences between JDK 1.4.2 and JDK 5 is JDiff. [I discovered after sending this newsletter that the author of JDiff is on our newsletter :-]
Thread has some other rather useful methods, such as
getStackTrace()
and getAllStackTraces()
.
What else can you do in Java 5? You can measure elapsed
time in nanoseconds instead of milliseconds, which should
make performance calculations more accurate (or more suspect?).
Have a look at System.nanoTime()
.
That's all for this newsletter. I have to get to sleep before I catch yet another cold from overworking...
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.