Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
95: Working with Paths
I remember a junior dev on my team a few years ago who spent an entire Friday night debugging a FileNotFoundException that only happened in our production environment. On his local Windows machine, everything worked perfectly because he was using backslashes in his path strings. But the moment the code hit our Linux-based staging server, it collapsed. He had hardcoded "C:\\logs\\app.log", and the server, predictably, had no idea what a "C drive" was. It was a classic "works on my machine" disaster that could have been avoided if he hadn't treated file paths as simple strings.
In Java, treating a path as a String is a recipe for bugs. Paths are not just text; they are hierarchical structures that vary wildly between operating systems. That's why we use the java.nio.file.Path interface, introduced as part of the NIO.2 API. Think of Path as a programmatic representation of a location in the filesystem, decoupled from the actual file or directory it points to.
Moving Beyond the Legacy File API
You might have seen the older java.io.File class in legacy code. While File still exists, it's clunky and lacks many of the precision tools we need today. The Path interface is much more flexible. It doesn't actually "touch" the disk when you create it; it just defines the coordinates. To actually interact with the file (like reading its size or deleting it), you pass that Path object to the java.nio.file.Files utility class.
To create a path, you'll typically use Path.of() (introduced in Java 11) or Paths.get() in older versions. The beauty here is that you can pass multiple strings as arguments, and Java will automatically use the correct system-dependent separator (/ or \) for you.
import java.nio.file.Path;
// This works regardless of whether you're on Windows, macOS, or Linux
Path logPath = Path.of("var", "logs", "system.log");
System.out.println("Target path: " + logPath);
Navigating the Filesystem with Resolve and Normalize
Real-world paths are rarely clean. You'll often deal with relative paths, like ../config/settings.yaml, or you'll need to combine a base directory with a specific filename. This is where resolve() and normalize() become your best friends.
The resolve() method is essentially "path joining." If you have a base directory and a relative path, resolve() glues them together. If the path you are resolving is already an absolute path, it simply returns that absolute path, which prevents you from accidentally nesting absolute paths inside each other—a common mistake when handling user input.
Then there's normalize(). When you're jumping around directories using .. (parent) or . (current), your path string can become a mess. Normalizing removes these redundant elements to give you the cleanest possible version of the path.
Path baseDir = Path.of("/home/user/app");
Path relativePath = Path.of("config/../logs/debug.txt");
// Join them together
Path fullPath = baseDir.resolve(relativePath);
// Result: /home/user/app/config/../logs/debug.txt
// Clean up the ".." jumps
Path cleanPath = fullPath.normalize();
// Result: /home/user/app/logs/debug.txt
I always tell my mentees: never manually concatenate paths using the + operator. It's tempting, but it's exactly how those "Friday night bugs" happen. Stick to resolve(), and let the JVM handle the OS quirks.
📋 Practical Task
The Cross-Platform Path Sanitizer
You are building a tool that processes a list of user-provided file paths. Some users provide absolute paths, some provide paths with redundant ".." navigation, and some provide simple filenames that need to be stored in a specific application data directory.
Write a program that does the following:
- Define a
Pathobject representing a base directory:"app/data/storage". - Create a list of "dirty" input strings:
"user_settings.json""logs/../../temp/session.tmp""/etc/passwd"(an absolute path)
- For each input string:
- Convert the string to a
Path. - Use
resolve()to combine it with the base directory. - Use
normalize()to clean up any..or.segments. - Print the final, sanitized absolute path.
- Convert the string to a
Verify that the absolute path "/etc/passwd" remains absolute and does not get appended to your base directory, while the others are correctly resolved and cleaned.
There are no comments for now.