Exception handling is a mechanism used to handle disruptive, abnormal conditions to the control flow of our programs. The concept can be traced back to the origins of Lisp and is used in many different programming languages today.
If a program reaches such an abnormal condition, the Java runtime tries to find an appropriate exception handler by going through the call stack. This behavior is called “exception propagation”, and behaves differently regarding the type of exception.
Different Kinds of Exceptions
Exceptions can be categorized into 2 categories that differ in coding requirements and behavior: Checked and Unchecked Exceptions.
Checked exceptions are anticipated, and recoverable events outside of the normal control flow.
Because they are supposed to be anticipated, the compiler makes sure we declare them on our method signatures, and enforces either handling or propagating them further up the call stack. This is called the “catch or specify requirement”.
Checked Exceptions must be propagated explicitly using the
Unchecked exceptions are not anticipated and often supposed to be unrecoverable. They are not required to conform to the “catch or specify requirement”.
They will be automatically propagated up the call stack until an appropriate exception handler is found. Or, if none is found, the runtime terminates.
As the name implies, these exceptions are happening at runtime, often due to programming problems and bugs.
Examples we all know are the classics like
If our program crashes thanks to a
NullPointerException, it’s an indicator that we didn’t do enough validation, or forgot to initialize a value.
If a method returns such an exception, it might be anticipated, like
But as unchecked exceptions, they don’t have to be documented in any way.
Errors are unanticipated and unrecoverable events, usually thrown by the runtime itself.
These are all grave errors with almost no possibility to recover gracefully, except printing the current stack trace.
All subclasses of
Error fall into this category, and by convention are named
...Error instead of
Throwable and its Subtypes
The basic concept of handling exception comprises 3 (+1) keywords initiating code blocks interacting with each other:
The order of the blocks is essential.
All exception handling starts with a
try block, followed by zero or more
catch blocks, and ends with a single, but optional,
try block creates the scope we want to handle exceptions for.
It can’t exist without at least one
catch or a single
catch block is known as an exception handler.
The exception type handled must be specified by declaring a variable.
catch blocks with different exception types are possible.
But they must be ordered according to their (possible) relationships.
Instead of creating a
catch for every single exception type we want (or need) to handle, we can combine them, if their handling logic can be consolidated:
The combined variable will be either exception, but will have the type of the most common ancestor:
will become an
ClassNotFoundExceptionwill become an
Multi- and single-type
catch blocks can be used in tandem.
But the rules about ordering from most to least specific still apply. And any type can only be caught once.
finally block is an optional code block that is guaranteed to execute after the
try scope is left, either by successfully completing the
try block, or leaving a
It’s the right place for any required cleanup, regardless of the success of the
Dealing with resources can be simplified by using
try-with-resource feature introduced in Java 1.7.
Cleaning up resources was usually done in the
finally block, due to the guarantee to execute in any case:
With Java 1.7 an addition to the
try keyword was introduced:
AutoCloseable can be created right after the
try keyword, surrounded by parenthesis. We’re not restricted to a single object, multiple object creation can be separated by a semi-colon(
AutoCloseable will be closed, after the scope of
try will be left, either successfully, or via a
catch block. The closing order will be reversed to the creation order.
The key difference from handling resources ourselves in a
finally block is the possibility of additional exceptions during any close-operation.
In the example above, every object might throw an exception on closing.
The first occurred exception will be handled by the appropriate
Additional exceptions will be available via the exception’s
This way, we don’t need another
try/catch for closing.
But it means that an exception during the closing operation might end up being handled by a
catch block that was designed to handle an exception for non-closed-related code.
Dealing with a closeable resource is much more straight-forward and less code.
But we have to deal with suppressed exceptions and can’t add additional code around the close-operation.
try-with-resources doesn’t prevent us from supplying a
finally block, though, for any additional cleanup/logging we might need.
Now that we know how to handle exceptions, it’s time to learn about throwing them.
Java uses the keyword
throw to leave the normal control flow and propagate an exception to an appropriate handler:
The keyword must be followed by an instance of a
Throwable or subclass.
It doesn’t have to be
new, we can even re-
throw an exception in a
Checked exceptions must still oblige the “catch or specify requirement”, so we need either directly catch the exception (doesn’t make sense), or use the next keyword,
With the help of the keyword
throws, we can specify any thrown checked exceptions directly on a method signature.
This makes the compiler happy by satisfying the “catch or specify requirement”.
The exception doesn’t have to be thrown directly in the scope of the method. Any called code might throw the exception, and our method will propagate the exception further up the call stack:
Unchecked exceptions can be specified, too. But there are no compile-time advantages for doing so, no one will force us to catch it.
By implementing an interface or subclassing a type, we can modify the specified exception types.
Modifying types thrown
Just because an interface or a class has specified a specific exception type, doesn’t mean we have to obey it in an implementation or subclass.
Sub-types can use the original
throws declaration or modify it:
- Throw a subclass of the original exception:
- Remove the
throws Exception-> n/a
try-with-resource works for every previously existing type that implement
Closeable, without changing any of our code.
Although it wouldn’t be strictly necessary to change any implementation, because
IOException is a subclass of
Exception, it would have changed the method signature of code that’s not maintained by Java.
This feature also can lead to weird behavior, regarding which type we use:
Added in Java 1.4, chained exceptions are a mechanism to respond to an exception with another exception.
It can be set (publicly) in 2 different ways:
The biggest advantage of chained exceptions is the possibility to elevate the initially thrown exception into the same layer of abstraction as the surrounding scope. We can be more specific with the new exception, providing essential information needed for sensible logging and debugging purposes.
Creating Custom Exceptions
We’re not restricted to the exceptions provided by the JDK, we can easily create our own.
So why should we?
The most general cases are covered by the JDK. But there are scenarios where we need more fine-grained control or more information about the circumstances that lead to an exception.
throw custom exceptions directly, or we can re-
throw them as a chained exception.
Providing a Benefit
A custom exception should always provide a benefit, not just replicating an existing exception and its purpose with a new type. By adding additional fields, we can supply more information, allowing special handling for a particular case. Usually, an exception tells us what happened, but not much about why it happened. With a custom exception, it can:
ValidationException, we can handle validation problems besides more general exceptions and also get the field name. Another improvement would by providing the type of validation failed, or any other information we might need to handle the exception efficiently.
Business Logic Exceptions
So far, we’ve used exceptions to describe and handle disruptive events in the normal control flow. But we can use exceptions to reflect disruptions in our business logic, too:
It seems alluring to use exceptions for more fine-grained control over business logic problems. But be aware of the performance implications!
Creating exceptions isn’t free, and overusing them for the wrong reasons will have an impact.
Everything we do has performance implications, nothing’s for free. But some things can be cheap, so we need to know which features won’t affect the performance too much, and which are.
Java’s exception can be both.
Some parts are almost free, others are immensely expensive, depending on how we use it.
try-catch block is almost free.
The compiler adds an exception table with the corresponding range to the method and the target position for handling it:
This simple method translates following byte-code (shortened for readability):
public float divide(float, float); Code: 0: fload_1 1: fload_2 2: fdiv 3: freturn 4: astore_3 5: getstatic #3 // Field System.out:LPrintStream; 8: ldc #4 // String Division Error 10: invokevirtual #5 // Method PrintStream.println:(LString;)V 13: fconst_0 14: freturn Exception table: from to target type 0 3 4 Class java/lang/ArithmeticException
try-catch we got fewer opcodes in total:
public float divide(float, float); Code: 0: fload_1 1: fload_2 2: fdiv 3: freturn
If we ignore the additional opcodes of the actual exception handling in the first example (4–13), the
try-catch doesn’t need a single additional opcode at runtime.
If we wouldn’t
return in the
try, only a
goto would have been added.
We got more bytecode, but under non-error circumstances, we still have no impact.
And the added value of possible exception handling.
So there’s no need to neglect exception handling in fear of performance impact.
We don’t have to worry about the performance impact of throwing an exception.
It’s just a single opcode:
The bigger problem is that we need an instance of an exception type to throw, and its creation can be very costly.
Usually, the creation of objects is easily determinable and constant in complexity. Exceptions, on the other hand, are not, due to containing a stack trace.
At creation time, an exception will gather all stack frames in the current call stack, so we can access it with
The deeper the current call stack is, the costlier its gathering will be.
Even if we actually don’t care about the stack trace and might only pop one or two stack frames.
Throwing repeatedly in short succession is most likely a bigger problem. It can become so costly for the runtime, that it might omit the stack trace if too many exceptions are created repeatedly.
But if we really want to make sure that the stack trace is always included, regardless of its cost, we can use the JVM option
There are several options for reducing the cost of exceptions:
Throwable(String, Throwable, boolean boolean)constructor
Throwable, which is a supertype to all exceptions and errors, provides multiple constructors.
One of it allows more precise control of how an instance is created.
protected, so it’s only available to our own custom types:
By using this particular constructor, we can build cheaper exception instances.
All other constructors will always call
This can be really useful when the signal that an exception occurs is more important than the whole reason why.
My previous business logic exception example could be a good fit. We don’t need a stack trace if a customer registration fails due to validation issues.
Another option is creating a
static instance of the desired exception type and throwing it instead of creating a new one.
Just because we don’t really create a new instance, we don’t need to forfeit the advantages of having a stack trace altogether.
Throwable#fillInStackTrace() manually, we can have an on-demand stack trace.
Dos & Don’ts
Always throw the most specific exception type possible.
Be As Specific As Possible
The catcher can decide to only handle a broader type, but can’t change the specificity of the thrown type. So if we can provide the handler with more differentiable options, we should.
Let’s check out the type hierarchy of
java.lang.Throwable java.lang.Exception java.io.IOException java.nio.channels.ClosedChannelException java.nio.channels.AsynchronousCloseException java.nio.channels.ClosedByInterruptException
These are distinct exceptions for different kinds of disruptions of the normal control flow.
And each one might be handled differently.
If the handler doesn’t care about the finer details, they can use
catch (IOException e)instead of a more specific type.
Document Exceptions with Javadoc
All exceptions thrown by a method should be documented with Javadoc’s
@throw <type> <description>.
You might argue that unchecked exceptions are not part of the method contract, and therefore shouldn’t be documented. That’s a valid viewpoint, they represent what happens when the contract gets violated, and are most likely unrecoverable.
But in my opinion, every shred of extra information to fulfill such a contract might help.
If a method doesn’t allow
null for an argument, why not communicate it clearly with documenting a possible
When deciding against documenting such an exception, we need to at least specify all requirements with
Descriptive and Helpful Messages
Exceptions are an essential tool for debugging, especially in logs. That’s why every exception deserves a meaningful, descriptive, and helpful message. There are 3 parts an exception can contribute:
- What happened? The exception type
- Where did it happen? The stack trace
- Why did it happen? The message
One of the best examples of why this is important is the
It tells us what happened and where, but in case of a fluent call, we might not exactly know which call or property caused it.
But we can tell who to blame in the message, and debugging just became easier.
Even Java itself realized that more information might be useful in case of a
As awesome as the additional information is, we have to consider the possible leakage of implementation internals to logs.
Nested try blocks
Even though nested
try-blocks are possible, we should avoid them.
Besides from unnecessary complexity and less readability, any thrown exception in an inner block might be propagated to an outer
try-block, if not handled, which could lead to subtle bugs.
try-blocks should be refactored into multiple methods. This will make the code easier to reason with.
Don’t just Log & Rethrow
Never catch an exception just to log it, and throw it again:
Either handle an exception or let it go up the call stack.
The upstream code has no way to know if an exception was already logged. And it might be propagated through multiples frames, should every one log the exception?
If you can’t or won’t handle the exception, besides logging, let someone else handle it.
Some might argue that since throwing and catching exceptions are relatively costly operations, all this catching and rethrowing isn’t helping your runtime performance. Nor is it helping your code in terms of conciseness or maintainability.
An exception can be a chained exception, repackaging the information to be more specific and log the original circumstances.
Don’t Swallow Exceptions
Exceptions fulfill a purpose, don’t just ignore them:
Like “Don’t just Log & Rethrow”, either handle an exception or let it propagate.
There are situations where we are not really interested in exception details and don’t want to propagate it further. Maybe we should at least log it, instead.
Too Many throws
Too many exceptions in the
throws clause is an indicator of a method design weakness.
In an ideal world, methods wold be small, self-contained, side-effect free blocks of code.
If we need to add more and more exception types to
throws, it’s a good sign the method is doing too much.
It’s better to have multiple methods, with a single
throws, instead of an “everything” method, accumulating all exceptions.
Avoid Catching Exception or Throwable
After catching more specific types, we might add an additional
catch block with
Exception to handle any additional cause.
But it’s a cheap “cop-out”, why not propagate it further, so someone might handle it appropriately?
throw & finally
finally block is ensured to run after leaving a
try block or its
What happens if we
throw another exception in the
finally block’s exception will cancel out any otherwise propagated exception:
Another aspect to consider is the opposite: if we throw another exception in either the
catch block, the
finally block will still be executed:
Exceptions != Goto
Don’t confuse exceptions with an enterprisey-goto.
Exceptions are designed as disruptive events to the normal control flow, not as an addition to managing the ordinary control flow.
Especially business logic and stack trace-less exceptions should not be used to short-circuit the usual control flow. These invisible control flows make it hard to reason with our code, or even worse, with the code of other people.
A well-designed API must not force its clients to use exceptions for ordinary control flow.
— Effective Java by Joshua Bloch
As the name implies, they should be the exception, and not the new normal.
I’ve previously shown that in the non-exception case, it might be even faster than an additional
But if an exception occurs, the costs are significant.
Never replace a simple
null-check with catching a
Or checking array sizes with catching
We should use the tools provided by the JVM, like exception handling, the way they were intended. Only this way can we be sure to get the best performance out of them.
Alternatives to (Checked) Exceptions
Exceptions are a great tool, but there are alternative ways to design our APIs without anticipated exceptions disrupting the normal control flow. It all depends on how strict your error-handling is supposed to be.
Imagine loading the content of a file:
How about returning an
Optional<String>` instead of an exception?
The basic idea is handling any exceptional control flow in the implementation, instead of propagating an exception.
By choosing a more complex return value, we can communicate any problems back to the callee.
They might have to check the return value more thoroughly, but the scope of the exception changed.
In many cases, we might no longer need a
Other programming languages, even JVM-based ones like Scala, have specialized types for error handling, like
Either[+A, +B], which can be either the one, or the other type.
Java doesn’t have such a flexible type.
The closest multi-state type is
Although it doesn’t convey any additional information.
But with its help we can build our own simplistic
Our code can now return an
Either.right(successValue), and we can consume it in a functional fashion:
There’s more to exception than at first glance. We have to know when and how to use them, and when not.
Exceptions aren’t free, but we can reduce their cost to a minimum. And if we must, we can even replace them with other constructs.
The one thing missing from this article is how to handle exceptions in Java Streams. It’s a topic in itself, and deserves its own article.