Search code examples
javafile-ionio

Can I trust methods in Files will throw NoSuchFileException when expected?


The java.nio.file.Files API is a really nice improvement over the old java.io.File class, but one detail strikes me as odd; with the exception of delete() no methods document that they may throw NoSuchFileException, and even delete() says this is optional.

I'd like to be able to differentiate between failures due to missing files and other IO issues, but it seems this isn't guaranteed to be possible.

The alternative of calling Files.exists() and the like beforehand risks a race-condition if the file is created in between the two operations.

Can I expect methods in Files will raise a NoSuchFileException when appropriate? If so, where is this documented? If not, how can I safely determine the failure is due to a missing file?


Example: On Windows 7 with Java 7.0.02 the Files.readAllLines() method does raise a NoSuchFileException, though it's not explicitly documented to do so:

Files.readAllLines(Paths.get("foo"), StandardCharsets.UTF_8)
java.nio.file.NoSuchFileException: foo
  at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
  at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
  at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
  at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:229)
  at java.nio.file.Files.newByteChannel(Files.java:315)
  at java.nio.file.Files.newByteChannel(Files.java:361)
  at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:380)
  at java.nio.file.Files.newInputStream(Files.java:106)
  at java.nio.file.Files.newBufferedReader(Files.java:2660)
  at java.nio.file.Files.readAllLines(Files.java:2993)

Solution

  • In general: no, you cannot trust methods in java.nio.file.Files to throw a NoSuchFileException when expected, but you can verify.

    As you can see from the stacktrace, Files uses the FileSystemProvider to perform the file operations. The FileSystemProvider implementations are restricted (like the WindowsFileSystemProvider) and in turn use a lot of native (C) code. For example, I traced the NoSuchFileException to the WindowsException which relies on the operating system to report a ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND. Another example is the newInputStream route which goes from ChannelInputStream to WindowsChannelFactory to WindowsNativeDispatcher.c which finally calls the native Windows function CreateFileW.
    Given the amount of code involved just for Windows, I do not see how this can be trusted to work the same for for example Linux which uses the code here and here.

    In my experience, Linux (Posix) file system behavior is pretty consistent, but the Windows (NT) file system behavior is not: I would not assume Windows 7 to behave exactly the same as Windows 8.

    Finally, a remark about the race condition: no file system I know guarantees that files listed in a directory actually exist (some files may have already been deleted, especially when using multiple threads operating on files in the same directory). Using methods like Files.exists() beforehand is, in my experience, a bad idea unless you are about to allocate a lot of resources (e.g. creating a connection to upload a file). When dealing with files, it is better to assume everything is in order and catch exceptions and then try to determine what is wrong. E.g. when reading a file, open it without checking if the file exists, and if you catch an error, check if the file existed to start with. This can save a lot of I/O operations which in turn will help performance.