Throwing an exception in Java
Exceptions are used to control error flow in a Java program.
Exceptions work as follows:
- At any point where an error condition is detected, you may throw an exception
using the Java throw keyword.
- When an exception is thrown, normal program flow stops and control is passed back to the nearest suitable
surrounding catch block.
- If an exception is thrown back from a method, then that method does not return a value as normal.
- If there is no catch block that can catch the method, then it will eventually be passed to
an uncaught exception handler.
- Exceptions can be checked or unchecked, depending
on their class.
- A method must declare which checked exceptions (if any) it throws, but does not have
to declare unchecked exceptions.
- There are various standard unechecked exceptions that you can use for common conditions (and
which will be thrown by the JVM and platform API methods), including IllegalArgumentException
and NullPointerException.
- There are also various exceptions and errors thrown by the JVM itself
when executing code, allocating memory, decoding class files etc.
How to throw an exception
To throw an exception, we generally use the throw keyword followed by a newly constructed
exception object (exceptions are themselves objects in Java). Most exception constructors will take a String parameter
indicating a diagnostic message. For example, we can check an input parameter to our method and throw an
IllegalArgumentException if it is invalid:
public void calculate(int n) {
if (n > MAX_VALUE) {
throw new IllegalArgumentException("Value too big (" + n + ")");
}
...
}
In complex programs, it is generally good practice to sanity-check arguments and throw exceptions such
as IllegalArgumentException or NullPointerException so that the source of the issue is
obvious.
How to throw a checked exception
We can simply throw an IllegalArgumentException or NullPointerException because if you look at
their definitions, you will see that they extend RuntimeException. RuntimeException and its subclasses
are so-called unchecked exceptions: they can be thrown at any time, without the method that throws them
having to explicitly declare that it may throw them, or without the surrounding code having to explicitly catch them.
But what about "ordinary" checked exceptions— subclasses of Exception but not RuntimeException
(see the exception hierarchy). A common example is IOException. If you
throw one of these checked exceptions, you must either:
- catch it within the method
;
- decalre that the method throws it.
In the following example, we have a method deleteFiles(), which in turn calls Files.delete(). The
Files.delete() method declares that it throws an IOException. Therefore, because we call this method,
we must ourselves declare that our method throws this exception:
public void deleteFiles(Path[] files) throws IOException {
for (Path f : files) {
Files.delete(f);
}
}
If we try to throw a checked exception such as IOException (or call a method that can throw it)
without declaring that our method can throw it, then we will get a compiler error.
Alternatively, we may decide that there is no need to interrupt the process and have the caller deal with a failure
to delete a single file. Instead, we can catch the exception within our method:
public void deleteFiles(Path[] files) {
for (Path f : files) {
try {
Files.delete(f);
} catch (IOException ioex) {
System.err.println("Warning: failed to delete " + f);
}
}
}
Rethrowing an exception
Sometimes, we may want to do both things: catch the exception and then re-throw it to the caller. We can take
the same exception object that we caught, and throw that same exception object:
public void deleteFiles(Path[] files) throws IOException {
for (Path f : files) {
try {
Files.delete(f);
} catch (IOException ioex) {
System.err.println("Warning: failed to delete " + f);
throw ioex;
}
}
}
Declaring that we throw an unchecked exception
We don't have to declare that our method throws an unchecked exception such as IllegalArgumentException.
But occasionally we might want to as a hint to the programmer that it is a common error that a program may need to
deal with in the normal course of its duties.
There are examples of this in the standard Java API libraries.
The Integer.parseInt() method declares that it throws NumberFormatException. Technically speaking,
NubmerFormatException is a subclass of IllegalArgumentException, which we have already said is an unchecked
exception— so the method need not have declared it in its throws clause. But by declaring that
parseInt() throws a NumberFormatException, this forces the programmer to have to consider this
common error condition rather than "forgetting" about it.
Recasting an exception
The fact that the programmer is forced to deal with checked exceptions can be useful in cases where we don't want to
forget to handle an error. But conversely, they can be overly "fussy" in some cases. The reality is that we will sometimes call
methods that declare that they throw exceptions for errors that will basically never occur— or if they did, there would
be little realistically that could be done to recover from the error. For example, when opening a configuration
file that is absolutely required for our program to start up, we are forced to deal with an IOException in
the event of the file not being present or openable. What do we do in that case?
A pattern that we sometimes resort to is "recasting". We can catch the checked exception and throw
an unchecked RuntimeException in its place. The RuntimeException constructor allows us to pass
in the original exception as a 'cause':
public void initApplication() {
try {
readConfig();
} catch (IOException ioex) {
throw new RuntimeException("Fatal error: config file not found", ioex);
}
}
In a simple command-line program with no other outer try/catch block, throwing an uncaught exception like this will
terminate the application. In a more complex multithreaded program, it would pass control back to the thread's
outer exception handler (see uncaught exception handlers).
For more information and examples of recasting, see: recasting exceptions.
Exceptions with lambdas
The technique of recasting is often used when we need to throw a checked exception from within
a lambda expression. Although lambda expressions can throw exceptions,
the standard standard functional interfaces
such as Function do not
declare that they throw any exceptions.
When to catch and when to throw?
When dealing with exceptions, a key question the programmer must ask is: should I catch the exception
'immediately' or throw it back up to the caller. There is no single right or wrong answer to
this: the decision will usually depend on which code is best place to actually deal with the error.
In some cases, an exception deep down in some long running process might indicate a "stop the world" issue
that means the enire process has to be halted. In other cases, it might be a completely ignorable,
or may simply require one item being processed to be marked in error.
For more discussion, see: Exceptions: when to catch and when to throw?.
Some commonly thrown exceptions
There are certain unchecked exceptions that it is common and good practice to throw at the
beginning of a method:
- IllegalArgumentException
- If your method only accepts arguments within a particular range, e.g. only positive
numbers, then you should check for invalid parameters and throw an IllegalArgumentException.
For example:
public int calculateFactorial(int n) {
if (n < 0)
throw new IllegalArgumentException("n must be positive");
if (n >= 60)
throw new IllegalArgumentException("n must be < 60");
...
}
- NullPointerException
- If you know that a parameter to your method cannot be null, then it is
best to explicitly check for null and throw a NullPointerException.
That way, the problem is detected "there and then", rather than at some mysterious
point further down when part of your code tries to access the object in question.
- IllegalStateException
- This one is a little less common, but is useful a method relies on a previous
method having been called. For example, if your object requires its initialise()
method to be called before other methods, then you can set a flag inside initialise(),
and throw an IllegalStateException if initialise() hasn't been called:
private boolean initted;
public void initialise() {
// ...
initted = true;
}
public void doSomething() {
if (!initted)
throw new IllegalStateException("Object not initialised");
}
- UnsupportedOperationException
- This exception is designed for cases where you override an abstract class or
implement an interface,
but don't want or can't to implement certain methods. It is used by various
classes of the Java Collections Framework. Ideally,
your interface or method should also provide a means for the caller to determine in advance
whether it expects the given operation to be supported.
- RuntimeException and InternalError
- As alluded to above, these essentially mean "that should never happen and I don't know what to do if it does", with different degrees
of seriousness. You basically want the application to "bomb out" of whatever it's doing at that
moment. If essentially the application can go on working after the unexpected condition,
throw a RuntimeException. Throw an InternalError in cases where "this is a
very serious unexpected condition": the application is not expected to continue working
and should shut down as soon and safely as possible. These exceptions are
useful with the technique of recasting, where
we turn a normal exception into another that represents a serious condition.
As mentioned above, many other exceptions are regular "checked" exceptions (see the exception
hierarchy for more details), which means that if your method throws it, you will have to
declare it with throws in the method signature:
public void processFile(File f) throws IOException {
if (f.length() > MAX_LENGTH)
throw new IOException("File too big");
...
}
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.