Input stream error handling in Java

On the previous page, we saw how to open an input stream in Java and read sequential bytes from it. But we didn't look properly at error handling.

Many I/O-related methods in Java are declared as throwing IOException. This is a checked exception (i.e. one that we must explicitly deal with), because getting an error while reading from a file, network connection etc is generally a foreseeable event. (A disadvantage is that we must also spuriously catch this exception in cases where we basically know it will never be thrown, e.g. when reading from a ByteArrayInputStream.)

If we are defining a utility method to perform a particular operation on a file, the most common idiom is to throw IOException up: the caller probably wants to know that there was a problem. In this example, we test to see if a particular file is a JPEG file by seeing if the first four bytes are the letters "JFIF":

public boolean isJpegFile(File f) throws IOException {
  InputStream in = new FileInputStream(f);
  boolean ret = (in.read() == 'J' &&
                 in.read() == 'F' &&
                 in.read() == 'I' &&
                 in.read() == 'F');
  in.close();
  return ret;
}

Throwing IOException also makes the code a little neater as we'll see, because of what happens when we come to close the file.

Closing the stream in a finally clause

In the isJpegFile() method above, a problem occurs if an I/O error occurs while reading the file: we won't call close() on the stream. This wouldn't be a total disaster: input streams are generally defined to close themselves in their finalize methods. But it would certainly delay closing and the point at which the stream was closed would be unpredictable.

We can solve this problem by putting the close() call in a finally clause:

public boolean isJpegFile(File f) throws IOException {
  InputStream in = null;
  try {
    in = new FileInputStream(f);
    return (in.read() == 'J' &&
            in.read() == 'F' &&
            in.read() == 'I' &&
            in.read() == 'F');
  } finally {
    if (in != null)
      in.close();
  }
}

What isn't obvious in this code is that close() itself is declared to throw IOException. So if we don't throw IOException up from our method, we actually end up with two try/catch blocks:

public Boolean isJpegFile(File f) {
  InputStream in = null;
  try {
    in = new FileInputStream(f);
    return (in.read() == 'J' &&
            in.read() == 'F' &&
            in.read() == 'I' &&
            in.read() == 'F');
  } catch (IOException ioe) {
    // do some logging/diagnostic operation
    return null;
  } finally {
    if (in != null) {
      try {
        in.close();
      } catch (IOException ioe) {}
    }
  }
}

Notice that I've subtly changed boolean to the Boolean object variant: if we really wanted to not throw the exception, we might want to notify the caller of the error by returning a special "error status value", in this case null. (This is purely an example: it's generally preferrable to throw the exception than use spurious return values as error values.) To make this code slightly less messy, we can remove the check for in being null and simply catch Exception (which would be a NullPointerException if in was null, or else an IOException in the rare case of an error while closing):

public Boolean isJpegFile(File f) {
  InputStream in = null;
  try {
    ...
  } catch (IOException ioe) {
    ...
  } finally {
    try { in.close(); } catch (Exception e) {}
  }
}

However, the preferrable option is still often to throw IOException up from this kind of utility method. And as a bonus, this generally gives the neatest "mop-up" code since we don't need the extra try/catch inside the finally. However, we may need the above syntax if this isn't a utility method but is rather a "main routine" in our program that needs to deal with errors there and then because there's nobody "further up the stack" to pass the exception to.

When would InputStream.close() throw an exception?

You may at this point be wondering when closing an input stream would actually cause an error to be raised. In the case of closing a file, probably never in practice. (Conceivably, a file system could throw an error while updating "last access time" metadata, or some other metadata, on a file during closure.)

However, in the case of an input stream reading from a network connection, an error on closure is a bit more conceivable. A normal closure of a network socket actually involves a closure request (TCP/IP FIN packet) being sent over the connection and waiting for the other end to acknowledge this closure request. (In fact, the other end of the connection then in turn sends a closure request, which the closing end acknowledges.) So in the case of a socket input stream, a closure operation actually involves sending traffic over the connection and the closure can thus fail with an error.

Note that in many implementations, close() generally doesn't throw an IOException if the stream is already closed; it simply "fails silently" to close the stream again.

Next: buffering input

On the next page, we'll see how to use the convenience BufferedInputStream class for buffering input streams.


Written by Neil Coffey. Copyright © Javamex UK 2008. All rights reserved.