File Handling and I/O
Understanding Files, Paths, and Directories
You are viewing a free preview lesson.
Lesson Overview
Java application অনেক সময় file system-এর সঙ্গে কাজ করে।
Examples:
Course content load করা
Configuration file পড়া
Generated report save করা
Uploaded file process করা
Temporary file তৈরি করা
Directory scan করা
Logs বা exports manage করা
Modern Java-তে file system নিয়ে কাজ করার primary APIs হলো:
Path
Files
এগুলো পাওয়া যায়:
java.nio.file
এই lesson-এ আমরা শিখব:
- File path কী
PathকীFilesutility class- Relative path
- Absolute path
- Path normalization
- File এবং directory distinguish করা
- Existence checks
- File metadata
- Parent, file name, extension
- Directory contents inspect করা
- Cross-platform path handling
- Path validation-এর practical rules
এই lesson-এ আমরা এখনও full file reading/writing করব না।
প্রথমে file system-এর structure এবং path model ঠিকভাবে বুঝব।
Learning Objectives
এই lesson শেষে আপনি পারবেন:
Pathobject তৈরি করতে- Relative এবং absolute path distinguish করতে
- Path components inspect করতে
- Path safely combine করতে
- Normalize করতে
- File existence check করতে
- File এবং directory distinguish করতে
- Basic metadata read করতে
- Cross-platform path code লিখতে
- String concatenation দিয়ে path বানানোর সমস্যা explain করতে
What Is a File Path?
A file path file system-এর একটি location represent করে।
Example:
courses/java/module-1.md
আরেকটি:
/home/sakib/liveklass/course.md
Windows example:
C:\Users\Sakib\liveklass\course.md
Path বলে:
File বা directory কোথায় আছে?
Path Is Not the File Content
এই দুইটি আলাদা concept:
Path
File content
Example:
Path path =
Path.of(
"course.md"
);
এখানে file read করা হয়নি।
শুধু:
course.md
location represent করা হয়েছে।
Path
Modern Java file API-এর central abstraction:
java.nio.file.Path
Import:
import java.nio.file.Path;
Create:
Path path =
Path.of(
"course.md"
);
Path.of()
Java-তে path create করার common approach:
Path.of(...)
Example:
Path path =
Path.of(
"courses",
"java",
"lesson.md"
);
Conceptually:
courses/java/lesson.md
Actual separator operating system অনুযায়ী handle হয়।
Do Not Build Paths with String Concatenation
Weak:
String path =
"courses/"
+ courseCode
+ "/lesson.md";
Problems:
- Platform-specific separator
- Double slash
- Missing slash
- Harder composition
- Harder normalization
Better:
Path path =
Path.of(
"courses",
courseCode,
"lesson.md"
);
Cross-Platform Paths
Unix-like systems commonly use:
/
Windows commonly uses:
\
Avoid hardcoding:
"courses\\java\\lesson.md"
or:
"courses/java/lesson.md"
when constructing paths from multiple components।
Use:
Path.of(
"courses",
"java",
"lesson.md"
);
Java uses the correct platform representation।
Relative Path
A relative path does not start from a file-system root।
Example:
Path path =
Path.of(
"courses",
"java",
"lesson.md"
);
This is relative।
Meaning:
Start from the current working directory
then go to courses/java/lesson.md
Current Working Directory
When Java program runs, it has a working directory।
You can inspect it:
String currentDirectory =
System.getProperty(
"user.dir"
);
System.out.println(
currentDirectory
);
If output is:
/home/sakib/liveklass
Then relative path:
courses/java/lesson.md
refers to:
/home/sakib/liveklass/courses/java/lesson.md
Absolute Path
An absolute path begins from a file-system root।
Linux/macOS example:
/home/sakib/liveklass/course.md
Windows example:
C:\Users\Sakib\liveklass\course.md
Check:
Path path =
Path.of(
"/home/sakib/liveklass/course.md"
);
System.out.println(
path.isAbsolute()
);
Possible output:
true
Converting to Absolute Path
Path relativePath =
Path.of(
"courses",
"java",
"lesson.md"
);
Path absolutePath =
relativePath.toAbsolutePath();
System.out.println(
absolutePath
);
The result depends on the current working directory।
toAbsolutePath() Does Not Guarantee the File Exists
Important:
Path absolute =
Path.of(
"missing.txt"
).toAbsolutePath();
This can produce a perfectly valid absolute path even if:
missing.txt does not exist
Path construction and file existence are different concerns।
getFileName()
Path path =
Path.of(
"courses",
"java",
"lesson-1.md"
);
System.out.println(
path.getFileName()
);
Output:
lesson-1.md
getParent()
Path path =
Path.of(
"courses",
"java",
"lesson-1.md"
);
System.out.println(
path.getParent()
);
Possible output:
courses/java
getRoot()
For relative path:
Path path =
Path.of(
"courses",
"java"
);
System.out.println(
path.getRoot()
);
Result:
null
because relative path has no root।
For absolute Unix path:
Path path =
Path.of(
"/home/sakib"
);
Root is:
/
Path Components
Path path =
Path.of(
"courses",
"java",
"module-1",
"lesson.md"
);
Number of name components:
System.out.println(
path.getNameCount()
);
Output:
4
Components:
courses
java
module-1
lesson.md
Access a Path Component
System.out.println(
path.getName(
0
)
);
Output:
courses
Path Index Starts at Zero
Given:
courses/java/lesson.md
Components:
0 → courses
1 → java
2 → lesson.md
Same zero-based indexing principle as List and arrays।
resolve()
One of the most useful Path operations:
resolve()
It combines paths।
Example:
Path courseDirectory =
Path.of(
"courses",
"java"
);
Path lessonPath =
courseDirectory.resolve(
"lesson-1.md"
);
Result:
courses/java/lesson-1.md
Resolve Another Path
Path base =
Path.of(
"courses"
);
Path child =
Path.of(
"java",
"lesson.md"
);
Path result =
base.resolve(
child
);
Result:
courses/java/lesson.md
Why resolve() Is Better Than Concatenation
Weak:
String result =
base
+ "/"
+ child;
Better:
Path result =
base.resolve(
child
);
The API understands file-system path semantics।
Absolute Path with resolve()
Important behavior:
Path base =
Path.of(
"/home/sakib"
);
Path other =
Path.of(
"/tmp/course.md"
);
Path result =
base.resolve(
other
);
Because other is absolute, result becomes:
/tmp/course.md
The base is ignored।
This matters when resolving external input।
normalize()
Paths can contain:
.
..
Example:
Path path =
Path.of(
"courses",
"java",
"..",
"backend",
".",
"lesson.md"
);
Normalize:
Path normalized =
path.normalize();
Result:
courses/backend/lesson.md
Meaning of .
.
means:
Current directory
Example:
courses/./java
normalizes to:
courses/java
Meaning of ..
..
means:
Parent directory
Example:
courses/java/../backend
normalizes to:
courses/backend
Normalize Does Not Access the File System
path.normalize()
is a path operation।
It does not verify:
- File exists
- Parent exists
- Symbolic link target
- Permissions
It simply simplifies path components।
relativize()
Suppose:
Path base =
Path.of(
"/home/sakib/liveklass"
);
Path target =
Path.of(
"/home/sakib/liveklass/courses/java.md"
);
Then:
Path relative =
base.relativize(
target
);
Result:
courses/java.md
relativize() Requires Compatible Paths
Usually both paths should be:
Both absolute
or:
Both relative
Mixing absolute and relative paths can fail।
The Files Utility Class
Path represents a location।
Files performs file-system operations।
Import:
import java.nio.file.Files;
Examples:
Files.exists(path)
Files.isDirectory(path)
Files.isRegularFile(path)
Files.size(path)
Files.createDirectory(path)
Checking Whether a Path Exists
Path path =
Path.of(
"course.md"
);
boolean exists =
Files.exists(
path
);
System.out.println(
exists
);
Files.notExists()
boolean missing =
Files.notExists(
path
);
Important:
!Files.exists(path)
and:
Files.notExists(path)
are not conceptually identical in every edge case।
Why?
Because file existence may sometimes be:
Unknown
due to permission or I/O issues।
For beginner use, Files.exists() is usually sufficient।
File or Directory?
Regular file:
Files.isRegularFile(
path
);
Directory:
Files.isDirectory(
path
);
Example:
Path path =
Path.of(
"courses"
);
if (
Files.isDirectory(
path
)
) {
System.out.println(
"This is a directory."
);
}
Existence Alone Is Not Enough
Weak:
if (
Files.exists(
path
)
) {
// Assume file
}
The path may point to:
Directory
Symbolic link
Special file
If you specifically require a regular file:
if (
Files.isRegularFile(
path
)
) {
}
Directory Existence
Path courseDirectory =
Path.of(
"courses"
);
if (
!Files.isDirectory(
courseDirectory
)
) {
System.out.println(
"Course directory is unavailable."
);
}
Basic File Metadata
Java can inspect file metadata।
Examples:
Size
Last modified time
Creation time
Is directory
Is regular file
File Size
long size =
Files.size(
path
);
This method can throw:
IOException
Example:
try {
long size =
Files.size(
path
);
System.out.println(
size
);
} catch (
IOException exception
) {
System.out.println(
"Could not read file size."
);
}
Last Modified Time
FileTime lastModified =
Files.getLastModifiedTime(
path
);
Imports:
import java.nio.file.attribute.FileTime;
Example:
System.out.println(
lastModified
);
Basic File Attributes
For multiple metadata fields:
BasicFileAttributes attributes =
Files.readAttributes(
path,
BasicFileAttributes.class
);
Import:
import java.nio.file.attribute.BasicFileAttributes;
Then:
attributes.size();
attributes.creationTime();
attributes.lastModifiedTime();
attributes.isDirectory();
attributes.isRegularFile();
Example Metadata Method
public static void printMetadata(
Path path
) {
try {
BasicFileAttributes attributes =
Files.readAttributes(
path,
BasicFileAttributes.class
);
System.out.println(
"Size: "
+ attributes.size()
);
System.out.println(
"Created: "
+ attributes.creationTime()
);
System.out.println(
"Modified: "
+ attributes.lastModifiedTime()
);
System.out.println(
"Directory: "
+ attributes.isDirectory()
);
System.out.println(
"Regular file: "
+ attributes.isRegularFile()
);
} catch (
IOException exception
) {
throw new IllegalStateException(
"Could not read file metadata.",
exception
);
}
}
File Extension
Path does not have a built-in:
getExtension()
method।
You can inspect the file name:
String fileName =
path.getFileName()
.toString();
Then:
int dotIndex =
fileName.lastIndexOf(
'.'
);
Example helper:
public static String getExtension(
Path path
) {
if (path == null) {
throw new IllegalArgumentException(
"Path is required."
);
}
Path fileNamePath =
path.getFileName();
if (fileNamePath == null) {
return "";
}
String fileName =
fileNamePath.toString();
int dotIndex =
fileName.lastIndexOf(
'.'
);
if (
dotIndex <= 0
|| dotIndex
== fileName.length() - 1
) {
return "";
}
return fileName.substring(
dotIndex + 1
);
}
Examples
lesson.md → md
course.json → json
archive.tar.gz → gz
README → ""
.env → ""
file. → ""
Whether .env should be considered an extension is a domain decision।
Directory Listing
To inspect direct children of a directory:
Files.list(
directory
);
It returns:
Stream<Path>
Because the stream uses an underlying resource, use try-with-resources।
try (
var paths =
Files.list(
directory
)
) {
paths.forEach(
System.out::println
);
}
Why Try-With-Resources Here?
The stream returned by:
Files.list(...)
holds an open directory resource।
It should be closed।
try (...)
ensures cleanup।
List Only Regular Files
try (
var paths =
Files.list(
directory
)
) {
paths.filter(
Files::isRegularFile
).forEach(
System.out::println
);
}
List Only Directories
try (
var paths =
Files.list(
directory
)
) {
paths.filter(
Files::isDirectory
).forEach(
System.out::println
);
}
Files.list() Is Not Recursive
Suppose:
courses/
├── java/
│ └── lesson.md
└── backend/
└── api.md
Calling:
Files.list(
Path.of(
"courses"
)
)
returns direct children:
java
backend
It does not automatically return nested lesson files।
Recursive traversal will be covered later।
Checking Readability and Writability
Files.isReadable(
path
);
Files.isWritable(
path
);
Files.isExecutable(
path
);
These can be useful hints।
But they do not guarantee a later operation will succeed।
Why Pre-Checks Cannot Guarantee Success
Weak assumption:
if (
Files.isWritable(
path
)
) {
// Writing must succeed
}
Between check and actual write:
- Permissions may change
- File may disappear
- Disk may fill
- Another process may lock/change it
The actual write operation must still handle failure।
This is a general rule:
Pre-checks improve intent and feedback, but the real operation remains authoritative.
Path Equality
Path first =
Path.of(
"courses",
"java"
);
Path second =
Path.of(
"courses",
"java"
);
System.out.println(
first.equals(
second
)
);
Output:
true
Textually Different Paths May Refer to the Same Location
courses/java
courses/./java
courses/backend/../java
These may resolve to the same logical location after normalization।
Example:
Path first =
Path.of(
"courses",
"java"
);
Path second =
Path.of(
"courses",
".",
"java"
);
System.out.println(
first.equals(
second
)
);
Likely:
false
But:
first.normalize()
.equals(
second.normalize()
);
returns:
true
toRealPath()
toRealPath() resolves a path using the actual file system।
Path realPath =
path.toRealPath();
It may:
- Convert to absolute path
- Resolve symbolic links
- Remove redundant elements
- Verify the path exists
It can throw:
IOException
normalize() vs toRealPath()
normalize()
Pure path manipulation
Does not require file existence
Does not resolve real symbolic links
toRealPath()
Uses actual file system
Path must generally exist
Resolves real path details
Can throw IOException
Path Traversal Risk
Suppose application stores course files under:
/data/courses
User provides:
../../secret.txt
Weak:
Path path =
base.resolve(
userInput
);
This could escape the intended directory after normalization।
Safer Base-Directory Validation
Path baseDirectory =
Path.of(
"/data/courses"
)
.toAbsolutePath()
.normalize();
Path target =
baseDirectory.resolve(
userInput
)
.normalize();
if (
!target.startsWith(
baseDirectory
)
) {
throw new IllegalArgumentException(
"Path escapes the allowed directory."
);
}
This is an important security pattern when path components come from external input।
Why normalize() Matters in Path Validation
Input:
java/../../secret.txt
Before normalization, it visually starts under:
java
After normalization:
../secret.txt
Now escape becomes obvious।
Do Not Accept Arbitrary File Paths When a File Name Is Enough
If user only needs to specify a course code:
Weak:
User sends full storage path
Better:
User sends course code
Application constructs path
Example:
Path courseFile =
baseDirectory.resolve(
courseCode.getValue()
+ ".md"
);
This gives the application stronger control over storage locations।
Paths as Domain Boundaries
Low-level file adapter may accept:
Path
But higher-level domain service may accept:
CourseCode
Example:
public String loadCourseContent(
CourseCode courseCode
) {
Path path =
baseDirectory.resolve(
courseCode.getValue()
+ ".md"
);
return storage.read(
path
);
}
This prevents file-system details from leaking throughout the domain।
Directory Structure Example
A simple LiveKlass content storage:
content/
├── java-oop/
│ ├── introduction.md
│ ├── oop.md
│ └── collections.md
└── backend/
├── http.md
└── rest-api.md
Base:
Path contentDirectory =
Path.of(
"content"
);
Course directory:
Path javaDirectory =
contentDirectory.resolve(
"java-oop"
);
Lesson:
Path lesson =
javaDirectory.resolve(
"collections.md"
);
Complete Example: Inspect a Course Directory
package io.liveklass;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
public class Main {
public static void main(
String[] args
) {
Path courseDirectory =
Path.of(
"content",
"java-oop"
);
printPathInfo(
courseDirectory
);
if (
Files.isDirectory(
courseDirectory
)
) {
printChildren(
courseDirectory
);
}
}
private static void printPathInfo(
Path path
) {
System.out.println(
"Path: "
+ path
);
System.out.println(
"Absolute: "
+ path.isAbsolute()
);
System.out.println(
"Absolute path: "
+ path.toAbsolutePath()
.normalize()
);
System.out.println(
"File name: "
+ path.getFileName()
);
System.out.println(
"Parent: "
+ path.getParent()
);
System.out.println(
"Exists: "
+ Files.exists(
path
)
);
System.out.println(
"Directory: "
+ Files.isDirectory(
path
)
);
}
private static void printChildren(
Path directory
) {
try (
var paths =
Files.list(
directory
)
) {
paths.forEach(
Main::printChild
);
} catch (
IOException exception
) {
throw new IllegalStateException(
"Could not list course directory.",
exception
);
}
}
private static void printChild(
Path path
) {
System.out.println();
System.out.println(
"Child: "
+ path.getFileName()
);
try {
BasicFileAttributes attributes =
Files.readAttributes(
path,
BasicFileAttributes.class
);
System.out.println(
"Size: "
+ attributes.size()
);
System.out.println(
"Regular file: "
+ attributes.isRegularFile()
);
System.out.println(
"Directory: "
+ attributes.isDirectory()
);
} catch (
IOException exception
) {
throw new IllegalStateException(
"Could not inspect "
+ path
+ ".",
exception
);
}
}
}
Design Review
Why Use Path Instead of String?
Path communicates:
This value represents a file-system path.
It also provides:
resolve()normalize()getParent()getFileName()toAbsolutePath()
Why Use Files?
Path represents location।
Files performs file-system operations।
This separation is intentional।
Why Use Try-With-Resources for Files.list()?
The returned stream holds a directory resource that must be closed।
Why Wrap IOException?
Main example translates low-level I/O failure into a higher-level failure with context while preserving the cause।
Common Mistakes
Treating a Path as File Content
Creating a Path does not read anything।
Building Paths with Manual Separators
Can create portability and formatting issues।
Assuming Absolute Path Means Existing File
Absolute only describes location form।
Assuming normalize() Checks the File System
It only simplifies path components।
Assuming Files.exists() Means Regular File
The path may be a directory or another file type।
Forgetting to Close Files.list()
The returned stream uses a resource।
Trusting isWritable() as a Guarantee
The actual write can still fail।
Allowing Untrusted .. Components Without Validation
Can lead to path traversal outside the allowed directory।
Exposing Storage Paths to Domain Logic Everywhere
Creates file-system coupling।
Practice Exercises
Exercise 1: Build a Course Path
Create:
Path buildCoursePath(
Path baseDirectory,
String courseCode
)
Expected:
baseDirectory/java-oop
Do not use String path concatenation।
Exercise 2: Inspect a Path
Given:
Path path =
Path.of(
"content",
"java",
"lesson.md"
);
Print:
- File name
- Parent
- Name count
- Absolute path
- Whether it is absolute
Exercise 3: Normalize
Normalize:
content/java/../backend/./api.md
Predict the result before running the code।
Exercise 4: Validate Base Directory
Given:
Path base =
Path.of(
"/data/courses"
);
Reject:
../../secret.txt
using:
resolve()
normalize()
startsWith()
Exercise 5: Directory Inspection
Given a directory, print only regular files using:
Files.list()
Files.isRegularFile()
Use try-with-resources।
Exercise 6: File Metadata
Write:
void printFileMetadata(
Path path
)
Print:
- Size
- Creation time
- Last modified time
- Is regular file
- Is directory
Preserve the cause if metadata loading fails।
Predict the Result
Question 1
Path path =
Path.of(
"courses",
"java",
"lesson.md"
);
System.out.println(
path.getFileName()
);
Answer
lesson.md
Question 2
Path path =
Path.of(
"courses",
"java",
"..",
"backend"
);
System.out.println(
path.normalize()
);
Answer
Conceptually:
courses/backend
Question 3
Path path =
Path.of(
"missing.txt"
);
Path absolute =
path.toAbsolutePath();
Does this prove the file exists?
Answer
No।
It only creates an absolute representation of the path।
Question 4
Path base =
Path.of(
"/courses"
);
Path absoluteChild =
Path.of(
"/tmp/test.md"
);
System.out.println(
base.resolve(
absoluteChild
)
);
Answer
/tmp/test.md
An absolute resolved path replaces the base।
Question 5
Does:
Files.exists(
path
)
guarantee that:
Files.isRegularFile(
path
)
is true?
Answer
No।
The existing path may represent a directory or another file type।
Knowledge Check
Question 1
What is a Path?
Question 2
What is the difference between Path and Files?
Question 3
What is a relative path?
Question 4
What is an absolute path?
Question 5
What does resolve() do?
Question 6
What does normalize() do?
Question 7
Does normalize() require the file to exist?
Question 8
What does getFileName() return?
Question 9
Why should path strings not be manually concatenated?
Question 10
How do you check whether a path is a directory?
Question 11
Why is Files.list() used with try-with-resources?
Question 12
What is the difference between normalize() and toRealPath()?
Question 13
Why is .. dangerous in untrusted paths?
Question 14
Does isWritable() guarantee a future write?
Question 15
Why might a service accept CourseCode instead of a raw storage Path?
Knowledge Check Answers
Answer 1
A Java object representing a file-system location।
Answer 2
Path represents a location; Files performs operations on file-system paths।
Answer 3
A path interpreted relative to the current working directory or another base path।
Answer 4
A path starting from the file system root।
Answer 5
It combines a base path with another path component।
Answer 6
It removes redundant . and resolvable .. path components।
Answer 7
No।
It is a path manipulation operation।
Answer 8
The last name component of the path।
Answer 9
Manual separators create portability, formatting, and composition problems।
Answer 10
Using:
Files.isDirectory(
path
)
Answer 11
Because the returned stream holds an open directory resource that should be closed।
Answer 12
normalize() is purely lexical; toRealPath() consults the actual file system and can resolve symbolic links।
Answer 13
It may allow a resolved path to escape an intended base directory।
Answer 14
No।
The actual operation can still fail due to changing file-system conditions।
Answer 15
It keeps file-system implementation details out of higher-level domain APIs।
Lesson Summary
এই lesson-এ আমরা শিখেছি:
Pathfile-system location represent করেFilesfile-system operations provide করে- Creating a
Pathdoes not access file contents - Relative paths current working directory-এর ওপর depend করে
- Absolute paths file-system root থেকে শুরু হয়
toAbsolutePath()existence verify করে নাgetFileName()final path component return করেgetParent()parent path return করেresolve()safe path composition support করে- Manual path concatenation avoid করা উচিত
normalize()redundant.এবং..components simplify করেtoRealPath()actual file system consult করেFiles.exists()existence check করেFiles.isRegularFile()এবংFiles.isDirectory()path type distinguish করে- File metadata
Files.readAttributes()দিয়ে পড়া যায় Files.list()direct directory children stream করেFiles.list()result try-with-resources দিয়ে close করা উচিত- Pre-checks future file operation success guarantee করে না
- Untrusted paths path traversal risk তৈরি করতে পারে
- Resolved paths allowed base directory-এর মধ্যে আছে কি না validate করা উচিত
- Strong domain values file-system pathsকে higher-level application logic থেকে hide করতে পারে
Next Lesson
পরবর্তী lesson:
Reading Text Files
আমরা শিখব:
Files.readString()Files.readAllLines()Files.lines()BufferedReader- Small files vs large files
- Character encoding
- UTF-8
- Streaming content
- Resource handling
- File read exception translation