Throwing an exception in Java

Exceptions are used to control error flow in a Java program. Exceptions work as follows:

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:

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.