10

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)
dimo414
  • 47,227
  • 18
  • 148
  • 244

2 Answers2

1

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.

vanOekel
  • 6,358
  • 1
  • 21
  • 56
  • "it is better to assume everything is in order and catch exceptions and then try to determine what is wrong." certainly I agree, and to that end it would be nice if the `Files` API explicitly returned different exceptions for different error cases. – dimo414 Mar 22 '15 at 20:02
  • @dimo414 That would be nice but with oddities [like this](http://stackoverflow.com/q/12139482/3080094) (where you can delete a file if you remove the read-only attribute first) I'm not sure it can be made consistent across platforms. – vanOekel Mar 22 '15 at 20:44
  • Agreed, there are edge cases, but it's too bad the normal cases aren't documented more clearly. It sounds like the best thing to do is catch `IOException` and then check `Files.exists()`, and not bother looking for `NoSuchFileException` even though it may sometimes be thrown. – dimo414 Mar 22 '15 at 21:13
  • @dimo414 That would make cleaner and better maintainable code, but I would probably reverse it to save an I/O operation. Assume `NoSuchFileException` will usually be thrown (and if it is thrown, assume the file certainly does not exist), but sometimes may not be thrown. Use a `Boolean` variable `fileExists` with value `null` for "unknown" and `true`/`false` for "certainly does (not) exist". The code will be harder to read, but if I/O operations are costly, it could be worth it. – vanOekel Mar 22 '15 at 21:48
0

The Files class provides two deletion methods.

The delete(Path) method deletes the file or throws an exception if the deletion fails. For example, if the file does not exist a NoSuchFileException is thrown. You can catch the exception to determine why the delete failed as follows:

try {
    Files.delete(path);
} catch (NoSuchFileException x) {
    System.err.format("%s: no such" + " file or directory%n", path);
} catch (DirectoryNotEmptyException x) {
    System.err.format("%s not empty%n", path);
} catch (IOException x) {
    // File permission problems are caught here.
    System.err.println(x);
}

The deleteIfExists(Path) method also deletes the file, but if the file does not exist, no exception is thrown. Failing silently is useful when you have multiple threads deleting files and you don't want to throw an exception just because one thread did so first.

Note: The NoSuchFileException and DirectoryNotEmptyException are new exceptions introduced in Java 7.

Shraddha
  • 1,052
  • 11
  • 22
  • I am asking about the rest of the API, not just `delete()`/`deleteIfExists()` methods. The rest of the API (e.g. `readAllLines()`) does not document that it raises `NoSuchFileException`, even though it appears to do so (at least on Windows). Even `delete()` indicates this is an optional exception, suggesting callers cannot rely on this behavior. – dimo414 Mar 17 '15 at 05:56