Deep Dive: All about Exceptions

Overview

Exceptions are a mechanism for java code to signal extraordinary conditions (such as virtual machine errors like running out of memory, code bugs such as passing a negative number to a method that doesn’t accept negative numbers, alternative ways a method can exit, such as an attempt to read from a network socket aborting because the other end severed the connection, and even intentional control flow such as aborting a thread ASAP). They are similar to signals, if you are familiar with that concept.

Basic mechanism

Exceptions can be thrown anywhere in Java code.
When an exception is thrown, the currently executing line of code is aborted, and the JVM checks if the currently executing line of code is inside of a try block that has an associated catch block that catches the exception (this is known as the exception handler block). If an associated exception handler block exists, execution continues in that block, and the act of throwing the exception is effectively equivalent to break whatever loops you need to break and then go to the exception handler immediately.
If there is no exception handler, the entire method is aborted immediately (the method exits immediately), and the exception ‘bubbles up’ to the next method in the call stack (the method that called the method that just aborted). The process then repeats: Either the caller of the method that threw the exception has an exception handler for it, in which case execution continues there, or it does not, and this method, too, is aborted and now the method that called the method that called the method that threw the exception gets its chance.
This process continues all the way up to the JVM if no code on the call stack has an appropriate exception handler. At the top level, the thread itself will stop execution and echo the exception class name, associated message, stack trace, etc to System.err.

Throwing exceptions

To throw an exception, you use the throw statement, like so:

throw new IllegalArgumentException("foo should be positive");

When throwing an exception, you should usually add a convenient message explaining the situation in plain English, without ending the sentence in a full stop. You don’t have to create a new instance of an exception, but 99+% of the time you throw an exception, you create a new one like in the example. The aim of throwing an exception is usually to abort this method and signal to the caller that the method has finished executing with an unexpected result. In this sense, throw is a lot like return.

Catching exceptions

To catch an exception, you use the try/catch block, like so:

try {
    someMethodThatMightThrowAnException();
} catch (IllegalArgumentException e) {
    // handle the issue here.
}

You can add multiple catch blocks to a single try, each catching a different exception type. If an exception does occur in the body of the try block, the first exception handler whose catch line lists an exception type that matches the thrown exception ‘wins’ and code will resume execution there.
If you’re using Java 1.7 or newer (as you should be), you can also list multiple exception types for a single catch block. For example:

try {
    someMethodThatMightThrowAnException();
} catch (IllegalArgumentException | NullPointerException e) {
    // handle either of those exceptions here.
} catch (IOException e) {
    // And handle I/O issues here.
}

The exception type hierarchy

All things that you can throw or catch must either be the type java.lang.Throwable or a subtype of it. There are 4 major types you should be aware of:

java.lang.Error

A subclass of Throwable, Error is used to communicate serious, generally unrecoverable problems with the hardware or JVM itself: the system is out of memory, the jar file containing your classes is corrupt, or you’ve got a method that endlessly calls itself, and the stack has overflowed.
You should generally never throw any Errors yourself, with the possible exception of InternalError which you can throw if a JVM guarantee fails to hold (for example, the JVM specification guarantees that UTF-8 is an available character encoding. If it isn’t, you can throw InternalError).
You should subclass Error and throw it if an invariant is broken that can only be explained by a corrupt installation or failing hardware, such as when you read a data file via .getResourceAsStream that’s packed along with your code, and it’s not there. That’s as catastrophic as a missing class file, which are also handled with errors.
Error, and all subclasses of Error, are so-called ‘unchecked exceptions’ (see below). You rarely catch them.
Note that some errors may not have reliable stack traces, and some (most notable OutOfMemoryError) may indicate the JVM has become unstable.
The usual strategy to handle an Error is to just let the JVM crash. Most Errors thrown in java applications result in the application crashing entirely, and this is intentional, because the problem cannot be solved.

java.lang.Exception

The only other subclass of Throwable in the java.* namespace, this one indicates a more or less ‘expectable’ problem. Anything from unexpected user input to failing network connections to invalid SQL statements – they are generally handled by throwing some subclass of j.l.Exception. You should never throw Exception itself (always a more suitable subclass, and if no such suitable subclass exists, make your own), and, like j.l.Error, don’t catch it unless you are some sort of app server and you’re the final line of defense against a failing servlet, applet, event handler, etc.

Editor’s Note: This is a Java-centric concept; other JVM-based languages like Scala are far less restrictive in how exceptions are handled. Which approach is better for you depends very much on what you’re comfortable with. Using Java’s more restrictive approach is hardly ever a bad thing, but some other languages’ communities look down on it.

java.lang.RuntimeException

This is a subclass of java.lang.Exception and covers a mix of code bugs and unexpected and generally unfixable problems.
Examples (all mentioned exceptions in this list are subclasses of RuntimeException):

  • Whenever you write x.foo in java, and x is null, the line throws a NullPointerException.
  • Many methods will throw IllegalArgumentException if you for example pass a negative number to a method that expected only positive numbers.
  • If you try to refer to an array element that is outside of the bounds of that array, an ArrayIndexOutOfBoundsException is thrown. For example: int[] x = new int[10]; print(x[11]);

Like Error, RuntimeException and all subclasses of it are so-called ‘unchecked’.
The usual strategy to handle RuntimeExceptions depends on the exception; for exceptions that signify that you programmed a bug, the appropriate resolution is to just crash the application; the stack trace serves as debugging tool. There are also many runtime exceptions that occurr because of invalid user input (the user entered a word where they were supposed to enter a number) or some other problems for which you can write a resolution; you should catch those.

java.lang.Throwable

This gets us back to Throwable itself. You can subclass it, but you should only do that when you use exception as control flow, which is not something you should think about until you have outgrown the need for articles such as these. Until you know a lot better, don’t ever subclass Throwable itself.

Editor’s note, because it bears repeating: You can subclass Throwable, but you should only do that when you use exception as control flow, which is not something you should think about until you have outgrown the need for articles such as these. Until you know a lot better, don’t ever subclass Throwable itself.

Checked vs. Unchecked

As the previous paragraph explained, java.lang.Error and java.lang.RuntimeException, and all subclasses of those two, are known as the ‘unchecked exceptions’. The rest are checked. Those 2 classes are special and listed by name in the Java language specification. You can’t make more unchecked exceptions without subclassing Error, RuntimeException, or some other class that is itself a subclass of one of these two.
Checked exceptions MUST be handled in one of two ways:

  • You wrap the code that is declared to possibly throw one in a try/catch block which catches the checked exception in question, -OR-
  • The method with the code that is declared to possibly throw one, has a ‘throws’ clause that explicitly declares that it throws this exception.

In other words, if you’d like to write throw new IOException("Network connection lost"), then your method signature has to look like: public void myMethod() throws IOException. (IOException is a checked exception). Now code that calls myMethod inherits the requirements to handle it somehow: Either wrap the call to myMethod() in a try/catch block, or also put throws IOException in the method declaration line.
For unchecked exceptions, you have no such requirements. As such, you can throw them without adding that exception to the throws clause. In other words, all methods in all of Java, whether they are specified to do so or not, implicitly act like they have throws Error, RuntimeException tacked onto them. You can add throws NullPointerException to your method declaration if you like; this is meaningless (as NullPointerException is a subtype of RuntimeException and thus unchecked) but some programmers do this as a way to document their code.

Features of Throwable

Stack traces

Whenever any throwable object is created (with new, such as new FileNotFoundException()), the call stack (the chain of method invocations) is stored in the object you just made. A call stack looks something like this:

java.lang.FileNotFoundException: /foo/bar/baz (No such file or directory)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.</init><init>(FileInputStream.java:93)
    at Example.main(Example:8)

The actual exception was thrown (with a throw statement) inside the top listed method. That method was invoked by the constructor of FileInputStream, on line 138, that constructor was invoked by another FileInputStream constructor, and that constructor was invoked by our example code.
You can programatically browse the call stack using the .getStackTrace() method of Throwable. Default handlers of execution frameworks will print them to logs or the screen so you can review the problem.
ADVANCED TOPIC: If you wish to ‘save’ a stack trace (for example, because in a different thread at a later time, a problem will surface that was initially caused by the current invocation, but we don’t know yet if that will happen, so we wish to save the stack trace for now and possibly refer to it much later if failure does eventually happen), just create a new exception without actually throwing it (yet).

Cause

Often the right approach to handling an exception is to restate the problem in your own exception type, and with a more appropriate message. It helps to staple the original cause to this brand new exception. This is called the ’cause’ and can be attached via the initCause() method. Also, most exception types have a constructor that takes the cause if you have one. The cause will be logged/printed along with the new exception.
Make sure you add the appropriate cause if there is one; it really helps debug issues and figure out what went wrong.

Which exception should I throw?

Before we start, be aware that Java has been around for a long time and believes strongly in not changing already released code. As such, you can find many examples of methods throwing the ‘wrong’ kind of exception in java.* and popular libraries. That’s not ‘proof’ that their style is accepted convention or a good idea.

  • A method must list each checked exception that it might possibly throw, and calling code must then handle it somehow (either with a try/catch block, or by adding a throws clause to the method, passing on the burden of handling the issue to all callers of this method). This is very annoying if the method is ever invoked such that the checked exception couldn’t possibly occur (in the sense of: Having to write code that is literally pointless, therefore impossible to test, and aggravating the author of the code calling yours). Therefore, for all problems that can only occur for invalid input or due to conditions entirely under control of the caller, ALWAYS use unchecked exceptions, that is: subtypes of RuntimeException.
  • The type of the exception is the most important tool that you can give to callers of your code to handle the issue. Therefore, the more likely it is that you expect callers of your method to try/catch your exception, the more important it is to throw an appropriate type. Often, this means you should make your own type. Don’t shy away from this; making your own type (writing public class MyOwnException extends Exception {... in MyOwnException.java) is usually a good idea. When in doubt, make your own exceptions.

  • When different issues can come up but they are related in some way, especially if you expect that a caller might just want to handle all these issues in the exact same manner, you should build an exception hierarchy: One overarching type, with various subtypes of that type for more details. For example, java.io.FileNotFoundException is a subtype of java.io.IOException, which is a subtype of java.lang.Exception. If you call any method that may fail due to a file not existing, then FileNotFoundException may occur. You can catch this condition with catch (FileNotFoundException e) {...}, but if you don’t write such a catch block but you do catch all I/O problems with catch (IOException e) {...}, then all file not found problems will also be handled by this more general catch block. This is good API design: You give your caller the flexibility to handle issues as specifically, or as generally, as is appropriate for them. Make hierarchies of exception types if that is applicable.

  • The more likely a problem signaled via an exception can be handled in some appropriate way (other than ‘just crash the application or abort the web request’), the more you should lean towards a checked exception. The more likely a problem cannot be feasibly handled other than by aborting the entire operation, the more you should lean towards an unchecked exception. After all, a checked exception enforces the caller to deal with it, which is just pointless boilerplate if it is highly unlikely that the programmer that calls your method can do anything useful if the problem does occur.

Examples:

  • To signal that the caller provided invalid input arguments, use RuntimeException subtypes. A few common ones already exist:
    • NullPointerException is appropriate if an argument is null and that is not valid input. The message of the exception should be the name of the argument: if (foo == null) throw new NullPointerException("foo");
    • IndexOutOfBoundsException is appropriate when an index is passed that is out of the valid range.
    • IllegalStateException is appropriate if the object is in a state such that this method call isn’t valid at all. For example, a List class has been ‘locked’ and later a caller attempts to add another object to it.
    • UnsupportedOperationException is appropriate if the object is in a state such that the call isn’t valid, and the object has always been, and will always be, in this state. For example, the add method of a list that is designed to be immutable (all objects that are in it are added during its construction and the list will never change) should throw UnsupportedOperationException.
    • Various other types exist, but if you can’t find anything more appropriate, there’s always IllegalArgumentException. Because these are almost always bugs, you don’t need to make a specific subtype (under the rule: “the less likely it is that callers intend to catch it, the less need there is to make a subclass for it”).
  • To signal I/O errors, be it because of disk failure, network connection failure, or something more exotic, like failure to communicate with a device plugged into a serial port, throw java.io.IOException. You rarely throw it yourself though; you use APIs that talk to networks (such as HttpServletResponse, or Socket) and the API will throw the IOException for you.

A few examples where common libraries actually got it wrong:

  • NumberFormatException is thrown by Integer.parseInt(), Long.parseLong(), etcetera; these methods parse text input for a number and return it. The exception is thrown if the text passed to the method is not, in fact, a number. This error is both to be expected (users can make mistakes; if talking to another software product, it might be buggy or on some different version), and often handleable, and yet NumberFormatException is a runtime exception. I/O issues are in fact somewhat less recoverable and somewhat less expectable, and yet those are checked exceptions, which is inconsistent.
  • Various methods in the JDK take a string that represents the character encoding. For example, when turning an array of bytes into a string, you should pass in the name of the text encoding that it used. Certain encoding types such as UTF-8 are guaranteed by the Java virtual machine specification to always be available. Still, this code: new String(someByteArray, "UTF-8") is specced to throw UnsupportedEncodingException, which is a checked exception. That exception couldn’t possibly occur unless your JVM is corrupt (at which point you have far bigger problems). Fortunately this has been ‘solved’ in later versions by way of a method specifically designed for calling with known-valid charset names (new String(someByteArray, StandardCharsets.UTF_8)).

  • @SneakyThrows

    Project Lombok is a compiler plugin that adds the ability to ‘ignore’ the rule that you must either try/catch, or declare that you ‘throws’ a checked exception, by annotating your method with @SneakyThrows(IOException.class) for example. This is particularly useful for working around unwieldy APIs, such as the UnsupportedEncodingException example listed above.

    Editor’s note: I heartily endorse the use of Lombok, whose author contributed this article. I rarely work on any Java projects without it now.

    The ‘default’ exception handler

    Some IDEs and many examples use the following default implementation for an exception handler:

    try {
        // Code that throws some exception
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    This is a really bad default! You miss the message and the type of exception, and, more importantly, the code will just continue running immediately following the catch block. Most likely another exception will occur soon (given that clearly something is wrong, and in addition a bunch of your code, namely everything in the try block from the place where the exception occurred and onwards) also did not run. If that exception is handled similarly, yet another exception will occur soon. You’ll be faced with a cavalcade of stack traces, most of which are complete red herrings. Your app is also now completely broken, as code continues to execute even though your method has failed and its state is most likely no longer valid.
    Don’t do this!
    The single best default exception handler looks like this:

    try {
        // Code that throws some exception
    } catch (IOException e) {
        throw new RuntimeException("XXTODO: Uncaught", e);
    }
    

    The exception text makes no bones about it: You’ve intentionally decided not to worry about this exception right now. Your method will also abort instead of continuing to run in an invalid state, and you can throw RuntimeException without having to add a throws clause.

    How to make your own

    If no exception exists that exactly describes the issue you are attempting to signal to your method’s callers, or it is unchecked when you want it to be checked or vice versa, you have to make your own. Fortunately, it is very easy to do this. For example, to make a new unchecked exception, you would write:

    public class MyException extends RuntimeException {
        public MyException(String msg) {
            super(msg);
        }
        public MyException(String msg, Throwable cause) {
            super(msg, cause);
        }
    }
    

    Use a more descriptive name than MyException, of course. You can extend any existing class. If there’s no existing class that seems to make sense, extend Error, Exception, or RuntimeException depending on what kind of exception you want to create.

    The finally construct

    Sometimes you want code to execute to close resources or bring your object back to a valid state, and you want this code to run even if the main body of your method exits via an exception. The finally construct can help here. It’s part of the try syntax, and looks like:

    try {
        // code that might throw an exception
    } catch (NumberFormatException e) {
        // Runs if NumberFormatException occurs in body.
    } finally {
        // Runs always
    }
    

    In the above example, the code in the finally block is always executed. If the main body (or the catch handler) uses the return statement, your finally code runs right before your method returns. If the try block just gets to the end naturally without an exception occurring, execution jumps to the finally block. If a NumberFormatException occurs, execution jumps to your NumberFormatException catch handler, and after that handler has completed (or if that handler itself throws an exception), your finally block is executed. If some other exception occurs in the try body, then code execution first jumps to your finally block, and once that finishes, the exception is actually ‘thrown’ (execution jumps to the caller and the exception is raised there).
    If your finally block also explicitly exits the method (either via a return statement, or a throw statement), it overrides the explicit method exit that caused your finally block to run. Because that gets very confusing, you should not use return or throw in a finally block.
    A try block needs to have either a catch block or a finally block, or it wouldn’t do anything. However, you don’t need both; just try {} finally {} is valid, for example.
    NB: If a method never exits, for example because it loops endlessly or it has deadlocked, then the finally block would never execute. Also, if the JVM is shut down, either normally (with System.exit(0) for example), or forcibly (killed by the OS, or someone trips over a power cable), your finally blocks don’t run either.

    Automatic Resource Management (‘ARM’)

    Closing resources is a common pattern: Many classes, such as for example java.io.FileInputStream, are specified to require the caller of the constructor to eventually call close() on the stream. Failure to do so means the VM will leak resources, and, eventually, it can’t open any more files and the only solution is to restart the VM. Just calling close() when you are done is not sufficient because exceptions exist: You need to use a finally block. Because this is such a common pattern, there’s an easier way to do it, which is legal Java starting with Java v1.7:

    try (FileInputStream in = new FileInputStream(path)) {
        // code goes here; it can access 'in'
    }
    

    In the above snippet, no matter how execution exits the try block (via return statement, by running to the end of it, or via an exception), the resource will be closed.
    Another option is to use project lombok:

    @lombok.Cleanup FileInputStream in = new FileInputStream(path);
    // code goes here
    

    Here, ‘in’ will be closed when it goes out of scope, for example at the end of the method, no matter how it exits. Lombok’s cleanup works from java v1.6 and up. For more information, see the lombok feature page on @Cleanup.

    How ‘checkedness’ is javac only.

    The concept of checked exceptions are twofold:

    • You cannot catch a checked exception unless at least one thing in the associated try block is declared to throw that exception.
    • If any line is declared to throw a checked exception, then this line must exist either inside of a try block with an associated catch handler for this checked exception, or, the method it is in must have declared this checked exception in its throws line.

    However, these 2 rules are applied only by the Java compiler (javac). The actual JVM treats all exceptions as unchecked; bytecode that throws a checked exception without declaring it does so / catching it, will run just fine, and, at the bytecode level, you can have a catch handler for a checked exception that can’t actually occur and the JVM will run it. This is why other languages that also compile to class files but which don’t have checked exceptions can work at all, and it’s also what makes lombok’s @SneakyThrows tick.