المسارات وFiles API
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
So far your programs have kept everything in variables and collections, and all of it vanishes when the program ends. To remember anything between runs, a program must write to a file on disk and read it back later. This module teaches that skill, and it starts with the most basic question: how does a program point at a file?
In this lesson you will meet two types from the java.nio.file package: Path, which describes a location, and Files, a toolbox of static methods that act on that location (check it, create folders, measure, delete). Think of a filing cabinet: a Path is the label "drawer 2, folder Contacts", and Files is the clerk who actually opens the drawer.
Why this matters for a backend: servers save uploads, write logs, and load configuration from files, and they run on laptops, test servers, and cloud machines whose folder layouts all differ. Building paths the right way is what lets the same code run on all of them. You will also use the checked IOException from the exceptions module, because file operations can fail.
The next lesson, "Reading and writing text", uses these paths to read and write file contents.
Learning Objectives
By the end of this lesson you will be able to:
- Create a
PathwithPath.ofand join parts withresolve. - Explain that a
Pathis only a description of a location and does not create or open anything. - Tell relative paths from absolute ones, and say why relative paths travel better between computers.
- Use
Files.exists,Files.createDirectories,Files.size,Files.delete, andFiles.deleteIfExists. - Read the parts of a path with
getFileName,getParent, andnormalize. - Predict what happens when a file operation fails, and know that it throws
IOException.
Written Explanation
A Path is a description, not a file
Path p = Path.of("data", "contacts.csv");This line builds an object that says "the file contacts.csv inside the folder data". Nothing is created, opened, or even looked for. The folder and file might not exist at all. That is the single most important idea of this lesson: creating a Path never touches the disk. Only methods of Files do.
Path.of accepts one string or several, and joins several using the separator of the operating system you run on: a forward slash on Linux and macOS, a backslash on Windows. You never type the separator yourself, so your code is not tied to one system. The \ you will see in the output below appears because it was run on Windows.
Joining and reading paths
resolve appends a child to a path that you treat as a folder:
Path dir = Path.of("data");
Path file = dir.resolve("notes.txt"); // data/notes.txtIf the argument you pass to resolve is itself absolute, it replaces the folder instead of being appended. That is a rare surprise but a real one.
A few methods read a path apart. They only work on the text of the path, so they do not check the disk:
| Method | Meaning | On data/notes.txt |
|---|---|---|
getFileName() | the last element | notes.txt |
getParent() | everything before the last element, or null if there is none | data |
normalize() | removes . and cancels name/.. pairs | data/../data/./notes.txt becomes data/notes.txt |
isAbsolute() | does it start from a root? | false |
Careful with getParent(): for Path.of("a.txt") it returns null, because there is no folder part. Calling a method on that null throws NullPointerException.
Relative versus absolute
An absolute path names the full location from the root of the file system, for example C:\Users\me\data.csv on Windows or /home/me/data.csv on Linux. A relative path such as data/contacts.csv starts from the program's working directory, the folder the program was started from. If you run the same program from a different folder, a relative path points to a different place. When you need to see where a relative path really leads, p.toAbsolutePath() shows it.
This is why the rule for this module is: use relative paths in your code. A hard-coded C:\Users\me\data.csv works on exactly one computer. A relative data/contacts.csv works anywhere, as long as the program is started from the same project folder. (In an IDE, check the run configuration to see which folder is the working directory. The setting is named differently in different IDEs.)
The Files toolbox
All the methods below are static methods of Files, and they take a Path.
| Method | What it does |
|---|---|
Files.exists(p) | true if the file or folder exists. Also returns false if it cannot tell. |
Files.createDirectories(dir) | creates the folder and any missing parents. No error if it already exists. |
Files.size(p) | size of the file in bytes. |
Files.delete(p) | deletes it. Throws an exception if it does not exist. |
Files.deleteIfExists(p) | deletes it and returns true, or returns false if it was not there. |
All of these except exists can throw IOException, the checked exception you met in the exceptions module. Either catch it with try/catch, or declare throws IOException on the method. The examples below use throws on main to stay short.
One honest warning: Files.exists only tells you the situation at that moment. Another program could create or delete the file a millisecond later. For that reason, do not rely on "check, then act" as a guarantee. Always be ready to handle an IOException from the action itself.
Examples
Example 1: a Path touches nothing (simple)
import java.nio.file.*;
public class Ex1 {
public static void main(String[] args) {
Path p = Path.of("data", "contacts.csv");
System.out.println(p);
System.out.println(Files.exists(p));
Path dir = Path.of("data");
Path file = dir.resolve("notes.txt");
System.out.println(file + " | " + file.getFileName() + " | " + file.getParent());
System.out.println(Path.of("/a/b").resolve("/x/y"));
System.out.println(Path.of("data/../data/./notes.txt").normalize());
}
}Output (run on Windows, so separators are backslashes; on Linux or macOS you would see forward slashes):
data\contacts.csv
false
data\notes.txt | notes.txt | data
\x\y
data\notes.txtWe built a path to a file that does not exist and nothing went wrong, because a Path is only a description. The fourth line shows the resolve surprise: an absolute argument replaces the base. (On Windows /x/y has no drive letter, so it prints as \x\y.)
Example 2: create a folder, then check, write, and delete (practical)
import java.io.IOException;
import java.nio.file.*;
public class Ex2 {
public static void main(String[] args) throws IOException {
Path dir = Path.of("data");
Files.createDirectories(dir);
Files.createDirectories(dir); // second call: no error
Path file = dir.resolve("hello.txt");
System.out.println("exists before: " + Files.exists(file));
Files.writeString(file, "hi\n");
System.out.println("exists after: " + Files.exists(file) + ", size " + Files.size(file));
System.out.println(Files.deleteIfExists(file));
System.out.println(Files.deleteIfExists(file));
try {
Files.delete(file);
} catch (IOException e) {
System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}Output:
exists before: false
exists after: true, size 3
true
false
NoSuchFileException: data\hello.txtFiles.writeString is previewed here only to create a file; the next lesson explains it. Notice three things. Calling createDirectories twice is harmless. The size is 3 bytes: the letters h, i, and one newline. And the two ways to delete behave differently when the file is missing: deleteIfExists returns false, while delete throws NoSuchFileException, a subclass of IOException. (The documentation says implementations may throw a different IOException here, so catch IOException rather than depending on the exact subclass.)
Example 3: one place that builds the path (realistic)
import java.io.IOException;
import java.nio.file.*;
public class Ex3 {
static Path resolveInDataDir(String name) throws IOException {
Path dir = Path.of("data");
Files.createDirectories(dir);
return dir.resolve(name);
}
public static void main(String[] args) throws IOException {
Path p = resolveInDataDir("report.txt");
System.out.println("relative: " + p);
System.out.println("isAbsolute: " + p.isAbsolute() + ", absolute form isAbsolute: " + p.toAbsolutePath().isAbsolute());
System.out.println("same ending: " + p.toAbsolutePath().endsWith(p));
}
}Output:
relative: data\report.txt
isAbsolute: false, absolute form isAbsolute: true
same ending: trueA real program should decide in one place where its files live. This helper guarantees the folder exists and hands back a path inside it, so the rest of the program never builds paths by hand. The absolute form ends with the relative path: it is the same location, spelled from the root.
Backend Insight
A backend rarely has a fixed home. The same service runs on your laptop, in a test environment, and in a container where the folder layout is different again. Code that hard-codes C:\Users\ahmed\uploads fails the moment it leaves your machine, and the failure is often confusing: "file not found" for a file you can see on your own disk.
Professional systems keep the base folder in one place (a constant, or later a configuration setting) and build every other path from it with resolve. Uploads, logs, and exported reports then all move together when the base folder changes. You will meet file uploads and logging again later in the course, and both depend on exactly this habit.
There is also a security angle you will see later: a path built from a user's text, such as an uploaded file name containing ../.., can point outside the folder you meant. normalize() is one of the tools used to detect that. For now, remember that user-supplied text should never become a path without checking.
Common Mistakes
Mistake 1: Thinking Path.of creates the file
- Incorrect: "I ran
Path.of("data/out.txt"), so the file is there now." - Correct: "The
Pathdescribes a location. The file exists only after aFilesmethod such aswriteStringcreates it."
Mistake 2: Hard-coded absolute paths
- Incorrect:
Path.of("C:\\Users\\me\\contacts.csv") - Correct:
Path.of("data", "contacts.csv")
The absolute path breaks on every other computer, and on Linux and macOS it is not even a valid location.
Mistake 3: Typing the separator yourself
- Incorrect:
Path.of("data\\" + name) - Correct:
Path.of("data", name)orPath.of("data").resolve(name)
The first version only works on Windows. The second lets Java pick the right separator.
Mistake 4: Forgetting the folder does not exist yet
Writing to data/notes.txt fails if data does not exist. Call Files.createDirectories(Path.of("data")) first. It is safe to call every time, because an existing folder is not an error.
Mistake 5: Calling a method on getParent() without a null check
Path.of("a.txt").getParent() is null. Writing file.getParent().resolve("x") then throws NullPointerException for a file with no folder part.
Exercises
Try each exercise before opening the solutions. Compile and run the code where it asks for output.
Level A: Foundation
A1. What does Path.of("notes.txt") do to the disk? What does Files.exists(Path.of("notes.txt")) return if the file is not there?
أظهر الحل A1
A1. It does nothing to the disk: it only builds a description. Files.exists returns false.
A2. Write two different ways to build the path data/2024/report.txt without typing a separator yourself.
أظهر الحل A2
A2. Path.of("data", "2024", "report.txt") and Path.of("data").resolve("2024").resolve("report.txt"). Both print data/2024/report.txt (with backslashes on Windows).
A3. Which is better for a program you will share with a classmate: Path.of("C:\\work\\todo.txt") or Path.of("todo", "list.txt")? Give two reasons.
أظهر الحل A3
A3. Path.of("todo", "list.txt") is better. It is relative, so it works wherever the program runs from, and Java picks the correct separator. The absolute path exists only on one computer and is not a valid location on Linux or macOS.
Level B: Application
B1. Given Path p = Path.of("logs").resolve("app").resolve("today.log");, write down what p, p.getFileName(), and p.getParent() print (use forward slashes). Then write what Path.of("logs/app/../old/./x.log").normalize() gives.
أظهر الحل B1
B1. p is logs/app/today.log, getFileName() is today.log, getParent() is logs/app. The normalized path is logs/old/x.log: the . disappears and app/.. cancels out. Checked by running the code.
B2. Write a method static Path ensureDir(String name) throws IOException that creates the folder (and parents) and returns its Path. Calling it twice must not fail.
أظهر الحل B2
B2.
static Path ensureDir(String name) throws IOException {
return Files.createDirectories(Path.of(name));
}createDirectories returns the directory path and does not fail if the folder exists. Running ensureDir("data/sub") printed data\sub on Windows.
B3. A program calls Files.delete(path) on a file that is already gone. What happens? Show two different ways to write the deletion so that a missing file is not a problem.
أظهر الحل B3
B3. Files.delete throws an IOException (typically NoSuchFileException). Option one: Files.deleteIfExists(path); returns false instead of throwing. Option two: wrap Files.delete(path) in try/catch (IOException e) and handle the problem there.
Level C: Challenge
C1. Write static Path backupOf(Path file) that returns a path in the same folder with .bak added to the file name: data/a.txt gives data/a.txt.bak. It must also work for a path with no folder part, such as a.txt.
أظهر الحل C1
C1.
static Path backupOf(Path file) {
Path bakName = Path.of(file.getFileName() + ".bak");
Path parent = file.getParent();
return parent == null ? bakName : parent.resolve(bakName);
}backupOf(Path.of("data", "a.txt")) gave data\a.txt.bak and backupOf(Path.of("a.txt")) gave a.txt.bak. The null check is required because getParent() is null for a single name.
C2. Write static String describe(Path p) throws IOException that returns "<path>: missing" if the file does not exist, otherwise "<path>: N bytes". Explain in one sentence why a file could still disappear after your check.
أظهر الحل C2
C2.
static String describe(Path p) throws IOException {
if (!Files.exists(p)) return p + ": missing";
return p + ": " + Files.size(p) + " bytes";
}Another program (or the user) can delete the file between exists and size, so size may still throw an IOException. The check is a convenience, not a guarantee.
Mini Challenge
Create a small helper class AppFiles for an imaginary notes app. It should hold the base folder notes-app as one constant Path, and offer two methods: Path fileFor(String name), which makes sure the base folder exists and returns the path of name inside it, and boolean isEmptyOrMissing(Path p) throws IOException, which returns true if the file does not exist or its size is zero.
Test it in a main that prints fileFor("todo.txt"), whether it is empty or missing before it exists, and then after you create it with Files.writeString(path, "x").
Check your work: no separator is typed by hand, no absolute path appears anywhere, and running the program twice does not crash on the existing folder.
Summary & Next Lesson
Key takeaways
- A
Pathdescribes a location. Creating one never touches the disk. - Build paths with
Path.of(...)andresolve, so Java chooses the right separator. - Prefer relative paths such as
data/contacts.csv. Hard-coded absolute paths break on other computers. Files.exists,createDirectories,size,delete, anddeleteIfExistsact on the disk. Most can throw the checkedIOException.getParent()can returnnull, and anexistscheck can be out of date a moment later.
Next lesson
Now that you can point at files and create folders, the next lesson, "Reading and writing text", shows how to put text into a file and read it back, and what the default behaviour is when the file already exists.
Self-check
- Does
Path.of("data", "x.txt")create a file? - Why is
data/contacts.csva better choice thanC:\Users\me\contacts.csv? - What is the difference between
Files.deleteandFiles.deleteIfExistsfor a missing file? - What does
Path.of("report.txt").getParent()return? - Why should you call
Files.createDirectoriesbefore writing into adatafolder?
أظهر الإجابات
- No. It only describes a location; nothing on disk changes.
- It is relative, so it works on any computer where the program is started from the project folder, and Java supplies the right separator.
deletethrows anIOException(typicallyNoSuchFileException);deleteIfExistsreturnsfalse.null, because the path has no folder part.- Writing into a folder that does not exist fails.
createDirectoriescreates the folder and its parents, and it does nothing if the folder already exists.
References
- Oracle, Path (Java SE 21 API). It supports
Path.ofjoining with the system separator,resolve(including the absolute-argument case),getParentreturningnull,normalize, and relative paths resolving against the working directory. - Oracle, Files (Java SE 21 API). It supports
createDirectoriesnot failing for an existing folder,deleteversusdeleteIfExists, theIOExceptiondeclarations, and the warning thatexistsresults can be out of date. - Not verified in a source: the remark that
Path.of("/x/y")prints as\x\yon Windows comes from running the code, not from documentation, and the security remark about../in user input is general practice rather than a cited claim.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.