If you’ve ever wanted to understand how failure is represented in source code, you’ve come to the right place. In addition to an overview of Java exceptions, this article gets you started with Java’s language features for throwing objects, trying code that may fail, catching thrown objects, and cleaning up your code after an exception has been thrown.
In this first half of the tutorial, you’ll learn about basic language features and library types. In the second half, we’ll discuss some of the more advanced capabilities.
What you’ll learn in this Java tutorial
- About Java exceptions and their types
- The difference between checked and unchecked exceptions
- Three ways to throw Java exceptions
- How to test for exceptions (try blocks)
- How to catch exceptions (catch blocks)
- How to clean up exceptions (finally blocks)
What are Java exceptions?
Failure occurs when a Java program’s normal behavior is interrupted by unexpected behavior. This divergence is known as an exception. For example, a program tries to open a file to read its contents, but the file doesn’t exist; that’s an exception. From a programming perspective, Java exceptions are library types and language features used to represent and deal with program failure in code. Java classifies exceptions into a few types: checked exceptions, unchecked exceptions, and errors, which must be handled by the JVM. I’ll have more below about how the Java virtual machine handles exceptions.
Checked exceptions
Java classifies exceptions arising from external factors (such as a missing file) as checked exceptions. The Java compiler checks that such exceptions are either handled (corrected) where they occur or documented to be handled elsewhere.
Runtime (unchecked) exceptions
Suppose a program attempts to divide an integer by integer 0. This impossibility illustrates another kind of exception, namely a runtime exception. Unlike checked exceptions, runtime exceptions typically arise from poorly written source code, and should thus be fixed by the programmer. Because the compiler doesn’t check that runtime exceptions are handled or documented to be handled elsewhere, you can think of a runtime exception as an unchecked exception.
Errors
Some exceptions are very serious because they jeopardize a program’s ability to continue execution. For example, a program tries to allocate memory from the JVM but there isn’t enough free memory to satisfy the request. Another serious situation occurs when a program tries to load a classfile via a Class.forName()
method call, but the classfile is corrupt. This kind of exception is known as an error. You should never try to handle errors yourself because the JVM might not be able to recover from it.
Exceptions in source code
An exception may be represented in source code as an error code or as an object. I’ll introduce both and show you why objects are superior.
Error codes versus objects
Programming languages such as C use integer-based error codes to represent failure and reasons for failure—i.e., exceptions. Here are a couple of examples:
if (chdir("C:temp"))
printf("Unable to change to temp directory: %dn", errno);
FILE *fp = fopen("C:tempfoo");
if (fp == NULL)
printf("Unable to open foo: %dn", errno);
C’s chdir()
(change directory) function returns an integer: 0 on success or -1 on failure. Similarly, C’s fopen()
(file open) function returns a nonnull pointer (integer address) to a FILE
structure on success or a null (0) pointer (represented by constant NULL
) on failure. In either case, to identify the exception that caused the failure, you must read the global errno
variable’s integer-based error code.
Error codes present some problems:
- Integers are meaningless; they don’t describe the exceptions they represent. For example, what does 6 mean?
- Associating context with an error code is awkward. For example, you might want to output the name of the file that couldn’t be opened, but where are you going to store the file’s name?
- Integers are arbitrary, which can lead to confusion when reading source code. For example, specifying
if (!chdir("C:temp"))
(!
signifies NOT) instead ofif (chdir("C:temp"))
to test for failure is clearer. However, 0 was chosen to indicate success, and soif
must be specified to test for failure.
(chdir("C:temp")) - Error codes are too easy to ignore, which can lead to buggy code. For example, the programmer could specify
chdir("C:temp");
and ignore theif (fp == NULL)
check. Furthermore, the programmer need not examineerrno
. By not testing for failure, the program behaves erratically when either function returns a failure indicator.
To solve these problems, Java embraced a new approach to exception handling. In Java, we combine objects that describe exceptions with a mechanism based on throwing and catching these objects. Here are some advantages of using objects versus error code to denote exceptions:
- An object can be created from a class with a meaningful name. For example,
FileNotFoundException
(in thejava.io
package) is more meaningful than 6. - Objects can store context in various fields. For example, you can store a message, the name of the file that could not be opened, the most recent position where a parse operation failed, and/or other items in an object’s fields.
- You don’t use
if
statements to test for failure. Instead, exception objects are thrown to a handler that’s separate from the program code. As a result, the source code is easier to read and less likely to be buggy.
Throwable and its subclasses
Java provides a hierarchy of classes that represent different kinds of exceptions. These classes are rooted in the java.lang
package’s Throwable
class, along with its Exception
, RuntimeException
, and Error
subclasses.
Throwable
is the ultimate superclass where exceptions are concerned. Only objects created from Throwable
and its subclasses can be thrown (and subsequently caught). Such objects are known as throwables.
A Throwable
object is associated with a detail message that describes an exception. Several constructors, including the pair described below, are provided to create a Throwable
object with or without a detail message:
- Throwable() creates a
Throwable
with no detail message. This constructor is appropriate for situations where there is no context. For example, you only want to know that a stack is empty or full. - Throwable(String message) creates a
Throwable
withmessage
as the detail message. This message can be output to the user and/or logged.
Throwable
provides the String getMessage()
method to return the detail message. It also provides additional useful methods, which I’ll introduce later.
The Exception class
Throwable
has two direct subclasses. Exception
is a Throwable
subclass that describes an exception arising from an external factor such as attempting to read from a nonexistent file. Exception
declares the same constructors (with identical parameter lists) as Throwable
, and each constructor invokes its Throwable
counterpart. Exception
inherits Throwable
‘s methods; it declares no new methods.
Java provides many exception classes that directly subclass Exception
. Here are three examples:
- CloneNotSupportedException signals an attempt to clone an object whose class doesn’t implement the
Cloneable
interface. Both types are in thejava.lang
package. - IOException signals that some kind of I/O failure has occurred. This type is located in the
java.io
package. - ParseException signals that a failure has occurred while parsing text. This type can be found in the
java.text
package.
Notice that each Exception
subclass name ends with the word Exception
. This convention makes it easy to identify the class’s purpose.
You’ll typically subclass Exception
(or one of its subclasses) with your own exception classes (whose names should end with Exception
). Here are a couple of custom subclass examples:
public class StackFullException extends Exception
{
}
public class EmptyDirectoryException extends Exception
{
private String directoryName;
public EmptyDirectoryException(String message, String directoryName)
{
super(message);
this.directoryName = directoryName;
}
public String getDirectoryName()
{
return directoryName;
}
}
The first example describes an exception class that doesn’t require a detail message. It’s default noargument constructor invokes Exception()
, which invokes Throwable()
.
The second example describes an exception class whose constructor requires a detail message and the name of the empty directory. The constructor invokes Exception(String message)
, which invokes Throwable(String message)
.
Objects instantiated from Exception
or one of its subclasses (except for RuntimeException
or one of its subclasses) are checked exceptions.
The RuntimeException class
Exception
is directly subclassed by RuntimeException
, which describes an exception most likely arising from poorly written code. RuntimeException
declares the same constructors (with identical parameter lists) as Exception
, and each constructor invokes its Exception
counterpart. RuntimeException
inherits Throwable
‘s methods. It declares no new methods.
Java provides many exception classes that directly subclass RuntimeException
. The following examples are all members of the java.lang
package:
- ArithmeticException signals an illegal arithmetic operation, such as attempting to divide an integer by 0.
- IllegalArgumentException signals that an illegal or inappropriate argument has been passed to a method.
- NullPointerException signals an attempt to invoke a method or access an instance field via the null reference.
Objects instantiated from RuntimeException
or one of its subclasses are unchecked exceptions.
The Error class
Throwable
‘s other direct subclass is Error
, which describes a serious (even abnormal) problem that a reasonable application should not try to handle—such as running out of memory, overflowing the JVM’s stack, or attempting to load a class that cannot be found. Like Exception
, Error
declares identical constructors to Throwable
, inherits Throwable
‘s methods, and doesn’t declare any of its own methods.
You can identify Error
subclasses from the convention that their class names end with Error
. Examples include OutOfMemoryError
, LinkageError
, and StackOverflowError
. All three types belong to the java.lang
package.
Throwing exceptions
A C library function notifies calling code of an exception by setting the global errno
variable to an error code and returning a failure code. In contrast, a Java method throws an object. Knowing how and when to throw exceptions is an essential aspect of effective Java programming. Throwing an exception involves two basic steps:
- Use the
throw
statement to throw an exception object. - Use the
throws
clause to inform the compiler.
Later sections will focus on catching exceptions and cleaning up after them, but first let’s learn more about throwables.
The throw statement
Java provides the throw
statement to throw an object that describes an exception. Here’s the syntax of the throw
statement:
throw throwable;
The object identified by throwable
is an instance of Throwable
or any of its subclasses. However, you usually only throw objects instantiated from subclasses of Exception
or RuntimeException
. Here are a couple of examples:
throw new FileNotFoundException("unable to find file " + filename);
throw new IllegalArgumentException("argument passed to count is less than zero");
The throwable is thrown from the current method to the JVM, which checks this method for a suitable handler. If not found, the JVM unwinds the method-call stack, looking for the closest calling method that can handle the exception described by the throwable. If it finds this method, it passes the throwable to the method’s handler, whose code is executed to handle the exception. If no method is found to handle the exception, the JVM terminates with a suitable message.
The throws clause
You need to inform the compiler when you throw a checked exception out of a method. Do this by appending a throws
clause to the method’s header. This clause has the following syntax:
throws checkedExceptionClassName (, checkedExceptionClassName)*
A throws
clause consists of keyword throws
followed by a comma-separated list of the class names of checked exceptions thrown out of the method. Here is an example:
public static void main(String[] args) throws ClassNotFoundException
{
if (args.length != 1)
{
System.err.println("usage: java ... classfile");
return;
}
Class.forName(args[0]);
}
This example attempts to load a classfile identified by a command-line argument. If Class.forName()
cannot find the classfile, it throws a java.lang.ClassNotFoundException
object, which is a checked exception.
It’s necessary to inform the compiler that a checked ClassNotFoundException
is being thrown by attaching a throws ClassNotFoundException
clause to the invoking main()
method’s header. After all, the exception isn’t handled in this method. When the exception is thrown to the JVM, it will note this. In this case, because there is no parent method of main()
, it will terminate with a message.
You’ll see additional examples of throws
later on. For now, keep these rules in mind for working with throws
clauses:
- If at all possible, don’t include the names of unchecked exception classes (such as
ArithmeticException
) in athrows
clause. These names don’t need to be included becausethrows
clauses are for checked exceptions only. Including unchecked class names only clutters the source code. - You can append a
throws
clause to a constructor and throw a checked exception from the constructor when something goes wrong while the constructor is executing. The resulting object will not be created. - If a superclass method declares a
throws
clause, the overriding subclass method doesn’t have to declare athrows
clause. However, if the subclass method declares athrows
clause, the clause must not include the names of checked exception classes that are not also included in the superclass method’sthrows
clause-—unless they are the names of exception subclasses. For example, given superclass methodvoid open(String name) throws IOException {}
, the overriding subclass method could be declared asvoid open(String name) {}
,void open(String name) throws IOException {}
, orvoid open(String name) throws FileNotFoundException {}
—noting thatFileNotFoundException
subclassesIOException
. However, you couldn’t specify, in the subclass,void open(String name) throws ClassNotFoundException
, becauseClassNotFoundException
doesn’t appear in the superclass’sthrows
clause. - A checked exception class name doesn’t need to appear in a
throws
clause when the name of its superclass appears. For example, you don’t need to specifythrows FileNotFoundException, IOException
. Onlythrows IOException
is necessary. - The compiler reports an error when a method throws a checked exception and doesn’t also handle the exception or list the exception in its
throws
clause. - You can declare a checked exception class name in a method’s
throws
clause without throwing an instance of this class from the method. (Perhaps the method has yet to be fully coded.) However, Java requires that you provide code to handle this exception, even though it isn’t thrown.
Using try blocks to test for exceptions
Java provides the try
block to delimit a sequence of statements that may throw exceptions. A try
block has the following syntax:
try
{
// one or more statements that might throw exceptions
}
The statements in a try
block serve a common purpose and might directly or indirectly throw an exception. Consider the following example:
FileInputStream fis = null;
FileOutputStream fos = null;
try
{
fis = new FileInputStream(args[0]);
fos = new FileOutputStream(args[1]);
int c;
while ((c = fis.read()) != -1)
fos.write(c);
}
This example excerpts a larger Java Copy
application that copies a source file to a destination file. It uses the java.io
package’s FileInputStream
and FileOutputStream
classes (introduced later in the article) for this purpose. Think of FileInputStream
as a way to read an input stream of bytes from a file, and FileOutputStream
as a way to write an output stream of bytes to a file.
The FileInputStream(String filename)
constructor creates an input stream to the file identified by filename
. This constructor throws FileNotFoundException
when the file doesn’t exist, refers to a directory, or another related problem occurs. The FileOutputStream(String filename)
constructor creates an output stream to the file identified by filename
. It throws FileNotFoundException
when the file exists but refers to a directory, doesn’t exist and cannot be created, or another related problem occurs.
FileInputStream
provides an int read()
method to read one byte and return it as a 32-bit integer. This method returns -1 on end-of-file. FileOutputStream
provides a void write(int b)
method to write the byte in the lower 8 bits of b
. Either method throws IOException
when something goes wrong.
The bulk of the example is a while
loop that repeatedly read()
s the next byte from the input stream and write()
s that byte to the output stream, until read()
signals end-of-file.
The try
block’s file-copy logic is easy to follow because this logic isn’t combined with exception-checking code (if
tests and related throw
statements hidden in the constructors and methods), exception-handling code (which is executed in one or more associated catch
blocks), and cleanup code (for closing the source and destination files; this code is relegated to an associated finally
block). In contrast, C’s lack of a similar exception-oriented framework results in more verbose code, as illustrated by the following excerpt from a larger C cp
application (in this article’s code archive) that copies a source file to a destination file:
if ((fpsrc = fopen(argv[1], "rb")) == NULL)
{
fprintf(stderr, "unable to open %s for readingn", argv[1]);
return;
}
if ((fpdst = fopen(argv[2], "wb")) == NULL)
{
fprintf(stderr, "unable to open %s for writingn", argv[1]);
fclose(fpsrc);
return;
}
while ((c = fgetc(fpsrc)) != EOF)
if (fputc(c, fpdst) == EOF)
{
fprintf(stderr, "unable to write to %sn", argv[1]);
break;
}
In this example, the file-copy logic is harder to follow because the logic is intermixed with exception-checking, exception-handling, and cleanup code:
- The two
== NULL
and one== EOF
checks are the equivalent of the hiddenthrow
statements and related checks. - The three
fprintf()
function calls are the exception-handling code whose Java equivalent would be executed in one or morecatch
blocks. - The
fclose(fpsrc);
function call is cleanup code whose Java equivalent would be executed in afinally
block.
Using catch blocks to catch exceptions
Java’s exception-handling capability is based on catch
blocks. This section introduces catch
and various catch
blocks.
The catch block
Java provides the catch
block to delimit a sequence of statements that handle an exception. A catch
block has the following syntax:
catch (throwableType throwableObject)
{
// one or more statements that handle an exception
}
The catch
block is similar to a constructor in that it has a parameter list. However, this list consists of only one parameter, which is a throwable type (Throwable
or one of its subclasses) followed by an identifier for an object of that type.
When an exception occurs, a throwable is created and thrown to the JVM, which searches for the closest catch
block whose parameter type directly matches or is the supertype of the thrown throwable object. When it finds this block, the JVM passes the throwable to the parameter and executes the catch
block’s statements, which can interrogate the passed throwable and otherwise handle the exception. Consider the following example:
catch (FileNotFoundException fnfe)
{
System.err.println(fnfe.getMessage());
}
This example (which extends the previous try
block example) describes a catch
block that catches and handles throwables of type FileNotFoundException
. Only throwables matching this type or a subtype are caught by this block.
Suppose the FileInputStream(String filename)
constructor throws FileNotFoundException
. The JVM checks the catch
block following try
to see if its parameter type matches the throwable type. Detecting a match, the JVM passes the throwable’s reference to fnfe
and transfers execution to the block. The block responds by invoking getMessage()
to retrieve the exception’s message, which it then outputs.
Specifying multiple catch blocks
You can specify multiple catch
blocks after a try
block. For example, consider this larger excerpt from the aforementioned Copy
application:
FileInputStream fis = null;
FileOutputStream fos = null;
{
fis = new FileInputStream(args[0]);
fos = new FileOutputStream(args[1]);
int c;
while ((c = fis.read()) != -1)
fos.write(c);
}
catch (FileNotFoundException fnfe)
{
System.err.println(fnfe.getMessage());
}
catch (IOException ioe)
{
System.err.println("I/O error: " + ioe.getMessage());
}
The first catch
block handles FileNotFoundException
s thrown from either constructor. The second catch
block handles IOException
s thrown from the read()
and write()
methods.
When specifying multiple catch
blocks, don’t specify a catch
block with a supertype before a catch
block with a subtype. For example, don’t place catch (IOException ioe)
before catch (FileNotFoundException fnfe)
. If you do, the compiler will report an error because catch
would also handle
(IOException ioe)FileNotFoundException
s, and catch (FileNotFoundException
would never have a chance to execute.
fnfe)
Likewide, don’t specify multiple catch
blocks with the same throwable type. For example, don’t specify two catch (IOException ioe) {}
blocks. Otherwise, the compiler reports an error.
Using finally blocks to clean up exceptions
Whether or not an exception is handled, you may need to perform cleanup tasks, such as closing an open file. Java provides the finally
block for this purpose.
The finally
block consists of keyword finally
followed by a brace-delimited sequence of statements to execute. It may appear after the final catch
block or after the try
block.
Cleaning up in a try-catch-finally context
When resources must be cleaned up and an exception isn’t being thrown out of a method, a finally
block is placed after the final catch
block. This is demonstrated by the following Copy
excerpt:
FileInputStream fis = null;
FileOutputStream fos = null;
try
{
fis = new FileInputStream(args[0]);
fos = new FileOutputStream(args[1]);
int c;
while ((c = fis.read()) != -1)
fos.write(c);
}
catch (FileNotFoundException fnfe)
{
System.err.println(fnfe.getMessage());
}
catch (IOException ioe)
{
System.err.println("I/O error: " + ioe.getMessage());
}
finally
{
if (fis != null)
try
{
fis.close();
}
catch (IOException ioe)
{
// ignore exception
}
if (fos != null)
try
{
fos.close();
}
catch (IOException ioe)
{
// ignore exception
}
}
If the try
block executes without an exception, execution passes to the finally
block to close the file input/output streams. If an exception is thrown, the finally
block executes after the appropriate catch
block.
FileInputStream
and FileOutputStream
inherit a void close()
method that throws IOException
when the stream cannot be closed. For this reason, I’ve wrapped each of fis.close();
and fos.close();
in a try
block. I’ve left the associated catch
block empty to illustrate the common mistake of ignoring an exception.
An empty catch
block that’s invoked with the appropriate throwable has no way to report the exception. You might waste a lot of time tracking down the exception’s cause, only to discover that you could have detected it sooner if the empty catch
block had reported the exception, even if only in a log.
Cleaning up in a try-finally context
When resources must be cleaned up and an exception is being thrown out of a method, a finally
block is placed after the try
block: there are no catch
blocks. Consider the following excerpt from a second version of the Copy
application:
public static void main(String[] args)
{
if (args.length != 2)
{
System.err.println("usage: java Copy srcfile dstfile");
return;
}
try
{
copy(args[0], args[1]);
}
catch (IOException ioe)
{
System.err.println("I/O error: " + ioe.getMessage());
}
}
static void copy(String srcFile, String dstFile) throws IOException
{
FileInputStream fis = null;
FileOutputStream fos = null;
try
{
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(dstFile);
int c;
while ((c = fis.read()) != -1)
fos.write(c);
}
finally
{
if (fis != null)
try
{
fis.close();
}
catch (IOException ioe)
{
System.err.println(ioe.getMessage());
}
if (fos != null)
try
{
fos.close();
}
catch (IOException ioe)
{
System.err.println(ioe.getMessage());
}
}
}
The file-copying logic has been moved into a copy()
method. This method is designed to report an exception to the caller, but it first closes each open file.
This method’s throws
clause only lists IOException
. It isn’t necessary to include FileNotFoundException
because FileNotFoundException
subclasses IOException
.
Once again, the finally
clause presents a lot of code just to close two files. In the second part of this series, you will learn about the try-with-resources
statement, which obviates the need to explicitly close these files.
In conclusion
This article introduced you to the basics of Java’s traditional exception-oriented framework, but there is much more to grasp. The second half of this tutorial introduces Java’s more advanced exception-oriented language features and library types, including try-with-resources
.