Files.exists()

Test whether a path exists or is a file or directory, understand the third answer it can hide, and avoid check-then-act races.

Signature
public static boolean exists(Path path, LinkOption... options)
  • Added in Java Java 7

Parameters

ParameterTypeDescription
$pathPathThe path to test.
$optionsoptionalLinkOption...= symbolic links are followedPass LinkOption.NOFOLLOW_LINKS to test the link itself instead of the file it points to.

Return value

booleantrue if the file exists; false if it does not exist or its existence cannot be determined.

Errors

No IOException; a failure to check is reported as false. SecurityException if a security manager denies read access (security managers can no longer be enabled from Java 24).

Files.exists(path) asks the file system whether something is at that path. It never throws IOException; when the answer cannot be found, for example because a parent directory denies access, it returns false.

Three answers, not two

Because of that, exists returning false does not prove the file is missing. Files.notExists returns true only when the file is confirmed absent. If both return false, the status is unknown. For most code the difference only matters when you are diagnosing permission problems on a server.

More specific checks

  • Files.isRegularFile(path): exists and is an ordinary file.
  • Files.isDirectory(path): exists and is a directory.
  • Files.isReadable, isWritable, isExecutable: exists and the JVM process has that access.

All of them follow symbolic links by default. A link whose target is missing reports false from exists; pass LinkOption.NOFOLLOW_LINKS to ask about the link itself.

The check-then-act race

The most common misuse is checking before acting:

if (Files.exists(path)) {
    return Files.readString(path);   // the file can be deleted between the two lines
}

Another process, a cleanup job, or a second request can change the file between the check and the read, so the read must handle the error anyway. Skip the check, do the operation, and catch NoSuchFileException or FileAlreadyExistsException. For creation, use CREATE_NEW or Files.createFile, which check and create in one step.

Where it is the right tool

Startup validation is a good fit: fail fast with a clear message when a configured upload or data directory is missing or not writable, before the first request arrives. Health checks and admin endpoints that report on state are fine too, because nothing then acts on the answer.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Fail fast at service startup when a configured data or upload directory is missing or not writable.
  • Report in an admin or health endpoint whether an expected file, such as a licence or certificate, is in place.
  • Detect a dangling current-release symbolic link in a deployment script before switching traffic.

Frequently asked questions

How do I check if a file exists in Java?

Call Files.exists(Path.of("file.txt")). For a stricter test use Files.isRegularFile for files or Files.isDirectory for directories. If you are about to read or create the file, skip the check and handle NoSuchFileException or FileAlreadyExistsException instead.

What is the difference between Files.exists and Files.notExists?

exists is true only when the file is confirmed present, and notExists is true only when it is confirmed absent. When the check fails, for example because of permissions, both return false.

Why does Files.exists return false for a symbolic link?

It follows links by default, so it reports on the target. If the target is missing, the result is false. Pass LinkOption.NOFOLLOW_LINKS to test the link itself.

More examples

A symbolic link to nothing

By default exists follows the link and finds nothing at the target. With NOFOLLOW_LINKS it reports the link entry itself, which is what deployment scripts that swap a current link need to know.

Example · java
void main() throws IOException {
    Path dir = Files.createTempDirectory("links");
    Path link = dir.resolve("current");
    try {
        // A "current release" link left behind after its target was removed.
        Files.createSymbolicLink(link, dir.resolve("release-41"));

        IO.println("exists: " + Files.exists(link));
        IO.println("exists (no follow): " + Files.exists(link, LinkOption.NOFOLLOW_LINKS));
        IO.println("isSymbolicLink: " + Files.isSymbolicLink(link));
    } finally {
        Files.deleteIfExists(link);
        Files.deleteIfExists(dir);
    }
}

// Output:
// exists: false
// exists (no follow): true
// isSymbolicLink: true

Act first, handle the missing case

There is no window between a check and the read, so a file deleted by another process is handled the same way as one that never existed. This is the shape to use behind a GET endpoint that returns 404 for a missing file.

Example · java
Optional<String> readIfPresent(Path file) throws IOException {
    try {
        return Optional.of(Files.readString(file));
    } catch (NoSuchFileException e) {
        return Optional.empty();
    }
}

void main() throws IOException {
    Path dir = Files.createTempDirectory("invoices");
    Path invoice = dir.resolve("INV-1001.txt");
    try {
        IO.println(readIfPresent(invoice).orElse("no invoice yet"));
        Files.writeString(invoice, "Total: 25.99 GBP");
        IO.println(readIfPresent(invoice).orElse("no invoice yet"));
    } finally {
        Files.deleteIfExists(invoice);
        Files.deleteIfExists(dir);
    }
}

// Output:
// no invoice yet
// Total: 25.99 GBP

Validating directories at startup

Checking configured directories once at startup turns a confusing failure on the first upload into a clear message in the deployment log. Nothing acts on the answer straight away, so the race described above does not apply.

Example · java
record DirCheck(String name, Path path) {}

void main() throws IOException {
    Path base = Files.createTempDirectory("svc");
    Path uploads = Files.createDirectory(base.resolve("uploads"));
    try {
        List<DirCheck> checks = List.of(
            new DirCheck("uploads", uploads),
            new DirCheck("exports", base.resolve("exports")));

        for (DirCheck c : checks) {
            boolean ok = Files.isDirectory(c.path()) && Files.isWritable(c.path());
            IO.println(c.name() + ": " + (ok ? "ok" : "missing or not writable"));
        }
    } finally {
        Files.deleteIfExists(uploads);
        Files.deleteIfExists(base);
    }
}

// Output:
// uploads: ok
// exports: missing or not writable

Discussion

  • Be the first to comment on this lesson.