Files, paths and the working directory

Absolute and relative, the working directory that breaks your file-not-found, and how a program resolves a path.

4 min read🧭 Programming Foundations

More beginner time is lost to "file not found" than to any language feature, and the file is almost always there. What is wrong is the path — specifically, what the path is relative to.

Two kinds of path

An absolute path starts from the root of the filesystem and is unambiguous anywhere:

plaintext
/home/alice/project/config.yml      Linux, macOS
C:\Users\alice\project\config.yml   Windows

A relative path starts from wherever the program happens to be:

plaintext
config.yml              in the current directory
data/input.csv          in a subdirectory
../shared/lib.jar       up one level, then down

Relative paths are shorter and portable between machines, which is why code uses them. They are also the entire problem, because "wherever the program happens to be" is a thing you have to know.

The working directory is the answer

Every running process has a working directory — the folder it considers "here". A relative path is resolved against it, and against nothing else.

This is the part that surprises people: it is not where the file is. It is not where the code is. It is where the process was started.

plaintext
project/
├── config.yml
└── src/
    └── App.java        code that opens "config.yml"

Run it from project/ and the file is found. Run the same program from project/src/ and it is not — same code, same file, different working directory. The IDE and the terminal often differ here, which is why a program works when you press Run and fails when you run it yourself.

/home/anaproject/youconfig.ymlfoundsrc/App.javautil/Dates.javaresolvingconfig.ymlfrom projectconfig.ymlproject/config.ymlproject/config.yml

from project. Standing in project, "config.yml" means project/config.yml. The string never changed; the answer did.

1 / 2

Step between the two frames and watch the right-hand column. The program did not change. The path did not change. Only where it was started changed, and that decided whether the file exists.

Reading a path

SymbolMeans
/separator (Windows uses \, and Java accepts / on both)
.this directory
..the parent directory
~your home directory — a shell convenience, not understood by every program
a leading /absolute, from the root
a leading dot in a namehidden by convention: .git, .env

That last row matters more than it looks. .env and .gitignore do not appear in ls or in a file browser by default, which is how people spend an afternoon looking for configuration that was in front of them. ls -la shows them.

Extensions are a hint, not a fact

.java, .class, .jar, .txt — the extension is part of the name and nothing more. Renaming photo.jpg to photo.txt does not change one byte of the file; it changes which program your desktop offers to open it with.

What decides what a file is is its content. A .class file begins with cafebabe whatever it is called, and javac refuses a file that does not end in .java purely as a convention it chose.

Three different "not found"s

Beyond paths you write, programs search in places you did not — and the three searches fail differently:

MessageWhere it lookedLesson
file not foundthe working directorythis one
command not foundPATHthe next one
class not foundthe classpaththe Java course

Reading which of the three you got is most of the diagnosis. They feel identical and have nothing in common.

Try it yourself

Break it on purpose

  1. Create a/b/note.txt with some text in it.
  2. From a/, print the file using a relative path. Now do it from a/b/. Now from your home directory.
  3. Do the same three times using an absolute path.
  4. Explain in one sentence why step 3 needed no thought and step 2 did.
The point
bash
mkdir -p a/b && echo "hello" > a/b/note.txt
 
cd a  && cat b/note.txt          # relative: works from here
cd b  && cat note.txt            # a different relative path, the same file
cd ~  && cat a/b/note.txt        # only if a/ happens to be in your home
 
cat /full/path/to/a/b/note.txt   # the same command, from anywhere

An absolute path is the same sentence wherever you say it. A relative path is a sentence that depends on where you are standing — which is exactly why code that uses one must control, or at least know, its working directory.

Misconceptions

  • "The path is relative to the source file." It is relative to the process's working directory. Where the source file lives is not consulted.
  • "It works in the IDE, so the path is right." The IDE picks a working directory for you, usually the project root. Your deployment will pick a different one.
  • "Absolute paths are safer." They are unambiguous and not portable — an absolute path from your laptop is meaningless in a container. Configuration, not hard-coding, is the answer.
Progress is saved on this device and to your account when signed in.