Master modern file operations through NIO.2 architecture, path manipulation mastery, and advanced I/O patterns for robust file handling
By the end of this lesson, you will:
<aside> š
NIO.2 Philosophy
NIO.2 (New I/O 2) treats the file system as a navigable data structure rather than just strings. Paths are like GPS coordinates for files, providing precise navigation and rich metadata access through a modern, exception-safe API.
</aside>
File I/O Evolution - From Legacy to Modern
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š LEGACY [java.io](<http://java.io>).File š MODERN NIO.2 (java.nio.file)
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā File file = new File("path.txt"); ā ā Path path = Paths.get("path.txt"); ā
ā ā ā ā
ā ā PROBLEMS: ā ā ā
BENEFITS: ā
ā ⢠String-based paths ā ā ⢠Structured Path objects ā
ā ⢠Limited metadata access ā ā ⢠Rich attribute support ā
ā ⢠Platform-specific behavior ā ā ⢠Platform abstraction ā
ā ⢠Poor error handling ā ā ⢠Detailed exceptions ā
ā ⢠No symbolic link support ā ā ⢠Full link support ā
ā ⢠Blocking operations only ā ā ⢠Async and watch capabilities ā
ā ā ā ā
ā if (file.exists()) { ā ā if (Files.exists(path)) { ā
ā file.delete(); // boolean ā ā Files.delete(path); // throws ā
ā } ā ā } ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š¤ļø PATH STRUCTURE VISUALIZATION
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā /home/user/projects/java-app/src/main/java/[App.java](<http://App.java>) ā
ā āāā¬āā āāā¬āā āāāā¬āāāā āāāā¬āāāā āā¬ā āāā¬āā āāā¬āā āāā¬āā ā
ā root user projects java-app src main java filename ā
ā ā
ā Path Methods: ā
ā ⢠getRoot() ā / ā
ā ⢠getParent() ā /home/user/projects/java-app/src/main/java ā
ā ⢠getFileName() ā [App.java](<http://App.java>) ā
ā ⢠getNameCount() ā 7 ā
ā ⢠getName(2) ā projects ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
<aside> š§
Path Navigation Philosophy
Think of Paths as precise coordinates in the file system. They can be absolute (full address) or relative (directions from current location), and can be manipulated like mathematical operations.
</aside>
public class ProjectFileNavigator {
// š Project structure constants
public static final String PROJECT_ROOT = "/home/user/projects/java-app";
public static final String SOURCE_DIR = "src/main/java";
public static final String RESOURCE_DIR = "src/main/resources";
public static final String TEST_DIR = "src/test/java";
private Path projectRoot;
private Path sourceDir;
private Path resourceDir;
private Path testDir;
public ProjectFileNavigator(String projectPath) throws IOException {
initializePaths(projectPath);
validateProjectStructure();
}
private void initializePaths(String projectPath) {
// šļø Creating paths in different ways
projectRoot = Paths.get(projectPath);
// Absolute path creation
Path absoluteSource = Paths.get(projectPath, SOURCE_DIR);
// Relative path creation
Path relativeSource = Paths.get(SOURCE_DIR);
// URI-based path creation
try {
Path uriPath = Paths.get(new URI("[file://](file://)" + projectPath));
System.out.println("URI path: " + uriPath);
} catch (URISyntaxException e) {
System.err.println("Invalid URI syntax: " + e.getMessage());
}
// Path resolution (joining paths)
sourceDir = projectRoot.resolve(SOURCE_DIR);
resourceDir = projectRoot.resolve(RESOURCE_DIR);
testDir = projectRoot.resolve(TEST_DIR);
System.out.println("šļø Project Paths Initialized:");
System.out.println(" Root: " + projectRoot);
System.out.println(" Source: " + sourceDir);
System.out.println(" Resources: " + resourceDir);
System.out.println(" Tests: " + testDir);
}
// š Path analysis and manipulation
public void demonstratePathOperations() {
System.out.println("\\nš Path Analysis Operations");
System.out.println("===========================");
Path sampleFile = sourceDir.resolve("com/example/[App.java](<http://App.java>)");
// Basic path information
System.out.println("Sample file: " + sampleFile);
System.out.println(" Is absolute: " + sampleFile.isAbsolute());
System.out.println(" Root: " + sampleFile.getRoot());
System.out.println(" Parent: " + sampleFile.getParent());
System.out.println(" File name: " + sampleFile.getFileName());
System.out.println(" Name count: " + sampleFile.getNameCount());
// Path components
System.out.println("\\nPath components:");
for (int i = 0; i < sampleFile.getNameCount(); i++) {
System.out.println(" [" + i + "] " + sampleFile.getName(i));
}
// Path manipulation
Path normalized = sampleFile.normalize();
Path parent = sampleFile.getParent();
Path resolved = parent.resolve("../other/[File.java](<http://File.java>)");
Path normalizedResolved = resolved.normalize();
System.out.println("\\nPath manipulation:");
System.out.println(" Original: " + sampleFile);
System.out.println(" Normalized: " + normalized);
System.out.println(" Resolved: " + resolved);
System.out.println(" Resolved normalized: " + normalizedResolved);
// Path relationships
demonstratePathRelationships();
// Path comparison
demonstratePathComparison();
}
private void demonstratePathRelationships() {
System.out.println("\\nš Path Relationships:");
Path javaFile = sourceDir.resolve("[App.java](<http://App.java>)");
Path parentDir = sourceDir;
Path siblingFile = sourceDir.resolve("[Config.java](<http://Config.java>)");
// Parent-child relationships
System.out.println("Java file starts with source dir: " +
javaFile.startsWith(parentDir));
System.out.println("Java file ends with '[App.java](<http://App.java>)': " +
javaFile.endsWith("[App.java](<http://App.java>)"));
// Sibling relationships
Path sibling = javaFile.resolveSibling("[Config.java](<http://Config.java>)");
System.out.println("Sibling of [App.java](<http://App.java>): " + sibling);
// Relative paths
try {
Path relativePath = parentDir.relativize(javaFile);
System.out.println("Relative path from source to file: " + relativePath);
Path reconstructed = parentDir.resolve(relativePath);
System.out.println("Reconstructed path: " + reconstructed);
System.out.println("Paths equal: " + javaFile.equals(reconstructed));
} catch (IllegalArgumentException e) {
System.out.println("Cannot relativize paths: " + e.getMessage());
}
}
private void demonstratePathComparison() {
System.out.println("\\nāļø Path Comparison:");
Path path1 = Paths.get("/home/user/documents/file.txt");
Path path2 = Paths.get("/home/user/../user/documents/file.txt");
Path path3 = Paths.get("documents/file.txt");
// Lexical comparison
System.out.println("Path1: " + path1);
System.out.println("Path2: " + path2);
System.out.println("Lexical equality (path1 == path2): " + path1.equals(path2));
System.out.println("After normalization: " + path1.equals(path2.normalize()));
// File system comparison (requires existing files)
try {
// This would compare actual file system entities
// boolean sameFile = Files.isSameFile(path1, path2);
// System.out.println("Same file in filesystem: " + sameFile);
} catch (Exception e) {
System.out.println("Cannot compare files (may not exist): " + e.getMessage());
}
// Path ordering
List<Path> paths = Arrays.asList(
Paths.get("zebra.txt"),
Paths.get("apple.txt"),
Paths.get("banana.txt")
);
System.out.println("\\nPath sorting:");
[paths.stream](<http://paths.stream>)()
.sorted()
.forEach(path -> System.out.println(" " + path));
}
// š Directory structure validation
private void validateProjectStructure() throws IOException {
System.out.println("\\nš Project Structure Validation");
System.out.println("===============================");
validateDirectory(projectRoot, "Project Root");
validateDirectory(sourceDir, "Source Directory");
validateDirectory(resourceDir, "Resource Directory");
validateDirectory(testDir, "Test Directory");
}
private void validateDirectory(Path dir, String description) throws IOException {
if (Files.exists(dir)) {
if (Files.isDirectory(dir)) {
System.out.println("ā
" + description + " exists: " + dir);
// Show directory contents
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
System.out.println(" Contents:");
for (Path entry : stream) {
String type = Files.isDirectory(entry) ? "š" : "š";
System.out.println(" " + type + " " + entry.getFileName());
}
} catch (IOException e) {
System.out.println(" ā Cannot read directory contents: " + e.getMessage());
}
} else {
System.out.println("ā ļø " + description + " exists but is not a directory: " + dir);
}
} else {
System.out.println("ā " + description + " does not exist: " + dir);
// Create directory structure if needed
System.out.println(" Creating directory structure...");
Files.createDirectories(dir);
System.out.println(" ā
Directory created: " + dir);
}
}
// šÆ Advanced path operations
public void demonstrateAdvancedOperations() {
System.out.println("\\nš Advanced Path Operations");
System.out.println("===========================");
// Working with different file systems
demonstrateFileSystemOperations();
// Path conversion operations
demonstratePathConversions();
// Complex path manipulations
demonstrateComplexManipulations();
}
private void demonstrateFileSystemOperations() {
System.out.println("š„ļø File System Operations:");
// Get default file system
FileSystem defaultFS = FileSystems.getDefault();
System.out.println("Default file system: " + defaultFS);
System.out.println("Separator: '" + defaultFS.getSeparator() + "'");
System.out.println("Root directories:");
for (Path root : defaultFS.getRootDirectories()) {
System.out.println(" " + root);
}
// File system attributes
System.out.println("File stores:");
for (FileStore store : defaultFS.getFileStores()) {
try {
System.out.println(" " + [store.name](<http://store.name>)() + " (" + store.type() + ")");
System.out.println(" Total: " + formatBytes(store.getTotalSpace()));
System.out.println(" Available: " + formatBytes(store.getUsableSpace()));
} catch (IOException e) {
System.out.println(" Cannot read store info: " + e.getMessage());
}
}
}
private void demonstratePathConversions() {
System.out.println("\\nš Path Conversions:");
Path path = sourceDir.resolve("[App.java](<http://App.java>)");
// Convert to File (legacy compatibility)
File file = path.toFile();
System.out.println("Path to File: " + file);
// Convert back to Path
Path pathFromFile = file.toPath();
System.out.println("File to Path: " + pathFromFile);
System.out.println("Round-trip equality: " + path.equals(pathFromFile));
// Convert to URI
URI uri = path.toUri();
System.out.println("Path to URI: " + uri);
// Convert to absolute path
Path absolute = path.toAbsolutePath();
System.out.println("Absolute path: " + absolute);
// Convert to real path (resolves symbolic links)
try {
Path realPath = path.toRealPath();
System.out.println("Real path: " + realPath);
} catch (IOException e) {
System.out.println("Cannot resolve real path: " + e.getMessage());
}
}
private void demonstrateComplexManipulations() {
System.out.println("\\nš§© Complex Path Manipulations:");
// Build complex path hierarchies
Path baseConfig = resourceDir.resolve("config");
Path envConfig = baseConfig.resolve("environments");
Path devConfig = envConfig.resolve("[dev.properties](<http://dev.properties>)");
Path prodConfig = envConfig.resolve("[prod.properties](<http://prod.properties>)");
System.out.println("Configuration hierarchy:");
System.out.println(" Base: " + baseConfig);
System.out.println(" Environment: " + envConfig);
System.out.println(" Development: " + devConfig);
System.out.println(" Production: " + prodConfig);
// Find common parent
Path commonParent = findCommonParent(devConfig, prodConfig);
System.out.println("Common parent: " + commonParent);
// Create relative paths between configurations
try {
Path devToProd = devConfig.getParent().relativize(prodConfig);
System.out.println("Dev to Prod relative: " + devToProd);
} catch (IllegalArgumentException e) {
System.out.println("Cannot create relative path: " + e.getMessage());
}
}
// š§ Utility methods
private Path findCommonParent(Path path1, Path path2) {
Path p1 = path1.toAbsolutePath().normalize();
Path p2 = path2.toAbsolutePath().normalize();
while (p1 != null && !p2.startsWith(p1)) {
p1 = p1.getParent();
}
return p1;
}
private String formatBytes(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024));
return String.format("%.1f GB", bytes / (1024.0 * 1024 * 1024));
}
// š Path statistics
public void generatePathStatistics() throws IOException {
System.out.println("\\nš Path Statistics");
System.out.println("==================");
System.out.println("Project structure analysis:");
analyzeDirectory(projectRoot, 0, 3);
}
private void analyzeDirectory(Path dir, int level, int maxLevel) throws IOException {
if (level > maxLevel || !Files.exists(dir) || !Files.isDirectory(dir)) {
return;
}
String indent = " ".repeat(level);
System.out.println(indent + "š " + dir.getFileName() + "/");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
analyzeDirectory(entry, level + 1, maxLevel);
} else {
try {
long size = Files.size(entry);
System.out.println(indent + " š " + entry.getFileName() +
" (" + formatBytes(size) + ")");
} catch (IOException e) {
System.out.println(indent + " š " + entry.getFileName() + " (size unknown)");
}
}
}
}
}
}