Throwing an exception and Advanced features of Exceptions


Throwing an exception

The following example shows the basics of throwing an exception:

public void checkNumber(int number) throws IllegalArgumentException {
    if (number < 0) {
          throw new IllegalArgumentException("Number must be positive: " + number);
     }
}

The exception is thrown on the 3rd line. This statement can be broken down into two parts:

  • new IllegalArgumentException(...) is creating an instance of the IllegalArgumentException class, with a message that describes the error that exception is reporting.
  • throw ... is then throwing the exception object.

When the exception is thrown, it causes the enclosing statements to terminate abnormally until the exception is handled. This is described in other examples.

It is good practice to both create and throw the exception object in a single statement, as shown above. It is also good practice to include a meaningful error message in the exception to help the programmer to understand the cause of the problem. However, this is not necessarily the message that you should be showing to the end user. (For a start, Java has no direct support for internationalizing exception messages.)

There are a couple more points to be made:

  • We have declared the checkNumber as throws IllegalArgumentException. This was not strictly necessary, since IllegalArgumentException is a checked exception; see The Java Exception Hierarchy - Unchecked and Checked Exceptions. However, it is good practice to do this, and also to include the exceptions thrown a method's javadoc comments.
  • Code immediately after a throw statement is unreachable. Hence if we wrote this:
  • throw new IllegalArgumentException("it is bad");
    return;
     	
    the compiler would report a compilation error for the return statement

Exception chaining

Many standard exceptions have a constructor with a second cause argument in addition to the conventional message argument. The cause allows you to chain exceptions. Here is an example.

First we define an unchecked exception that our application is going throw when it encounters a non-recoverable error. Note that we have included a constructor that accepts a cause argument.

public class AppErrorException extends RuntimeException {
	public AppErrorException() {
		super();
	}
 
	public AppErrorException(String message) {
		super(message);
	}
 
	public AppErrorException(String message, Throwable cause) {
		super(message, cause);
	}
}
 
Next, here is some code that illustrates exception chaining.
 
public String readFirstLine(String file) throws AppErrorException {
	try (Reader r = new BufferedReader(new FileReader(file))) {
		String line = r.readLine();
		if (line != null) {
			return line;
		} else {
			throw new AppErrorException("File is empty: " + file);
		}
	} catch (IOException ex) {
		throw new AppErrorException("Cannot read file: " + file, ex);
	}
}

The throw within the try block detects a problem and reports it via an exception with a simple message. By contrast, the throw within the catch block is handling the IOException by wrapping it in a new (checked) exception. However, it is not throwing away the original exception. By passing the IOException as the cause, we record it so that it can be printed in the stacktrace, as explained in Creating and reading stacktraces.


Advanced features of Exceptions

This example covers some advanced features and use-cases for Exceptions.

Examining the callstack programmatically

Version ≥ Java SE 1.4

The primary use of exception stacktraces is to provide information about an application error and its context so that the programmer can diagnose and fix the problem. Sometimes it can be used for other things. For example, a SecurityManager class may need to examine the call stack to decide whether the code that is making a call should be trusted.

You can use exceptions to examine the call stack programmatically as follows:

Exception ex = new Exception(); // this captures the call stack
StackTraceElement[] frames = ex.getStackTrace();
System.out.println("This method is " + frames[0].getMethodName());
System.out.println("Called from method " + frames[1].getMethodName());

There are some important caveats on this:

  • The information available in a StackTraceElement is limited. There is no more information available than is displayed by printStackTrace. (The values of the local variables in the frame are not available.)
  • The javadocs for getStackTrace() state that a JVM is permitted to leave out frames:
    • Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this throwable is permitted to return a zero-length array from this method.

Optimizing exception construction

As mentioned elsewhere, constructing an exception is rather expensive because it entails capturing and recording information about all stack frames on the current thread. Sometimes, we know that that information is never going to be used for a given exception; e.g. the stacktrace will never be printed. In that case, there is an implementation trick that we can use in a custom exception to cause the information to not be captured.

The stack frame information needed for stacktraces, is captured when the Throwable constructors call the Throwable.fillInStackTrace() method. This method is public, which means that a subclass can override it. The trick is to override the method inherited from Throwable with one that does nothing; e.g.

public class MyException extends Exception {
    // constructors
    @Override
    public void fillInStackTrace() {
          // do nothing
    }
}

The problem with this approach is that an exception that overrides fillInStackTrace() can never capture the stacktrace, and is useless in scenarios where you need one.

Erasing or replacing the stacktrace

Version ≥ Java SE 1.4

In some situations, the stacktrace for an exception created in the normal way contains either incorrect information, or information that the developer does not want to reveal to the user. For these scenarios, the Throwable.setStackTrace can be used to replace the array of StackTraceElement objects that holds the information.

For example, the following can be used to discard an exception's stack information:

exception.setStackTrace(new StackTraceElement[0]);

Suppressed exceptions

Version ≥ Java SE 7

Java 7 introduced the try-with-resources construct, and the associated concept of exception suppression. Consider the following snippet:

exception.setStackTrace(new StackTraceElemeatry (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
    // do stuff
    int temp = 0 / 0; // throws an ArithmeticException
}

When the exception is thrown, the try will call close() on the w which will flush any buffered output and then close the FileWriter. But what happens if an IOException is thrown while flushing the output?

What happens is that any exception that is thrown while cleaning up a resource is suppressed. The exception is caught, and added to the primary exception's suppressed exception list. Next the try-with-resources will continue with the cleanup of the other resources. Finally, primary exception will be rethrown.

A similar pattern occurs if an exception it thrown during the resource initialization, or if the try block completes normally. The first exception thrown becomes the primary exception, and subsequent ones arising from cleanup are suppressed.

The suppressed exceptions can be retrieved from the primary exception object by calling getSuppressedExceptions.

Basic Programs