Java Exception 101

 

Overview

The most ‘fantastic’ usage of exception in Java I saw around 3 years ago is, a guy throws Exception in an inner loop in order to exit the outer loop.

Exception is fundamental knowledge in Java. However, someone analyzed half a million Java projects in GitHub, the result shows the usage is not good.[1]

What do developers do in exception catch blocks?

Kinds of Exceptions

Exceptions in Java Language Specification describes kinds of exceptions. See the following figure.

Exceptions in JLS

  1. Exception is the superclass of all the exceptions from which ordinary programs may wish to recover.
  2. Error is the superclass of all the exceptions from which ordinary programs are not ordinarily expected to recover. For instance, OutOfMemoryError.
  3. Checked Exception must be handled, while Unchecked Exception is not.

For example, when we invoke a method which may throw Checked Exception, we must surround with try/catch or add throw declaration.

try {
  // some routines which may throw CheckedException
} catch (CheckedException ex) {
  // must do something here
}
public void invoker() throws CheckedException {
  // some routines which may throw CheckedException
}

RuntimeException and Checked Exception

There are debates for RuntimeException and checked Exception, for example Should class IOException in Java have been an unchecked RuntimeException?. The debate is going on.

Previously, I thought RuntimeException usually indicates the logical bug in our source code. I found Exceptions in Java Language Specification explains how designers of the Java programming language thought.

The Java programming language requires that a program contains handlers for checked exceptions which can result from execution of a method or constructor.

This compile-time checking for the presence of exception handlers is designed to reduce the number of exceptions which are not properly handled.

Simply speaking, exceptions in run-time which could not be checked at compile-time are designed to be RuntimeException. See details in the Exceptions in Java Language Specification.

Exception 101

DON’T Swallow Exception

DON'T Swallow Exception!

DON'T Swallow Exception!

DON'T Swallow Exception!

This is extremely important. When the system goes online, finding swallowed exception is harder than looking for a needle in the haystack.

If you are not sure how to handle the exception, re-throw it.

Write Informative and Insensitive Exception Message, and Keep the Cause

// Bad Case
try {
  // some routines which may throw IOException
} catch (IOException ex) {
  throw new MyException("Unknown issue.");
}
// Bad Case, user object might include sensitive information.
try {
  // some routines which may throw UserNotFoundException
} catch (UserNotFoundException ex) {
  throw new MyException("Can not find the user: " + user.toString(), ex);
}
// Good Case
try {
  // some routines which may throw IOException
} catch (IOException ex) {
  throw new MyException("Informative message here.", ex);
}

Throw/Catch Specific Exception Instead of Its Superclass

Don’t Throw/Catch Exception and Throwable. (Definitely there are exceptional cases)

// Bad Case
public void useSpecificException() throws Exception {
  // some routines which may throw IOException and TimeoutException ;
}
// Good Case
public void useSpecificException() throws IOException, TimeoutException { 
  // some routines which may throw IOException and TimeoutException ;
}
// Bad Case, as the Exception might be RuntimeException, which should have other ways to handle.
public void useSpecificTryCatch() { 
  try {
    // some routines which may throw IOException and TimeoutException ;
  } catch (Exception ex) {
    // Do something here for Exception;
  } 
}
// Good Case. It is a bit controversial. In many real cases, developers just log each exception, therefore 
//     `try {} catch (IOException | TimeoutException ex) {}`
// might be used.
public void useSpecificTryCatch() { 
  try {
    // some routines which may throw IOException and TimeoutException ;
  } catch (IOException ex) {
    // Do something here for IOException;
  } catch (TimeoutException ex) {
    // Do something else here for TimeoutException;
  }
}

Log Only When Exception is Handled

// Bad Case, the same exception might be logged many times (log here and the outer invokers),
// which messes up the log and monitoring tool.
try {
  // some routines which may throw CheckedException
} catch (CheckedException ex) {
  LOGGER.error("Informative message in log", ex);
  throw ex;
}
// Good Case
try {
  // some routines which may throw CheckedException
} catch (CheckedException ex) {
  LOGGER.error("Informative message in log", ex);
  // Do something to recover if needed. 
}

Release Resources Finally

From Java 7, we should use try-with-resources for InputStream etc.

try (InputStream is = new FileInputStream("file")) {
  is.read();
}

Prior to Java 7, or for those resources which do not implement java.lang.AutoCloseable, remember to release resources in finally block. IOUtils.closeQuietly in Apache IOUtils is used by many developers prior to Java 7, but it swallows IOException which might be harmful in many cases especially closing a writer. It’s also discussed in Google guava, 95% of all usage of Closeable.closeQuietly is broken.

InputStream is = null;
try {
  is = new FileInputStream("file");
  is.read();
} finally {
  if (is != null) {
    is.close();
  }
}

Next

Some notorious exceptions (NullPointerException, OutOfMemoryError etc.) and corresponding mitigation will be described.

Reference

[1] Swallowed Exceptions: The Silent Killer of Java Applications

[2] Exceptions in Java Language Specification

[3] Should class IOException in Java have been an unchecked RuntimeException?

[4] The try-with-resources Statement

[5] IOUtils.closeQuietly in Apache IOUtils

[6] 95% of all usage of Closeable.closeQuietly is broken