1

I am working on a Java program that requires to check the existence of files.

Well, simple enough, the code make use calls to File.exists() for checking file existence. And the problem I have is, it reports false positive. That means the file does not actually exist but exists() method returns true. No exception was captured (at least no exception like "Stale NFS handle"). The program even managed to read the file through InputStream, getting 0 bytes as expected and yet no exception. The target directory is a Linux NFS. And I am 100% sure that the file being looked for never exists.

I know there are known bugs (kind of API limitation) exist for java.io.File.exists(). So I've then added another way round by checking file existence using Linux command ls. Instead of making call to File.exists() the Java code now runs a Linux command to ls the target file. If exit code is 0, file exists. Otherwise, file does not exist.

The number of times the issue is hit seems to be reduced with the introduction of the trick, but still pops. Again, no error was captured anywhere (stdout this time). That means the problem is so serious that even native Linux command won't fix for 100% of the time.

So there are couple of questions around:

  1. I believe Java's well known issue on File.exists() is about reporting false negative. Where file was reported to not exist but in fact does exist. As the API does not throws IOException for File.exists(), it choose to swallow the Exception in the case calls to OS's underlying native functions failed e.g. NFS timeout. But then this does not explain the false positive case I am having, given that the file never exist. Any throw on this one?
  2. My understanding on Linux ls exit code is, 0 means okay, equivalent to file exists. Is this understanding wrong? The man page of ls is not so clear on explaining the meaning of exit code: Exit status is 0 if OK, 1 if minor problems, 2 if serious trouble.
  3. All right, back to subject. Any surefire way to check File existence with Java on Linux? Before we see JDK7 with NIO2 officially released.
BuZZ-dEE
  • 6,075
  • 12
  • 66
  • 96
Stanley
  • 115
  • 3
  • 7
  • Are you creating and deleting the file in the same process where you are checking for its existence? You should never get something saying that a file exists if it has never existed, but if it was recently deleted then you may get different behavior (like .nfs923126 files) – evil otto Sep 22 '11 at 04:16
  • does File.isFile behave any better? – MeBigFatGuy Sep 22 '11 at 05:40
  • Thanks for all the reply. I think I've just figured out why I got the false positive result. It is indeed not File.exists() not working but a race condition in the home brewed background file reader causes file reading to continue in the absence of file. I've just fixed the reader and now everything are back on track as far as my testing goes. – Stanley Sep 22 '11 at 06:26
  • Otto, you are right that if a file recently exists and get deleted it may exhibited the false positive behavior. But I think this condition does not exist in my environment, which is now further asserted by latest round of testing result. Again, thanks everyone for contributing. I wished JDK7 could have released by now that it could save my some time by taking out the problematic java.io from suspect list :P Why don't they just backport NIO2 if it could take forever for 7 to release? Huh? Just kidding, kinds of :) – Stanley Sep 22 '11 at 06:26
  • Most file operations are passed directly to the OS (except for buffered data) so if Java is not giving you the right result it more likely to be something you are doing, or a bug in the JVM or OS. It is less likely to be that changing which methods you use in Java will help. – Peter Lawrey Sep 22 '11 at 07:21
  • 2
    Duplicate of [Alternative to File.exists() in Java](https://stackoverflow.com/questions/3833127/alternative-to-file-exists-in-java) – BuZZ-dEE Dec 08 '20 at 15:21

3 Answers3

1

Here is a JUnit test that shows the problem and some Java Code that actually tries to read the file.

The problem happens e.g. using Samba on OSX Mavericks. A possible reason is explaned by the statement in: http://appleinsider.com/articles/13/06/11/apple-shifts-from-afp-file-sharing-to-smb2-in-os-x-109-mavericks

It aggressively caches file and folder properties and uses opportunistic locking to enable better caching of data.

Please find below a checkFile that will actually attempt to read a few bytes and forcing a true file access to avoid the caching misbehaviour ...

JUnit test:

/**
 * test file exists function on Network drive replace the testfile name and ssh computer
     * with your actual environment
 * @throws Exception
 */
@Test
public void testFileExistsOnNetworkDrive() throws Exception {
    String testFileName="/Volumes/bitplan/tmp/testFileExists.txt";
    File testFile=new File(testFileName);
    testFile.delete();
    for (int i=0;i<10;i++) {
        Thread.sleep(50);
        System.out.println(""+i+":"+OCRJob.checkExists(testFile));
        switch (i) {
        case 3:
            // FileUtils.writeStringToFile(testFile, "here we go");
            Runtime.getRuntime().exec("/usr/bin/ssh phobos /usr/bin/touch "+testFileName);
            break;
        }
    }
}

checkExists source code:

/**
 * check if the given file exists
 * @param f
 * @return true if file exists
 */
public static boolean checkExists(File f)  {
    try {
        byte[] buffer = new byte[4];
        InputStream is = new FileInputStream(f);
        if (is.read(buffer) != buffer.length) { 
            // do something 
        }
        is.close();
        return true;
    } catch (java.io.IOException fnfe) {

    }
    return false;
}
Wolfgang Fahl
  • 15,016
  • 11
  • 93
  • 186
0

If you need to be robust, try to read the file - and fail gracefully if the file is not there (or there is a permission or other problem). This applies to any other language than Java as well.

The only safe way to tell if the file exist and you can read from it is to actually read a data from the file. Regardless of a file system - local, or remote. The reason is a race condition which can occur right after you get success from checkAccess(path): check, then open file, and you find it suddenly does not exist. Some other thread (or another remote client) may have removed it, or has acquired an exclusive lock. So don't bother checking access, but rather try to read the file. Spending time in running ls just makes race condition window easier to fit.

Matej Kovac
  • 325
  • 3
  • 7
0

JDK7 was released a few months ago. There are exists and notExists methods in the Files class but they return a boolean rather than throwing an exception. If you really want an exception then use FileSystems.getDefault().provider().checkAccess(path) and it will throw an exception if the file does not exist.

BuZZ-dEE
  • 6,075
  • 12
  • 66
  • 96
Alan
  • 2,807
  • 3
  • 22
  • 6
  • Yes, this is what I read from Sun's bug post as well. Use File.toPath().checkAccess(). Nice to hear that JDK7 is released. Thanks. – Stanley Sep 23 '11 at 03:46
  • This does not work as expected in the Network file scenario.Path path=FileSystems.getDefault().provider().getPath(f.toURI()); FileSystems.getDefault().provider().checkAccess(path,AccessMode.READ); in the JUnit test case in my answer will fail. – Wolfgang Fahl Apr 21 '14 at 10:39