Path describes filesystem locations in a platform-aware way, and Files performs operations such as copying, moving, listing, and inspecting those locations. Using resolve is clearer than manually joining separators, while normalize simplifies a path's lexical components. Neither operation by itself proves that an entry exists. This lesson separates path calculations from actual filesystem work and uses owned temporary fixtures for the latter. Its output avoids absolute machine-specific paths so the examples remain comparable across Windows and other systems.
Build paths from meaningful components
Path.of accepts components that the filesystem provider combines appropriately. resolve adds a relative child path to a base. An absolute child can replace the base rather than being appended, so validate assumptions when accepting arbitrary path input. getFileName returns the final component, while getParent returns the preceding path when present. A relative path is interpreted against the operation's working directory, which can differ between a terminal, editor, and deployed application.
Normalize lexical paths without assuming existence
normalize removes redundant components such as a name followed by a parent reference where the lexical rules allow it. It does not access the filesystem. A normalized path can still be nonexistent, and symbolic links can make lexical simplification differ from physical traversal. toAbsolutePath resolves against an absolute base but does not require the entry to exist. toRealPath accesses an existing entry and can resolve filesystem-specific details, including links according to its options.
Copy and move have destination policies
Files.copy normally rejects an existing destination unless a replacement option is supplied. Files.move likewise has explicit options and can depend on filesystem support. A move may not be atomic unless the selected operation and provider support that guarantee; ATOMIC_MOVE can fail when unavailable. Do not describe every move as a transaction. For a tutorial, create and manage destinations inside a temporary directory rather than overwriting an unrelated file to demonstrate an option.
Directory listings are closeable streams
Files.list returns a Stream<Path> backed by an open directory resource, so use try-with-resources. Its encounter order is not a promised alphabetic listing. Convert to names and sort when deterministic presentation matters. Files.walk can traverse a hierarchy and also requires closure. Choose depth and link-following behavior deliberately for larger trees. A simple directory listing is not an automatic recursive cleanup policy, and an application should distinguish files from directories where necessary.
Inspect attributes without treating checks as reservations
Files.exists, isRegularFile, and size answer particular questions at the time they run. Another process can change an entry afterward, so a successful existence check does not reserve it for a later read. Perform the operation and handle its failure according to the contract. Attribute methods may have link-related options and provider-specific behavior. When several attributes are needed, readAttributes can obtain a grouped view rather than making many separate queries with different timings.
Keep path scope and ownership visible
An application should define its base directory and the allowed form of child names. Lexical startsWith checks and normalize alone are not a complete security boundary in the presence of links or changing filesystem state. Use suitable validation and provider behavior for the actual requirement. The examples operate only on directories they create and remove only known fixture entries. Continue with file handling for content operations and preserve useful I/O errors when a path operation fails.
Practice tasks and checks
Construct a base path and resolve a relative child, then compare it with resolving an absolute child available on your platform. Print isAbsolute and getFileName instead of assuming a separator string. Normalize a path containing a parent component and explain why this is not an existence test. Extend DirectoryListing with a third owned file and verify sorted output regardless of creation order. Add a copy into the same temporary directory and choose whether an existing destination should be rejected or replaced before selecting options. Close every listing or walking stream. Finally, document the directory and child-name contract for a small report exporter, including how an I/O failure is reported.
Java Summary
- Path calculation and filesystem operations are different activities.
- Normalization is lexical and does not prove existence or physical identity.
- Close directory streams and define presentation order explicitly.
- Continue with file content operations when these examples are clear.
Java Examples
Each example shows the code, the output it produces and what each part does.
Resolve a child without manual separators
Save this program as PathComponents.java. Compile it with javac PathComponents.java and run it with java PathComponents.
Expected output:
The relative path contains reports, 2024, and week.txt, giving three name components. getFileName selects only the last one. No leading root or drive is supplied, so isAbsolute returns false. The code does not create any directory or file and needs no I/O cleanup. Printing components rather than the whole path avoids depending on whether the platform displays slashes or backslashes.
import java.nio.file.Path;
public class PathComponents {
public static void main(String[] args) {
Path base = Path.of("reports", "2024");
Path child = base.resolve("week.txt");
System.out.println(child.getNameCount());
System.out.println(child.getFileName());
System.out.println(child.isAbsolute());
}
}3
week.txt
falseCompare a normalized lexical path
Save this program as NormalizedPath.java. Compile it with javac NormalizedPath.java and run it with java NormalizedPath.
Expected output:
The drafts component followed by the parent reference cancels under lexical normalization, leaving archive and final.txt. The cleaned path therefore has two names and equals the directly constructed path on the same provider. No existence check occurs, and this does not prove physical traversal through a symbolic-link drafts directory would reach the same location. Use real-path operations when the contract depends on an existing filesystem identity.
import java.nio.file.Path;
public class NormalizedPath {
public static void main(String[] args) {
Path source = Path.of("archive", "drafts", "..", "final.txt");
Path cleaned = source.normalize();
System.out.println(cleaned.getNameCount());
System.out.println(cleaned.equals(Path.of("archive", "final.txt")));
}
}2
trueList owned entries in a defined presentation order
Save this program as DirectoryListing.java. Compile it with javac DirectoryListing.java and run it with java DirectoryListing.
Expected output:
The files are created beta first and alpha second, but the listing's sorted stage defines alphabetical display order. The stream closes before cleanup tries deleting its directory, which matters on platforms retaining directory handles. Only the known owned files are removed, then the empty parent is deleted. This is a small listing example, not a recursive delete routine for arbitrary user-supplied paths.
import java.nio.file.*;
import java.util.List;
import java.util.stream.Stream;
public class DirectoryListing {
public static void main(String[] args) throws Exception {
Path directory = Files.createTempDirectory("path-list-");
Path alpha = directory.resolve("alpha.txt");
Path beta = directory.resolve("beta.txt");
try {
Files.writeString(beta, "B");
Files.writeString(alpha, "A");
try (Stream<Path> entries = Files.list(directory)) {
List<String> names = entries.map(path -> path.getFileName().toString())
.sorted().toList();
System.out.println(names);
}
} finally {
Files.deleteIfExists(alpha);
Files.deleteIfExists(beta);
Files.delete(directory);
}
}
}[alpha.txt, beta.txt]Java FAQ
Does normalize make a path absolute?
No. Normalization simplifies lexical components while preserving whether the path is relative or absolute. Use toAbsolutePath for an absolute representation and remember that it still does not prove existence.
Why close Files.list?
Its returned stream can hold an open directory resource. Try-with-resources releases that resource on success and failure instead of relying on later garbage collection or process exit.
What is the difference between resolve and relativize?
resolve joins paths: base.resolve("2026/july.csv") adds the child to the base. relativize goes the other way and describes how to get from one path to another: relativizing /data/reports/2026/july.csv against /data/reports gives 2026/july.csv.
How do I copy a whole folder in Java?
Files.copy copies one file or one empty folder. To copy a tree, walk the source with Files.walk, compute each target with target.resolve(source.relativize(path)), create directories as you meet them, and copy the files. Libraries such as Apache Commons IO offer this as a single call.
How do I find the user's home folder?
Read the system property: Path.of(System.getProperty("user.home")). Resolving settings or download folders from it, such as home.resolve(".myapp"), avoids hard-coding paths like C:\Users\Sam that only work on one computer.
Java Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
What does resolve("week.txt") do to this relative base?
Does normalize access the filesystem?
Java Quiz
2 questions to check what you learned.
Why is the directory result sorted explicitly?
- Files.list does not promise alphabetic encounter order
- Creation order is always alphabetical
- Streams cannot contain Path values
Does Files.exists reserve the entry for a later read?
- No
- Yes
- Only for regular files