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 = new FileInputStream(f);
  try {    
    return (in.read() == 'J' &&
            in.read() == 'F' &&
            in.read() == 'I' &&
            in.read() == 'F');
  } finally {
      in.close();
  }
}

If an exception is thrown while opening the stream, we assume that there is no stream to close, so in this example we leave the construction of the FileInputStream outside of the try/catch block. 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) throws IOException {
  InputStream in = new FileInputStream(f);
  try {    
    return (in.read() == 'J' &&
            in.read() == 'F' &&
            in.read() == 'I' &&
            in.read() == 'F');
  } finally {
      try { in.close(); } catch (IOException ignore) {}
  }
}

Although our method is declared to throw IOException, we need to catch the exception inside the finally clause. Otherwise, if an exception occurred on reading, then again on trying to close, the "more interesting" exception that occurred on reading would be masked.

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.


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.