teaching machines

ImageIO Exception

December 10, 2016 by . Filed under code, public.

While grading student work, I pass through the whole spectrum of feelings, from elation to disappointment to bafflement to pride. Today I spent a fair bit of time in disappointment, but not because of my students’ code. Because of some code in the Java library. Hear, my friend, a tale of a standard library gone wrong.

The students were using BufferedImage and ImageIO in a homework assignment that I should have called SimFur, as they were generating animal skin patterns using cellular automata. One student hardcoded a path that would only have worked on the student’s computer. Consider this minimum working example that demonstrates the issue:

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.File;
import javax.imageio.ImageIO;

public class Demonstration {
  public static void main(String[] args) throws IOException {
    BufferedImage image = new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB);
    File file = new File("/nonexistent/directory/bad/bad/bad.png");
    ImageIO.write(image, "png", file); 
  }
}

When I graded the student’s work using my unit tests, this was the error message that popped up:

Exception in thread "main" java.lang.NullPointerException
        at javax.imageio.ImageIO.write(ImageIO.java:1538)
        at Demonstration.main(Demonstration.java:10)

This is not as helpful a message as it could be, and the student didn’t understand what the issue was. What makes matter worse is that when I ran this code outside my unit tests, I got this output:

java.io.FileNotFoundException: /nonexisting/directory/bad/bad/bad.png (No such file or directory)
        at java.io.RandomAccessFile.open0(Native Method)
        at java.io.RandomAccessFile.open(RandomAccessFile.java:316)
        at java.io.RandomAccessFile.<init>(RandomAccessFile.java:243)
        at javax.imageio.stream.FileImageOutputStream.<init>(FileImageOutputStream.java:69)
        at com.sun.imageio.spi.FileImageOutputStreamSpi.createOutputStreamInstance(FileImageOutputStreamSpi.java:55)
        at javax.imageio.ImageIO.createImageOutputStream(ImageIO.java:419)
        at javax.imageio.ImageIO.write(ImageIO.java:1530)
        at Demonstration.main(Demonstration.java:10)
Exception in thread "main" java.lang.NullPointerException
        at javax.imageio.ImageIO.write(ImageIO.java:1538)
        at Demonstration.main(Demonstration.java:10)

See that helpful error message, with the FileNotFoundException? That’s the one I wanted my student to see. But for some reason or another I got the NullPointerException instead. I sensed that somewhere the FileNotFoundException was getting caught and printed to System.err instead of bubbling up to my handling code. In its place came the less meaningful NullPointerException.

Since the NullPointerException arose in ImageIO.write, I figured the standard library must be doing this suppression. I went to the OpenJDK source code and worked backwards from ImageIO.write. The offending code was in FileImageOutputStreamSpi:

try {
  return new FileImageOutputStream((File)output);
} catch (Exception e) {
  e.printStackTrace();
  return null;
}

The standard library should not be doing output like this! Why even have exceptions if this is how they are going to be handled?! I can accept the excuse that maybe the method wasn’t suppose to throw any checked exceptions, but then it should have been implemented this way:

try {
  return new FileImageOutputStream((File)output);
} catch (Exception e) {
  throw new RuntimeException(e);
}

Wrapping the checked exception up in an unchecked exception would at least have allowed meaningful information about the error to propagate up the call chain.

My disappointment was somewhat abated when I saw that there was an open issue to get this fixed. Then I saw that it was first reported in April 2004. Twelve years later, no progress has been made. According to the comments, this method was never even specified to return null. To add salt to the wound, I was unable to vote up the issue because Joe Users like me can’t get accounts on their issue tracker.