Exceptions in Java: You're (Probably) Doing It Wrong
You've probably used exceptions in Java, but have you used them properly? Generally, exceptions are for exceptional cases. See whether or not you should use checked exceptions, and when and how to use Java exceptions.
Join the DZone community and get the full member experience.
Join For FreeI recently wrote about some of the things I do differently in my codebases relative to other programmers. The post was very popular and generated a lot of healthy discussion which I’m always a fan of. One of the cornerstones was that I do not use Checked Exceptions. One of the comments asked how I actually use Exceptions in my codebases, so I wanted to cover this in some more detail.
Exceptions should be for Exceptional cases. These are unexpected scenarios, and usually, will not have a nice easy way of recovery. If there is a sensible recovery option, then do not use an exception; using exceptions to control the flow through your program is bad practice. If we take this to be the case, then the best way to handle an Exception is to alert this out; whether through logs or some other service depends on how you work on your application. You then have to make a choice:
Either you keep the application up and have someone manually inspect the application to ensure that it is in the correct state and not damaged (usually a bad idea)
Assume your application is damaged and you bring down the instance
The latter option is preferred but is obviously contingent on your architecture being designed to cope with this.
In terms of coding, this is easy to do. If you have an exceptional circumstance, throw an application specific Runtime exception- extend RuntimeException with a name related to your application. This means when tracing logs you know the error is related to code you have written.
Why use runtime and not checked? Checked exceptions lead to ugly code as the exception handling must be propagated upwards, leading to all calling methods having “throws SomeException” on the end, which leads to more catch blocks. The old argument is that this “forces” developers to handle exceptions properly. Anyone on a real code base knows that this does not happen, and Exceptions are routinely ignored, or printed out and then ignored. This isn’t some sign of a terrible developer; it is such a common occurrence that it is a sign that checked Exceptions are broken. If you need further evidence, look to C# and pretty much all of the new JVM languages such as Clojure and Scala have done away with checked exceptions completely.
This is all fine for your own code, however, most libraries and frameworks come with Exceptions built in. How do we handle these?
If you have no sensible way to handle/recover from the exception then throw it up, but first wrap it in a RuntimeException of your own creation. If my application is called “Stockfighter,” I will have a “StockfighterException” to wrap any other Exception:
public class StockfighterException extends RuntimeException{
public StockfighterException(String error, Exception e) {
super(error, e);
}
}
I do this for two reasons; firstly, when I then debug the application I can see where the Exception has come from in my code base and add any useful information. This is immensely useful when debugging. Secondly, as mentioned, I dislike checked exceptions. This means I can then throw it up the stack without polluting all of the methods with “throws XException”.
As mentioned, this will normally kill the application which is the desired effect if it is a genuinely exceptional case. If I don’t want to kill it, or I want to log some extra information, I will usually wrap the key functional parts of my application in an ExceptionHandler. This is usually a decorator of the class which wraps the functions in a try catch block. I will also add ExceptionHandlers to any Threads to ensure they aren’t silently swallowed.
This is also a good separation of concerns in OO terms. If you have multiple implementations of something then you will often need to handle and log exceptions in a similar way in each one. Using a decorating handler means this can be shared amongst the implementations.
One of my favourite patterns is when using composites. In a lot of code, something will happen (receiving an event for example) and I will have multiple subscribers that will want to act on that.
InfoPublisher publisher = new InfoPublisher();
publisher.subscribe(
new ErrorHandlingSubscriber(
new CompositeSubscriber(
new StoreInfomationSubscriber(),
new DoSomethingWithInfoSubscriber(),
new AnotherActorSubscriber()
)
)
)
My InfoPublisher publishes its event to whatever has “subscribed”. I have a CompositeSubscriber which loops through all of the Subscribers to pass the information to it.
class CompositeSubscriber implements Subscriber{
private Subscriber[] subscribers;
public CompositeSubscriber(Subscriber... subscribers) {
this.subscribers = subscribers;
}
@Override
public void onInfo(String info) {
for (Subscriber subscriber : subscribers) {
subscriber.onInfo(info);
}
}
}
This is nice, clean and simple. I can then wrap the whole thing in an ExceptionHandler to deal with anything that goes truly wrong in the codebase.
public class ErrorHandlingSubscriber implements Subscriber{
private Subscriber subscriber;
private Logger logger = LoggerFactory.getLogger(ErrorHandlingSubscriber.class);
public ErrorHandlingSubscriber(Subscriber subscriber) {
this.subscriber = subscriber;
}
@Override
public void onInfo(String info) {
try{
subscriber.onInfo(info);
}catch (Exception e){
logger.error("Serious Exception!", e);
System.exit(1);
}
}
}
This way my exception handling is cleanly separated from my functional code.
So, in summary:
Never use Exceptions for control flow in your application. Use them only for exceptional circumstances
Wrap Checked Exceptions in your own app RuntimeException so you know where the Exception has come from
Create a top level ExceptionHandler decorating your code to appropriately log or crash the application.
Follow me on Twitter at @SambaHK for more ramblechats.
Opinions expressed by DZone contributors are their own.
Comments