Java Exception Handling Examples in Open Source Projects

In “Effective Java“, Joshua Bloch wrote 9 tips about how to handle exceptions in Java. These tips have become the de facto standard for Java exception handling. In this post, I list some examples of Java exception handling in some open source projects and comment the usage by following the 9 tips of exception handling.

The 9 tips about Java exception handling are:

1. Use exceptions only for exceptional conditions
2. Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
3. Avoid unnecessary use of checked exceptions
4. Favor the use of standard exceptions
5. Throw exceptions appropriate to the abstraction
6. Document all exceptions thrown by each method
7. Include failure-capture information in detail messages
8. Strive for failure atomicity
9. Don't ignore exceptions

1. Use exceptions only for exceptional conditions

This item is mainly about avoiding using exceptions for ordinary control flow.

For example, instead of using exception to terminate a loop control flow:

try{
  Iterator<Foo> iter = ...;
  while(true) {
    Foo foo = i.next();
    ...
  }
} catch (NoSuchElementException e){
}

the regular iteration over a collection should be used:

for(Iterator<Foo> iter = ...; i.hasNext();){
  Foo foo = i.next();
  ...
}

I didn’t find any examples that use exceptions for regular control flow.

2. Use checked exceptions for recoverable conditions and runtime exceptions for programming errors

In most cases, if the caller can recover the exception, then checked exceptions should be used. If not, runtime exception should be used. Runtime exceptions indicates programming errors that can be prevented by checking some preconditions, such as array boundary and nullness checks.

In the following method, IllegalArgumentException is a RuntimeException, whose usage indicates programming errors. Programming errors can often be avoided by checking the preconditions. So this is a bad example based on this tip. The exception can be avoided by checking preconditions, i.e., “hasNext()” method here. (link to source code)

/**
 * Convert a tag string into a tag map.
 *
 * @param tagString a space-delimited string of key-value pairs. For example, {@code "key1=value1 key_n=value_n"}
 * @return a tag {@link Map}
 * @throws IllegalArgumentException if the tag string is corrupted.
 */
public static Map<String, String> parseTags(final String tagString) throws IllegalArgumentException {
    // delimit by whitespace or '='
    Scanner scanner = new Scanner(tagString).useDelimiter("\\s+|=");
 
    Map<String, String> tagMap = new HashMap<String, String>();
    try {
        while (scanner.hasNext()) {
            String tagName = scanner.next();
            String tagValue = scanner.next();
            tagMap.put(tagName, tagValue);
        }
    } catch (NoSuchElementException e) {
        // The tag string is corrupted.
        throw new IllegalArgumentException("Invalid tag string '" + tagString + "'");
    } finally {
        scanner.close();
    }
 
    return tagMap;
}

3. Avoid unnecessary use of checked exceptions

Checked exceptions force callers to deal with the exceptional conditions because compiler will complain if not. Overuse of checked exceptions brings the burden to callers for handling the exceptional conditions. So checked exceptions should be used when necessary. The rule of thumb for using a checked exception is when the exception can not be avoided by checking preconditions and the caller can take some useful actions for handling the exception.

The commonly used runtime exceptions themselves are examples of NOT overuse of checked exceptions. The common runtime exceptions are: ArithmeticException, ClassCastException, IllegalArgumentException, IllegalStateException, IndexOutOfBoundExceptions, NoSuchElementException, and NullPointerException.

In the following method (link to source code), when propertyName is not one of the target cases, there is not much the caller can do, so a runtime exception is thrown.

@Override
public Object get(String propertyName) {
  switch (propertyName.hashCode()) {
    case 842855857:  // marketDataName
      return marketDataName;
    case -1169106440:  // parameterMetadata
      return parameterMetadata;
    case 106006350:  // order
      return order;
    case 575402001:  // currency
      return currency;
    case 564403871:  // sensitivity
      return sensitivity;
    default:
      throw new NoSuchElementException("Unknown property: " + propertyName);
  }
}

4. Favor the use of standard exceptions

The most commonly reused Java exception classes are as follows. You may check out the complete list here.

1. java.io.IOException
2. java.io.FileNotFoundException
3. java.io.UnsupportedEncodingException
4. java.lang.reflect.InvocationTargetException
5. java.security.NoSuchAlgorithmException
6. java.net.MalformedURLException
7. java.text.ParseException
8. java.net.URISyntaxException
9. java.util.concurrent.ExecutionException
10. java.net.UnknownHostException

None of the top 10 is the most commonly used ones shown in the book. But note that these are counted by projects, i.e., if a class is used in a project, it’s counted only once no matter how many methods in the project is using it. So this is by # of projects, but by # of occurrences in code.

5. Throw exceptions appropriate to the abstraction

Exception thrown should have connection to the task the caller performs. This item introduce exception transaltion (catch an exception and throw another) and exception chaining (wrap an exception in a new exception to keep the causal chain of the exception).

private void serializeBillingDetails(BillingResult billingResult,
        BillingDetailsType billingDetails) {
 
    try {
        final JAXBContext context = JAXBContext
                .newInstance(BillingdataType.class);
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty("jaxb.formatted.output", Boolean.FALSE);
        final BillingdataType billingdataType = new BillingdataType();
        billingdataType.getBillingDetails().add(billingDetails);
        marshaller.marshal(factory.createBillingdata(billingdataType), out);
        final String xml = new String(out.toByteArray(), "UTF-8");
        billingResult.setResultXML(xml.substring(
                xml.indexOf("<Billingdata>") + 13,
                xml.indexOf("</Billingdata>")).trim());
        billingResult.setGrossAmount(billingDetails.getOverallCosts()
                .getGrossAmount());
        billingResult.setNetAmount(billingDetails.getOverallCosts()
                .getNetAmount());
    } catch (JAXBException | UnsupportedEncodingException ex) {
        throw new BillingRunFailed(ex);
    }
}

The above method catches JAXBException and UnsupportedEncodingException, and rethrows a new exception that is appropriate to the method’s abstraction level. The new BillingRunFailed exception wraps the original exception. So this is a good example of exception chaining. The benefit of exception chaining is keeping the lower-level exception that is helpful for debugging the problem. (link to source code)

6. Document all exceptions thrown by each method

This is heavily under-used. Most of the public APIs lack of the @throws Java doc to explain the exception being thrown.

Here is a good example. (link to source code)

...
 *
 * @throws MalformedURLException The formal system identifier of a
 * subordinate catalog cannot be turned into a valid URL.
 * @throws IOException Error reading subordinate catalog file.
 */
public String resolveSystem(String systemId)
  throws MalformedURLException, IOException {
...

This is a bad example of lacking the information about in what cases the exception is thrown.

 * @throws Exception exception
 */
public void startServer() throws Exception {
    if (!externalDatabaseHost) {

7. Include failure-capture information in detail messages

private OutputStream openOutputStream(File file) throws IOException {
    if (file.exists()) {
        if (file.isDirectory()) {
            throw new IOException("File '" + file + "' exists but is a directory");
        }
        if (!file.canWrite()) {
            throw new IOException("File '" + file + "' cannot be written to");
        }
    } else {
        final File parent = file.getParentFile();
        if (parent != null) {
            if (!parent.mkdirs() && !parent.isDirectory()) {
                throw new IOException("Directory '" + parent + "' could not be created");
            }
        }
    }
    return new FileOutputStream(file, false);
}

In this method, the IOException uses different string to pass the different failure-capture information.

8. Strive for failure atomicity

Item 8 is about failing. The general rule is that a failed method should not change the state of the objects in the method. In order to fail early, one way is to check parameters for validity before performing the operation. The following is a good example of following this tip.

/**
 * Assigns a new int value to location index of the buffer instance.
 * @param index int
 * @param newValue int
 */
public void modifyEntry(int index, int newValue) {
        if (index < 0 || index > size - 1) {
            throw new IndexOutOfBoundsException();
        }
 
//        ((int[]) bufferArrayList.get((int) (index / pageSize)))[index % pageSize] =
        ((int[]) bufferArrayList.get((index >> exp)))[index & r] =
            newValue;
}

9. Don’t ignore exceptions

public static Bundle decodeUrl(String s) {
    Bundle params = new Bundle();
    if (s != null) {
        String array[] = s.split("&");
        for (String parameter : array) {
            String v[] = parameter.split("=");
            try {
                params.putString(URLDecoder.decode(v[0], "UTF-8"), URLDecoder.decode(v[1], "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    }
    return params;
}

Printing stack traces should be almost always avoided in production code. This is as bad as ignoring the exceptions. This writes to standard error stream, which is not where the log goes to using a logging framework. (link to source code)

You may want to check out top 10 questions about Java exceptions on StackOverflow.

1 thought on “Java Exception Handling Examples in Open Source Projects”

Leave a Comment