3

While trying to debug a weird issue where I knew an exception should have been thrown but was not, I found the following in the Java standard library's java.lang.ClassLoader class:

/**
 * Open for reading, a resource of the specified name from the search path
 * used to load classes.  This method locates the resource through the
 * system class loader (see {@link #getSystemClassLoader()}).
 *
 * @param  name
 *         The resource name
 *
 * @return  An input stream for reading the resource, or <tt>null</tt>
 *          if the resource could not be found
 *
 * @since  1.1
 */
public static InputStream getSystemResourceAsStream(String name) {
    URL url = getSystemResource(name);
    try {
        return url != null ? url.openStream() : null;
    } catch (IOException e) {
        return null;
    }
}

What reason would there be where the decision was made that this exception should not be thrown but instead silently consumed?

While discussing this with a coworker a possible option was that perhaps this function predated IOException and thus this was added to maintain backwards compatibility, however this method was added in Java 1.1, while IOException was added in 1.0.

This is a file operation so IOExceptions would not be out of place, so why would the makers of an exception based language choose returning null over passing up a thrown exception?

2 Answers2

4

This is unlikely to fail because:

getSystemResource() and therefore getSystemResourceAsStream() are using the System class loader.

It reads from local disk.

If it fails, we failed to read a file a jar or dir tree from the classpath.

IO Failures are unlikely, as we already started running from these jars, which have been verified ( and already read).

Failure to find the file is generally having the wrong filename , or a bad build of your code that doesn't include the resource. In these cases a null pointer will make your code crash fast in testing and your production code won't need to handle an IOException that only occurs if your build is screwed up.

Tim Williscroft
  • 3,563
  • 1
  • 21
  • 26
  • I suppose it's a preference thing, but I would rather have the try catch where there's no way around handling it than the null check which can be forgotten. Also, I didn't receive a `NullPointerException` as that is only thrown in the case you pass a null parameter. In the error case I had there was a typo in the name of the file, and it failed silently and continued, which caused other misleading errors much further into the process. – Ellie Harper May 24 '17 at 13:34
  • Using the \@NotNull annotations will let your static checker do that for you. Once the standard library gets updated to have null-ness annotations on values \@NotNull will add more value. – Tim Williscroft May 24 '17 at 23:48
2

The writers of the function get to define what "could not be found" includes. Here they include any IOExceptions thrown in the attempt as "could not be found". This simplifies the usage of this function, as the user only needs to check for null. A more modern library might have this return Option<InputStream>.

Notice how they don't try-catch getSystemResource, the documentation of that also specifies it returns null on failure.

Caleth
  • 10,519
  • 2
  • 23
  • 35
  • I picked this answer as it more directly approaches the 'why' but it's kinda sad that there isn't like dev notes from Java 1.1 that could be researched for more insight into the decision. – Ellie Harper May 24 '17 at 13:44
  • A very blunt instrument, but you could use something along the lines of https://stackoverflow.com/questions/23561555/java-exceptions-counter-on-jvm-hotspot to peek at the caught exception. Other than logging, what would you do differently with an IOException than a null result? – Caleth May 24 '17 at 13:56
  • 1
    The IOException being thrown would force you to be aware of it, as your code wouldn't compile if you didn't try-catch wrap it or pass it up, and also it would allow for a very easily defined point of failure and would almost force better feedback. In my case, the fact that the exception was silently consumed lead to a long hunt through the code base as the point where an exception finally occurred was much later in an almost completely unrelated function. – Ellie Harper May 24 '17 at 14:11