19

I'm using Java 17 on Windows 10 with NTFS. There is a file A:\foo.bar that shows up in a Java Files.list() operation, but that when I try to read it using Java, it throws a java.nio.file.FileSystemException:

A:\foo.bar: The process cannot access the file because it is being used by another process

That's fine. There is another low-level process which has locked the file. When I turn off that other process, Java can access the file just fine. In fact even Robocopy, when trying to access this file, will skip the file (actually it will appear that Robocopy has copied the file, but it hasn't). So no mystery so far—another process is locking the file for exclusive access.

But here's the strange part. For the most part the file appears normal to Java:

  • As I mentioned the file A:\foo.bar shows up in a Files.list() of A:\.
  • If I call Files.isRegularFile(fooBarFile), it returns true as expected.
  • If I call Files.isReadable(fooBarFile), it turns false as I might expect (and which is useful in this case).
  • If I call Files.readAttributes(fooBarFile, "*") I see the attributes (timestamps, etc.).
  • If I call Files.readAttributes(fooBarFile, DosFileAttributes.class) it returns the DOS attributes.

But if I call Files.exists(fooBarFile) it returns false! So it would appear that a file that is locked for exclusive access by another process will return false for exists(), which to me doesn't seem to follow the semantics of the exists() method as explained in its API.

As it is, it does seem useful to see if a file is not accessible by checking to see if exists() returns false yet isRegularFile() returns true; nevertheless that was unexpected and seems to be undocumented. Is this expected behavior? Is it documented? Does it work the same on other platforms, e.g. Linux?

Finally I note that Files.notExists(fooBarFile) returns false as well, so Java is not saying that the file is nonexistent, merely that it does not exist. Hmmm … the only way I can make that make sense is if exists() means "accessible", but the API contract for exists() does not talk about accessibility. The notExists() documentation adds:

Note that this method is not the complement of the exists method. Where it is not possible to determine if a file exists or not then both methods return false.

That doesn't seem to apply to this situation either. So although this behavior is useful, it is unexpected, doesn't seem to be documented, and therefore I'm hesitant about relying too much on it. Can anyone provide more information or better yet authoritative documentation?

Update: A similar thing seems to happen with unreadable directories. For example the A:\System Volume Information directory is marked as "hidden" and "read-only" on Windows. It similarly throws a java.nio.file.FileSystemException exception if you try to access it. But Files.exists() returns false, even though Files.isDirectory() returns true! In fact it behaves exactly as the bullet points above, except that isDirectory() returns true instead of isRegularFile().

Thus it seems like Files.exists() is duplicating Files.isReadable() (at least on OpenJDK 17 on Windows), even though that behavior doesn't seem to follow the Files.exists() API contract. Is this a JDK bug?

Garret Wilson
  • 18,219
  • 30
  • 144
  • 272
  • 1
    Perhaps `Files.isReadable` is a more useful check? – VGR Aug 01 '22 at 15:38
  • @Garret Wilson you may dig deeper into what is described on https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/file/Files.html#notExists(java.nio.file.Path,java.nio.file.LinkOption...) `false if its existence cannot be determined`. Maybe because locked it can't be determined from the system – Panagiotis Bougioukos Aug 01 '22 at 16:55
  • VGR, `Files.isReadable()` is useful, yes; here it returns `false`. (I'll update the question with that information.) I guess that only emphasizes the question: why is `exist()` acting like `isReadable()`? I would expect `exists()` to return `true` and `isReadable()` to return `false`. – Garret Wilson Aug 01 '22 at 17:11
  • 2
    Panagiotis, to your question, if Java can't determine whether the file exists or not, how can it still determine that it is a regular file? In other words, if it can't determine if it exists or not, I would expect `isRegularFile()` to return `false`. I guess I'm just trying to understand how the behavior meshes with the method semantics, and whether this is platform-dependent and/or JVM implementation dependent. – Garret Wilson Aug 01 '22 at 17:13
  • @GarretWilson could you provide a minimal reproduceable example? What is the process that makes this issue upon this file? Does it have anything to do with hyper-v ? – Panagiotis Bougioukos Aug 01 '22 at 17:21
  • The program in question is [VeraCrypt,](https://veracrypt.fr/); when it has an `.hc` volume mounted, the file behaves as indicated (and RoboCopy skips it). – Garret Wilson Aug 01 '22 at 17:50
  • I updated the question to indicate this this happens to the `A:\System Volume Information` as well! This seems like buggy behavior to me. – Garret Wilson Aug 01 '22 at 18:35
  • Note that it *may* be that the JVM is asking it to the underlying file system. – MC Emperor Aug 01 '22 at 19:15
  • @GarretWilson what is the file format that `A:\ ` uses ? – Panagiotis Bougioukos Aug 02 '22 at 00:07
  • Panagiotis all these examples are using NTFS. – Garret Wilson Aug 02 '22 at 00:39
  • @GarretWilson could you please execute `Files.readAttributes(fooBarFile, DosFileAttributes.class, LinkOption.NOFOLLOW_LINKS);` and report any difference from `Files.readAttributes(fooBarFile, DosFileAttributes.class)` ? – Panagiotis Bougioukos Aug 02 '22 at 08:02
  • @GarretWilson also give a try on `Files.isRegularFile(fooBarFile, LinkOption.NOFOLLOW_LINKS)` and check if there is any difference – Panagiotis Bougioukos Aug 02 '22 at 08:07
  • @GarretWilson And in the end you can also verify the modified command `Files.exists(fooBarFile, LinkOption.NOFOLLOW_LINKS);` – Panagiotis Bougioukos Aug 02 '22 at 08:37
  • Just try to open the file and cope with the exception as and when it occurs. All this futzing about with `exists()/notExists()/isReadable()/isDirectoy()notExists().isRegularFile()` is just a waste of time, and is also vulnerable to timing-window problems. The exception is still lurking and you still have to deal with it. If you can't open the file, you can't open the file, and if you can you can. That's the only thing that actually matters. So do that, and nothing else. The best test for determining whether any resource can be used is simply to try to use it in the normal way. – user207421 Aug 02 '22 at 23:51
  • @GarretWilson I believe I have provided the answer to the question explaining why this is not a bug, but a feature. If you agree, could you please accept the answer as to make the future readability of the thread more clear? – Panagiotis Bougioukos Sep 15 '22 at 21:24

1 Answers1

11

Thus it seems like Files.exists() is duplicating Files.isReadable() (at least on OpenJDK 17 on Windows), even though that behavior doesn't seem to follow the Files.exists() API contract. Is this a JDK bug?

TL;DR

This is the way it was designed to behave and it follows the API contract. One major reason for why we have 2 methods, is that both methods return true if they are sure about the question.

  • Files.exist() true means file definitely exists.
  • Files.notExists() true means file definitely not exists.

But false does not mean otherwise. It could as well mean that the system can't determine the outcome and it returns false as the safest return if it can't throw such a checked exception.

  • If you want to do some action which is based on the condition that the file does not exist in the system, you should never use If (!Files.exists(file)) {...}. In this case let's say you want to create a new file and you want to make sure that another file does not already exist you should always check with If(Files.notExists(file)){...}

  • On the other side if you want to do some action which is based on the condition that the file exist in the system, you should never use If (!Files.notExists(file)) {...}. In this case let's say you want to read/modify a file and you want to make sure that the file already exist you should always check with If(Files.exists(file)){...}

Analytical explanation

I believe the java API doc of Files.exists(fooBarFile) could be a bit better, although the java API doc for notExists is a bit clearer for this in the following:

Note that this method is not the complement of the exists method. Where it is not possible to determine if a file exists or not then both methods return false.

Here is what I suspect is going on.

According to the method description

//  @return  
// {@code true} if the file exists; 
// {@code false} if the file does not exist or its existence cannot be determined.
public static boolean exists(Path path, LinkOption... options) {
        if (options.length == 0) {
            FileSystemProvider provider = provider(path);
            if (provider instanceof AbstractFileSystemProvider)
                return ((AbstractFileSystemProvider)provider).exists(path);
        }

        try {
            if (followLinks(options)) {
                provider(path).checkAccess(path); <-------------- if no options provided this is executed
            } else {
                // attempt to read attributes without following links
                readAttributes(path, BasicFileAttributes.class,
                               LinkOption.NOFOLLOW_LINKS);
            }
            // file exists
            return true;
        } catch (IOException x) {
            // does not exist or unable to determine if file exists
            return false;  <----- AccessDeniedException is causing return false
        }

    }

If someone inspects the code he will see the meaning of @return {@code false}... or its existence cannot be determined.

could be further explained from the line of .checkAccess(path) which mentions in the API doc:

 * @throws  AccessDeniedException
 *          the requested access would be denied or the access cannot be
 *          determined because the Java virtual machine has insufficient
 *          privileges or other reasons. <i>(optional specific exception)</i>

So in case a AccessDeniedException is thrown which is also a subclass of FileSystemException (which also the questioner is getting from another command during file read), then Files.exists(fooBarFile) is supposed to return false because of that exception which does not allow the JVM to determine if the file definitely exists.

Question also mentions:

Files.notExists(fooBarFile) returns false as well

which I think originates also from the same point.

public static boolean notExists(Path path, LinkOption... options) {
        try {
            if (followLinks(options)) {
                provider(path).checkAccess(path);  <---------- If no options provided this line executes
            } else {
                // attempt to read attributes without following links
                readAttributes(path, BasicFileAttributes.class,
                               LinkOption.NOFOLLOW_LINKS);
            }
            // file exists
            return false;
        } catch (NoSuchFileException x) {
            // file confirmed not to exist
            return true;
        } catch (IOException x) { 
            return false;   <------ In case of AccessDeniedException or some other IOException the return is false.
        }
    }

Which verifies that both methods exists and notExists are designed to return false in case AccessDeniedException or some other IOException is thrown, as this would be translated into , JVM can't determine if the file exists or not.

So each method gives the safest return it could give in this uncertain condition, considering it can't throw such an exception. So in case

  • it is exists() it returns false if it can't determine, so that the developer would not be sure that the file exists and move forward with actions such as reading the file.
  • it is notExists() it returns false if it can't determine, so that developer would not be sure that the file does not exist and move forward with actions such as creating a new file.

So both methods shall be used appropriately and trusted for a definite answer only if they return true. false does not mean negative answer to exists or notExists. And someone should never be based on the outcome of !Files.exists(file) and !Files.notExists(file), at least as a logical inversion of the answer of the method.

Panagiotis Bougioukos
  • 15,955
  • 2
  • 30
  • 47
  • https://stackoverflow.com/questions/673654/when-is-it-okay-to-check-if-a-file-exists – thwd Aug 02 '22 at 21:33