Odd behaviour when deleting Files with Files.delete()

If Files.delete did not throw exception it means it deleted the file. Files.delete javadoc says that "on some operating systems it may not be possible to remove a file when it is open and in use by this Java virtual machine or other programs".


Can you delete an open file?

It is perfectly valid to delete the directory entry of a file when the file is opened. In Unix this is the default semantics and Windows behaves similarily as long as FILE_SHARE_DELETE is set on all file handles open to that file.

[Edit: Thanks to @couling for discussions and corrections]

However, there is a slight difference: Unix deletes the file name immediately, while Windows deletes the file name only when the last handle is closed. It however, prevents you from opening a file with the same name until the last handle to the (deleted) file is closed.

Go figure ...

On both systems, however, deleting the file does not necessarily make the file go away, it still occupies space on the disk as long as there is still an open handle to it. Space occupied by the file is only released when the last open handle is closed.

Excursion: Windows

That it is necessary to specify the flag on Windows makes it seem to most people that Windows cannot delete open files, but that's actually not true. That's just the default behaviour.

CreateFile():

Enables subsequent open operations on a file or device to request delete access.

Otherwise, other processes cannot open the file or device if they request delete access.

If this flag is not specified, but the file or device has been opened for delete access, the function fails. Note Delete access allows both delete and rename operations.

DeleteFile():

The DeleteFile function marks a file for deletion on close. Therefore, the file deletion does not occur until the last handle to the file is closed. Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED.

Having an open handle to a file with no name is one of the most typical methods of creating unnamed temporary files: Create a new file, open it, delete the file. You now have a handle to a file that nobody else can open. On Unix, the filename is truly gone and on Windows you cannot open a file with the same name.

The question is now:

Does Files.newOutputStream() set FILE_SHARE_DELETE?

Looking at the source, you can see that shareDelete indeed defaults to true. The only way to reset it is to use the non-standard ExtendedOpenOption NOSHARE_DELETE.

So yes, you can delete opened files in Java unless they are explicitly locked.

Why can't I re-create the deleted file?

The answer to that is hidden in the documentation of DeleteFile() above: The file is only marked for deletion, the file is still there. On Windows, you cannot create a file with the name of a file marked for deletion until the file is properly deleted, i.e. all handles to the file are closed.

The possible confusion of mixing name deletion and actual file deletion is probably why Windows disallows deleting open files by default in the first place.

Why does Files.exists() return false?

Files.exists() in the deep end on Windows opens that file at some point and we already know that we cannot re-open a deleted-but-still-open file on Windows.

In detail: Java code calls FileSystemProvider.checkAccess()) with no arguments, which calls WindowsFileSystemProvider.checkReadAccess() which straight away tries to open the file and hence fails. From what I can tell, this is the path taken when you call Files.exist().

There is also another code path that calls GetFileAttributeEx() to retrieve file attributes. Once again, it is not documented what happens when you try to retrieve the attributes of an deleted but not yet removed file, but indeed, you cannot retrieve the file attributes of a file marked for deletion.

Guessing, I'd say that GetFileAttributeEx() calls GetFileInformationByHandle() at some point, which it will never get to because it cannot get a file handle in the first place.

So indeed, after DeleteFile() the file is gone for most practical purposes. It still has a name, however, shows up in directory listings and you cannot open a file with the same name until the original file had all its handles closed.

This behaviour is more or less consistent, because using GetFileAttributes() to check if a file exists is a actually an file accessibility check, which is interpreted as file exists. FindFirstFile() (used by Windows Explorer to determine the file list) finds file names but tells you nothing about accessibility of the names.

Welcome to a few more weird loops in your head.