File Handling in Java
1. Java IO Overview
Java's file I/O is built on the concept of streams — sequences of data flowing between a source and a destination. There are two fundamental categories:
| Category | Base Classes | Unit | Use For |
|---|---|---|---|
| Byte Streams |
InputStream /
OutputStream
|
byte (8-bit) | Binary files: images, audio, video |
| Character Streams |
Reader /
Writer
|
char (16-bit Unicode) | Text files: .txt, .csv, .xml |
The java.io package contains the
traditional I/O classes. The
java.nio package (New I/O, Java 7+)
provides the modern Path/Files
API which is more powerful and concise.
Best practice: Prefer
java.nio.file.Files for new code.
Use java.io classes when working
with older APIs or when wrapping with buffered streams.
2. File Class
The java.io.File class represents a
file or directory path. It does not read or write content
— it manages metadata and filesystem operations.
import java.io.File;
public class FileClassDemo {
public static void main(String[] args) throws Exception {
File file = new File("example.txt");
// Create a new file
if (file.createNewFile()) {
System.out.println("File created: " + file.getName());
} else {
System.out.println("File already exists.");
}
// File metadata
System.out.println("Absolute path: " + file.getAbsolutePath());
System.out.println("Exists? " + file.exists());
System.out.println("Is file? " + file.isFile());
System.out.println("Is dir? " + file.isDirectory());
System.out.println("Size: " + file.length() + " bytes");
System.out.println("Readable? " + file.canRead());
System.out.println("Writable? " + file.canWrite());
// Directory operations
File dir = new File("myFolder");
dir.mkdir(); // create single directory
// dir.mkdirs(); // create parent directories too
File dir2 = new File("parent/child/grandchild");
dir2.mkdirs();
// List directory contents
File currentDir = new File(".");
String[] contents = currentDir.list();
System.out.println("Files in current directory:");
if (contents != null) {
for (String name : contents) {
System.out.println(" " + name);
}
}
// Delete the file
if (file.delete()) {
System.out.println("File deleted.");
}
}
}
Absolute path: C:\projects\example.txt
Exists? true
Is file? true
Is dir? false
Size: 0 bytes
3. FileInputStream and FileOutputStream
These byte-stream classes read and write raw bytes. Always close
them in a finally block or use
try-with-resources to prevent resource leaks.
import java.io.*;
public class ByteStreamDemo {
public static void main(String[] args) {
// Write bytes to a file
try (FileOutputStream fos = new FileOutputStream("data.bin")) {
byte[] data = {72, 101, 108, 108, 111}; // "Hello" in ASCII
fos.write(data);
fos.write('!');
System.out.println("Bytes written successfully.");
} catch (IOException e) {
System.err.println("Write error: " + e.getMessage());
}
// Read bytes from a file
try (FileInputStream fis = new FileInputStream("data.bin")) {
int byteValue;
System.out.print("Read bytes: ");
while ((byteValue = fis.read()) != -1) {
System.out.print((char) byteValue);
}
System.out.println();
} catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
} catch (IOException e) {
System.err.println("Read error: " + e.getMessage());
}
// Copy a file using byte streams
try (FileInputStream in = new FileInputStream("data.bin");
FileOutputStream out = new FileOutputStream("data_copy.bin")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
System.out.println("File copied successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Read bytes: Hello!
File copied successfully.
4. FileReader and FileWriter
Character-based streams that read and write text files using the platform's default charset (or a specified one). Ideal for plain text content.
import java.io.*;
public class CharStreamDemo {
public static void main(String[] args) {
// Write characters to a text file
try (FileWriter fw = new FileWriter("notes.txt")) {
fw.write("Java File Handling\n");
fw.write("Line 2: FileWriter example\n");
fw.write("Line 3: Written with try-with-resources\n");
System.out.println("Text written to notes.txt");
} catch (IOException e) {
e.printStackTrace();
}
// Read characters from a text file
try (FileReader fr = new FileReader("notes.txt")) {
int ch;
System.out.println("File contents:");
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
} catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
// Append to an existing file (second argument = true)
try (FileWriter fw = new FileWriter("notes.txt", true)) {
fw.write("Line 4: Appended line\n");
System.out.println("Appended successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
File contents:
Java File Handling
Line 2: FileWriter example
Line 3: Written with try-with-resources
Appended successfully.
5. BufferedReader and BufferedWriter
Wrapping FileReader/FileWriter
in BufferedReader/BufferedWriter
adds an in-memory buffer, dramatically reducing the number of disk
reads and writes. readLine() makes
line-by-line reading trivial.
import java.io.*;
public class BufferedStreamDemo {
public static void main(String[] args) throws IOException {
// Buffered writing
try (BufferedWriter bw = new BufferedWriter(
new FileWriter("buffered.txt"))) {
bw.write("First line");
bw.newLine(); // OS-independent line separator
bw.write("Second line");
bw.newLine();
bw.write("Third line");
System.out.println("Buffered write complete.");
}
// Buffered reading — line by line
try (BufferedReader br = new BufferedReader(
new FileReader("buffered.txt"))) {
String line;
int lineNum = 1;
while ((line = br.readLine()) != null) {
System.out.println("Line " + lineNum++ + ": " + line);
}
}
}
}
Line 1: First line
Line 2: Second line
Line 3: Third line
Performance: Always wrap FileReader/FileWriter with Buffered versions for real applications. Unbuffered reads hit the disk once per character — buffered reads load a chunk at a time.
6. Reading a File Line by Line
Three common approaches, each suited to different scenarios:
Method 1: BufferedReader (classic, memory-efficient)
import java.io.*;
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
Method 2: Scanner (flexible tokenizing)
import java.util.Scanner;
import java.io.File;
try (Scanner sc = new Scanner(new File("data.txt"))) {
while (sc.hasNextLine()) {
String line = sc.nextLine();
System.out.println(line);
}
}
Method 3: Files.readAllLines (NIO — simplest, loads all into memory)
import java.nio.file.*;
import java.util.List;
List<String> lines = Files.readAllLines(Paths.get("data.txt"));
for (String line : lines) {
System.out.println(line);
}
// Java 8+ stream approach (lazy — great for large files)
Files.lines(Paths.get("data.txt"))
.filter(l -> l.startsWith("ERROR"))
.forEach(System.out::println);
Memory warning:
Files.readAllLines() loads the
entire file into a List. For large
files (hundreds of MB), use
BufferedReader or
Files.lines() (which is lazy)
instead.
7. Writing to a File
Several options exist depending on your needs:
FileWriter with Append Mode
import java.io.*;
// Overwrite (default)
try (FileWriter fw = new FileWriter("log.txt")) {
fw.write("Session started\n");
}
// Append mode
try (FileWriter fw = new FileWriter("log.txt", true)) {
fw.write("New entry added\n");
}
PrintWriter (formatted output)
import java.io.*;
try (PrintWriter pw = new PrintWriter(new FileWriter("report.txt"))) {
pw.println("Report Title");
pw.printf("Value: %.2f%n", 3.14159);
pw.println("Done.");
System.out.println("PrintWriter write complete.");
}
Files.write (NIO — simplest)
import java.nio.file.*;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
// Write list of lines (overwrites)
Files.write(Paths.get("output.txt"),
Arrays.asList("Line A", "Line B", "Line C"),
StandardCharsets.UTF_8);
// Append to existing file
Files.write(Paths.get("output.txt"),
Arrays.asList("Appended line"),
StandardCharsets.UTF_8,
StandardOpenOption.APPEND,
StandardOpenOption.CREATE);
8. FilePermission and FileDescriptor
FilePermission (from
java.io) is used in conjunction with
Java's security manager to control which files a program can
access. It defines a target path and a set of allowed actions
(read,
write,
execute,
delete).
import java.io.FilePermission;
// Grant read permission on a specific file
FilePermission readPerm = new FilePermission("/data/config.txt", "read");
// Grant read and write on all files in /logs
FilePermission logPerm = new FilePermission("/logs/*", "read,write");
// Check if one permission implies another
System.out.println(logPerm.implies(readPerm)); // false (different path)
FileDescriptor is a low-level handle
to an underlying OS file, socket, or other resource. The JVM uses
it internally; you rarely need it directly. The three standard
constants are:
import java.io.FileDescriptor;
FileDescriptor stdin = FileDescriptor.in; // standard input
FileDescriptor stdout = FileDescriptor.out; // standard output
FileDescriptor stderr = FileDescriptor.err; // standard error
// Force OS to flush data from kernel buffers to disk
try (java.io.FileOutputStream fos = new java.io.FileOutputStream("critical.txt")) {
fos.write("important data".getBytes());
fos.getFD().sync(); // flush all OS buffers — ensures data is on disk
}
When they matter:
FilePermission is relevant in
enterprise applications with a security manager enforcing policy
files. FileDescriptor.sync() matters
in financial or transactional code where data must survive a
power failure.
9. Java NIO — Path and Files
The java.nio.file package (Java 7+)
provides a modern, expressive API.
Path is the NIO equivalent of
File, and
Files provides utility methods for
common operations.
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
public class NIODemo {
public static void main(String[] args) throws Exception {
Path path = Paths.get("nio_example.txt");
// Check existence
System.out.println("Exists: " + Files.exists(path));
// Write content
Files.writeString(path, "Hello from NIO!\nSecond line.\n",
StandardCharsets.UTF_8);
// Read all bytes
byte[] bytes = Files.readAllBytes(path);
System.out.println("Bytes read: " + bytes.length);
// Read as string (Java 11+)
String content = Files.readString(path);
System.out.println("Content:\n" + content);
// File metadata
System.out.println("Size: " + Files.size(path) + " bytes");
System.out.println("Last modified: " + Files.getLastModifiedTime(path));
// Copy a file
Path dest = Paths.get("nio_copy.txt");
Files.copy(path, dest, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Copied to: " + dest);
// Move (rename) a file
Path moved = Paths.get("nio_moved.txt");
Files.move(dest, moved, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Moved to: " + moved);
// Delete files
Files.deleteIfExists(path);
Files.deleteIfExists(moved);
System.out.println("Cleanup done.");
}
}
Bytes read: 28
Content:
Hello from NIO!
Second line.
Size: 28 bytes
Copied to: nio_copy.txt
Moved to: nio_moved.txt
Cleanup done.
10. Walking a Directory
Files.walk() traverses a directory
tree recursively. Files.list() returns
only the immediate children of a directory (non-recursive).
import java.nio.file.*;
import java.io.IOException;
public class DirectoryWalkDemo {
public static void main(String[] args) throws IOException {
Path startDir = Paths.get("C:/myproject");
System.out.println("=== All files recursively (Files.walk) ===");
try (java.util.stream.Stream<Path> stream = Files.walk(startDir)) {
stream.filter(Files::isRegularFile)
.forEach(p -> System.out.println(p));
}
System.out.println("\n=== Only Java files ===");
try (java.util.stream.Stream<Path> stream = Files.walk(startDir)) {
stream.filter(p -> p.toString().endsWith(".java"))
.forEach(p -> System.out.println(p.getFileName()));
}
System.out.println("\n=== Immediate children only (Files.list) ===");
try (java.util.stream.Stream<Path> stream = Files.list(startDir)) {
stream.forEach(p -> System.out.println(
p.getFileName() + (Files.isDirectory(p) ? " [DIR]" : " [FILE]")));
}
// Count total files
long count;
try (java.util.stream.Stream<Path> stream = Files.walk(startDir)) {
count = stream.filter(Files::isRegularFile).count();
}
System.out.println("\nTotal files: " + count);
}
}
C:\myproject\src\Main.java
C:\myproject\src\Utils.java
C:\myproject\pom.xml
=== Only Java files ===
Main.java
Utils.java
Total files: 3
11. try-with-resources for IO
Introduced in Java 7, try-with-resources automatically closes any
AutoCloseable resource at the end of
the block — even if an exception is thrown. This is the preferred
way to handle all I/O resources and eliminates boilerplate
finally blocks.
import java.io.*;
public class TryWithResourcesDemo {
// Old way — verbose and error-prone
public static void oldWay(String path) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(path));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try { br.close(); }
catch (IOException e) { e.printStackTrace(); }
}
}
}
// Modern way — clean and safe
public static void modernWay(String path) {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// br is automatically closed here
}
// Multiple resources — closed in reverse order
public static void copyFile(String src, String dst) {
try (BufferedReader br = new BufferedReader(new FileReader(src));
BufferedWriter bw = new BufferedWriter(new FileWriter(dst))) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
System.out.println("File copied with try-with-resources.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Rule: Any class implementing
AutoCloseable (including all java.io
streams, JDBC connections, and NIO channels) should be used in
try-with-resources. This is non-negotiable in production code.
12. Common IO Exceptions
Knowing which exception signals which problem helps you write better error handling:
| Exception | When It Occurs | Handling Tip |
|---|---|---|
FileNotFoundException |
File does not exist, or path is a directory, or no read permission. |
Subclass of IOException. Check
file.exists() first or show a
user-friendly message.
|
IOException |
General I/O failure: disk full, network drive disconnected, corrupted stream. | Catch separately after more specific exceptions; log the full stack trace. |
SecurityException |
Security manager denies read/write access. | Rare in modern apps; check security policy if running in a sandboxed environment. |
EOFException |
Reached end of file unexpectedly (common with
DataInputStream).
|
Use read() == -1 checks instead
of letting EOF throw.
|
import java.io.*;
public class ExceptionHandlingDemo {
public static void readFile(String path) {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (FileNotFoundException e) {
// Specific: file doesn't exist
System.err.println("ERROR: File not found at path: " + path);
System.err.println("Please check the file path and try again.");
} catch (IOException e) {
// General: disk error, permission denied, etc.
System.err.println("ERROR: Failed to read file: " + e.getMessage());
e.printStackTrace();
}
}
public static void main(String[] args) {
readFile("exists.txt"); // reads fine
readFile("nonexistent.txt"); // FileNotFoundException
}
}
