File sucks. Repeat after me. File sucks.

OK, now, this is not really helpful, so in order to demonstrate my point, this page will list some File API usage and compare that with what java.nio.file does in the same situation.

Let's get started...

Booleans, oh my Edit

Aaah, booleans.

File.renameTo(): returns a boolean.

File.createNewFile(): returns a boolean.

Etc etc.

You do check the return value of these operations, right? A lot of code doesn't, even today.

No, in fact, the problem is even worse than that: a lot of NEW code doesn't.

Let's just take a few examples...

Why can't I rename my file?? Edit

We'll start with .renameTo(). If the operation returns false, you know that it failed, and... That's about it. But can you pinpoint the reason why?

No sir. You can't. Unless you investigate the problem by hand (and are skilled enough with your OS to understand exactly what went wrong). No amount of "File code" you write to investigate the problem will be enough.

Many projects have tried (and at least they have the good idea to throw an exception on failure); none have succeeded at reliably reporting the reason. Ever. It is not for lack of trying. It is not because the code is bad. It is just that with File, you can't do it. Plain and simple.

Of course, java.nio.file has no such problem. What is more, you can govern its behaviour to tell whether the destination should be deleted if it already exists etc. Let's see for comparison purposes what Files.move() can throw:

  • AccessDeniedException: can't copy to new destination, or can't delete original file;
  • NoSuchFileException: source does not exist;
  • FileAlreadyExistsException: destination already exists, and you didn't specify to replace the destination;
  • DirectoryNotEmptyException: attempt to move a non empty directory across filesystems;
  • AtomicMoveNotSupportedException: cannot rename atomically, because, for instance, the filesystem does not support it, or a move is attempted across filesystems;
  • etc etc.

Why can't I delete my file?? Edit

I don't know sir. File.delete() would just return a boolean to me, how can I diagnose that? Even File.canWrite() is unreliable, so I can't reliably tell you whether you can actually modify the directory entries.

Of course, java.nio.file has no such problem either. You even have .deleteIfExists().

Why can't I create my file?? Or my directory?? Edit

I don't think there is a need to continue, is there?

Anyway, and as for each and every of these operations, with java.nio.file, this is not a problem.

FileNotFoundException, oh my Edit

FileNotFoundException, aka "the most useless exception to ever grace the JDK".

Here are some scenarios I have witnessed over the years where you are greeted with this exception; and what happens with java.nio.file for comparison:

  • You try and open a file which does not exist -- well, OK, this kind of makes sense, but that's the only case where the name is appropriate. java.nio.file will throw NoSuchFileException.
  • You try and open a file for reading/writing when you are not allowed to. Yes, FileNotFoundException. java.nio.file: AccessDeniedException. Making progress!
  • You try and open a file for writing on a read only filesystem. Guess what? FileNotFoundException! java.nio.file: ReadOnlyFileSystemException. Yes sir, you get the actual reason why your operation fails.
  • You try and open a file (for reading or writing, doesn't matter) which is in fact a symlink which loops: FileNotFoundException, milord. java.nio.file: FileSystemLoopException!

How refreshing, isn't it? Getting the actual reason why an operation fails. At last an API which allows you to do some serious filesystem work.

Hold on, I'm not finished yet...

Listing files in a directory, oh my Edit

With File, this is done using the listFiles() method.

Now, if the target of your file is a directory, it sort of works. However, what do you think happens in the following situations:

  • the target is not a directory?
  • the target is a directory but you can't read entries from it?
  • the target is a symbolic link which loops?

Answer: it returns null in all of these cases.

Now, let's see how java.nio.file does it and what happens in the scenarios mentioned above. The method you will use is Files.newDirectoryStream(). And this is what happens:

As if this weren't bad enough, there's more; have you noticed that listFiles() returns an array? Yes, that means that if you have millions of entries in a directory (and you succeed in listing them of course), you will have an array with millions of entries in it... OOM, anyone?

Whereas java.nio.file returns a DirectoryStream which is, as its name says, a stream. Directory entries are loaded on demand only! Of course, this depends on the filesystem implementations, but all default filesystems provided by the JDK implement on-demand loading.