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.
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:
/home/alice/project/config.yml Linux, macOS
C:\Users\alice\project\config.yml WindowsA relative path starts from wherever the program happens to be:
config.yml in the current directory
data/input.csv in a subdirectory
../shared/lib.jar up one level, then downRelative 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.
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.
from project. Standing in project, "config.yml" means project/config.yml. The string never changed; the answer did.
from project/src. Standing in project/src, "config.yml" means nothing — the walk ends at project/src/config.yml, which does not exist. The string never changed; the answer did.
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
| Symbol | Means |
|---|---|
/ | 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 name | hidden 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:
| Message | Where it looked | Lesson |
|---|---|---|
| file not found | the working directory | this one |
| command not found | PATH | the next one |
| class not found | the classpath | the 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
- Create
a/b/note.txtwith some text in it. - From
a/, print the file using a relative path. Now do it froma/b/. Now from your home directory. - Do the same three times using an absolute path.
- Explain in one sentence why step 3 needed no thought and step 2 did.
The point
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 anywhereAn 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.