Threading is easy in Java, but today I was reminded of another of its pitfalls.
In Java- or container-provided threads, unhandled exceptions from our code will be printed or logged to the console. Create your own Thread
or use SwingWorker
/ ThreadPoolExecutor
, and it’s a different story..
Threaded code tends to die silently. Nothing on the console or logs. Unhandled exceptions are invisible, and leave very few clues. Let’s look at why.
Request-handling and Event-Handling loops
Web/ EJB containers, Swing and AWT applications might seem very different. But they all have a common basis, as they are all forms of event loop.
Processing a single request or event has the possibility of failure. Processing multiple must therefore include a reliability boundary, to isolate failures in a single request.
Well-behaved containers such a EJB/servlet containers, the AWT event-handler and the Java main thread all provide a reliability boundary in the form of a try/ catch
block which logs exceptions.
(The Java main thread is actually a process boundary, rather than an event loop. But as such a reliability boundary is still present.)
So in our normal daily coding, we are mostly writing code in the context of such a container- or platform-provided thread. No need to do anything special, any exceptions will show up in the logs.
But when we take these practices & start writing threaded code, we are in for a problem..
Thread, SwingWorker, FutureTask & ExecutorService
Writing threaded code has many challenges — synchronization, race conditions, proper division of work.. But perhaps the most basic requirement, is for a developer to be able to see any exceptions that occur!
When creating your own Thread
or using Runnable
, SwingWorker
, Future
, ThreadPoolExecutor
or ExecutorService
, the responsibility is yours to specifically examine how this task will get executed and ensure that exceptions will be logged.
Here are some basic recipes for safety:
Situation | Ways to make safe |
---|---|
Thread |
|
SwingWorker doInBackground() |
|
ThreadPoolExecutor |
|
Future / FutureTask |
|
The approaches vary somewhat, but the basic principle here is to either surround every threaded code block with try/catch manually, or to use a handler/ define a subclass to do this for us.
Obviously in any modest-size application, an automated approach is preferred. Coding try/catch
manually requires much repeated code, and has a high likelihood of some blocks being missed.
ThreadPoolExecutor
and friends are a bit different here. It already provides a method we can override, called on the successful or failed completion of tasks. The only difficulty here is checking if the Runnable is a Future
, and if so getting any exception.
Future
and FutureTask
may require special checking, as they hold exceptions internally. Calling get()
throws any enclosed exception wrapped in an ExecutionException. See the Javadoc.
Java library design
Now, considering the design of the Java libraries. Why was it done this way?
There are multiple situations here, where Java library code could have included diagnostics & “print to System.err” behavior for unhandled exceptions.
This would, of course, greatly assist application coding & use of these constructs — making exceptions visible, without requiring more boilerplate.
On the other side of the equation, is the need for Java to provide fundamental & general-purpose building blocks. These have to be usable to build platforms, frameworks, libraries and indeed other languages.
This argument says that exception-handling and the method of logging should not be prescribed, and should be provided by the user.
There is some middle ground — a “third way” — in Future
/ FutureTask
. Unfortunately this makes the exception harder to get out & diagnose than ever!
My assessment, leans towards better help for application code. Java main thread does it — why not be consistent? There would be minimal cost & much gain in providing useful “default” handlers.
Have you been bitten by exceptions in threaded code? Should the Java libraries handle this better? Add your comment now.
References:
– Dr Dobbs: Uncaught Java Thread Exceptions
– Stack Overflow: Handling exceptions from Java ExecutorService tasks
– Javadoc: ThreadPoolExecutor
– Javadoc: Future.get()
Very good article. I have seen this issue while developing Cron jobs using Spring too. At the end I put my job under a try catch which I hate the most to do.
Thanks Venkat, it’s concerning that the Java platform & libraries leave such a gap. Ability to see stacktraces & exceptions thrown by code, is a major issue.
In this case, we have to work around it — preferably with a solution we can reuse, such as a subclass or handler.
Of course having to fix any such occurrence, raises the need to fix all such occurrences. And the undesirability of missing any!