Every language
14 languages, copy-ready. One at a time with syntax highlighting, or all inline.
JSJavaScript
import { access, constants } from 'node:fs/promises';
async function exists(path) {
try {
await access(path, constants.F_OK);
return true;
} catch {
return false;
}
}
console.log(await exists('package.json') ? 'exists' : 'missing');Node has no boolean-returning exists in the promises API — the long-deprecated callback fs.exists aside, access-plus-catch is the blessed pattern (fs.existsSync is fine in sync scripts). access also conflates EACCES with ENOENT: match err.code if you need to distinguish.
TSTypeScript
import { stat } from 'node:fs/promises';
async function describe(path: string): Promise<'file' | 'directory' | 'missing'> {
try {
const s = await stat(path);
return s.isDirectory() ? 'directory' : 'file';
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return 'missing';
throw err;
}
}
console.log(await describe('package.json'));Distinguishing ENOENT from EACCES is the whole point — permission failures rethrow instead of masquerading as "missing", and one stat call answers both existence and type.
GoGo
package main
import (
"errors"
"fmt"
"os"
)
func main() {
if _, err := os.Stat("go.mod"); err == nil {
fmt.Println("exists")
} else if errors.Is(err, os.ErrNotExist) {
fmt.Println("missing")
} else {
panic(err) // permission denied on a parent, etc.
}
}os.Stat follows symlinks — use os.Lstat to test the link itself, which is how a broken symlink reads "missing" under Stat but "exists" under Lstat. The three-way branch keeps ErrNotExist distinct from other errors; errors.Is works across wrapped errors and os.IsNotExist(err) remains equivalent for unwrapped ones.
RsRust
use std::path::Path;
fn main() {
let path = Path::new("Cargo.toml");
match path.try_exists() {
Ok(true) => println!("exists"),
Ok(false) => println!("missing"),
Err(e) => panic!("cannot tell: {e}"),
}
println!("is a directory: {}", path.is_dir());
}exists() — and is_dir/is_file — swallow IO errors and report false; try_exists (Rust 1.70+) is the one that surfaces the absent-vs-unreadable distinction as a Result. All of them follow symlinks.
PHPPHP
<?php
$path = 'composer.json';
var_dump(file_exists($path));
var_dump(is_file($path));
var_dump(is_dir(__DIR__));file_exists follows symlinks, so a broken link reports false — is_link sees the link itself. PHP caches stat results per path for the request: after unlink/rename, call clearstatcache() or you read stale answers. is_file and is_dir return false for the opposite type, so a directory named composer.json makes is_file false while file_exists stays true.
PyPython
from pathlib import Path
path = Path('pyproject.toml')
if path.exists():
print('directory' if path.is_dir() else 'file')
else:
print('missing')exists() maps every OSError — permission errors included — to False, same as os.path.exists; catch explicitly when missing-vs-unreadable matters. is_symlink() detects the link itself, so a broken symlink can be "not exists but is_symlink". Pythonic alternative for open-if-present: try the open() and catch FileNotFoundError instead of check-then-open.
CC
#include <stdio.h>
#include <sys/stat.h>
int main(void) {
struct stat st;
if (stat("Makefile", &st) == 0) {
puts(S_ISDIR(st.st_mode) ? "directory" : "file");
} else {
perror("stat");
}
return 0;
}stat follows symlinks; lstat reports the link itself, which is how a broken link is detected. The access(2) shortcut has a famous trap — it tests the real UID/GID, not the effective one, so setuid programs get wrong answers. When the goal is really "open it if possible", just call open(2) and read errno (ENOENT vs EACCES). POSIX; Windows uses _stat.
C++C++
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
const fs::path p = "CMakeLists.txt";
std::error_code ec;
const bool ok = fs::exists(p, ec);
std::cout << "exists: " << std::boolalpha << ok << '\n';
if (ec) {
std::cerr << "cannot tell: " << ec.message() << '\n';
}
std::cout << "is directory: " << fs::is_directory(p) << '\n';
}C++17. The error_code overload reports failure through ec (and returns false) instead of throwing fs::filesystem_error like the plain overload — so check ec before trusting a false. is_directory has the same two-overload split; symlinks are followed.
C#C#
using System;
using System.IO;
if (File.Exists("app.config"))
Console.WriteLine("app.config is a file");
if (Directory.Exists("bin"))
Console.WriteLine("bin is a directory");Both methods swallow exceptions and return false — a locked or unreadable path is indistinguishable from an absent one, and each returns false for the other's type (File.Exists on a directory is false). .NET 7 adds Path.Exists, covering either kind in one call.
JvJava
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
public class Exists {
public static void main(String[] args) {
Path path = Path.of("pom.xml");
System.out.println(Files.exists(path) ? "exists" : "missing");
System.out.println(Files.isDirectory(path) ? "directory" : "file");
System.out.println("link itself: " + Files.exists(path, LinkOption.NOFOLLOW_LINKS));
}
}Files.exists returns false when it cannot determine the answer (access denied) rather than throwing. The NOFOLLOW_LINKS variant is the lstat-style check — a broken symlink is false followed but true unfollowed. Path.of needs Java 11+; java.io.File.exists() is the legacy equivalent with no link options.
SwSwift
import Foundation
let fm = FileManager.default
var isDirectory: ObjCBool = false
if fm.fileExists(atPath: "Package.swift", isDirectory: &isDirectory) {
print(isDirectory.boolValue ? "directory" : "file")
} else {
print("missing")
}The UnsafeMutablePointer<ObjCBool> out-parameter is an Objective-C carryover, but it is the only way to learn the type in the same syscall. fileExists follows symlinks, so a broken link reports false. For racing-safe checks, URL.checkResourceIsReachable() throws instead of returning a bare bool.
KtKotlin
import java.io.File
import java.nio.file.Files
import java.nio.file.LinkOption
fun main() {
val file = File("build.gradle.kts")
println(if (file.exists()) "exists" else "missing")
println(if (file.isDirectory) "directory" else "file")
val unfollowed = Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS)
println("link itself: $unfollowed")
}File.exists() swallows IOException and returns false — the same absent-vs-unreadable conflation as Java's Files.exists. The NIO Files.exists variant is the one that takes link options; file.toPath() bridges the two APIs.
RbRuby
path = 'Gemfile'
puts File.exist?(path) ? 'exists' : 'missing'
puts File.directory?(path) ? 'directory' : 'file'
puts File.symlink?(path) ? 'symlink' : 'not a symlink'File.exist? follows symlinks — a broken link reads as missing, while File.lstat (or File.symlink?) sees the link itself. The File.exists? alias was removed in Ruby 3.2; the question-mark spelling is the one that survives. Check-then-open still races: rescue Errno::ENOENT around the open instead.
ZigZig
const std = @import("std");
fn exists(path: []const u8) bool {
std.fs.cwd().access(path, .{}) catch return false;
return true;
}
pub fn main() void {
std.debug.print("exists: {}\n", .{exists("build.zig")});
}std.fs.Dir.access is the cheap probe (faccessat under the hood) but folds every error into false — match on error.FileNotFound when missing-vs-unreadable matters. std.fs.cwd().statFile(path) costs a full stat and returns Stat.kind (.file, .directory, .sym_link) when the type is needed too.