Backend Systemsbeginner
Java Exceptions & File I/O
Master Java exception handling: try/catch/finally, custom exceptions, checked vs unchecked. Read and write files using java.nio.file and BufferedReader.
Asma HafeezApril 17, 20265 min read
javaexceptionsfile-ionioerror-handling
Java Exceptions & File I/O
Exception handling is Java's way of dealing with runtime errors. File I/O is where you'll use it most often.
Exception Hierarchy
Throwable
├── Error — JVM-level problems (OutOfMemoryError) — don't catch
└── Exception
├── RuntimeException — unchecked (programming bugs)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── IllegalArgumentException
│ └── NumberFormatException
└── (other exceptions) — checked (must handle or declare)
├── IOException
├── FileNotFoundException
└── SQLExceptiontry / catch / finally
JAVA
try {
int result = 10 / 0; // throws ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero: " + e.getMessage());
} finally {
System.out.println("This always runs"); // cleanup here
}Multiple catch blocks
JAVA
String input = "not a number";
try {
int value = Integer.parseInt(input);
System.out.println(10 / value);
} catch (NumberFormatException e) {
System.out.println("Not a valid integer: " + input);
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero");
} catch (Exception e) {
// Catch-all — always last
System.out.println("Unexpected error: " + e.getMessage());
}Multi-catch (Java 7+)
JAVA
try {
// ...
} catch (NumberFormatException | ArithmeticException e) {
System.out.println("Input error: " + e.getMessage());
}Checked vs Unchecked
JAVA
// Checked exception — compiler forces you to handle it
public void readFile(String path) throws IOException {
// Must declare IOException in throws clause OR wrap in try/catch
BufferedReader reader = new BufferedReader(new FileReader(path));
}
// Unchecked (RuntimeException) — no declaration needed
public int divide(int a, int b) {
if (b == 0) throw new IllegalArgumentException("b cannot be zero");
return a / b;
}Custom Exceptions
JAVA
// Checked custom exception — extend Exception
public class InsufficientFundsException extends Exception {
private final double amount;
private final double balance;
public InsufficientFundsException(double amount, double balance) {
super(String.format("Cannot withdraw %.2f — balance is only %.2f", amount, balance));
this.amount = amount;
this.balance = balance;
}
public double getAmount() { return amount; }
public double getBalance() { return balance; }
}
// Unchecked custom exception — extend RuntimeException
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String userId) {
super("User not found: " + userId);
}
}
// Using custom exceptions
public class BankAccount {
private double balance;
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(amount, balance);
}
balance -= amount;
}
}
// Catching custom exception
try {
account.withdraw(500);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
System.out.printf("You need %.2f more%n", e.getAmount() - e.getBalance());
}try-with-resources
Resources that implement AutoCloseable (like files, connections) are automatically closed.
JAVA
// Old way — close manually in finally
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
} finally {
if (reader != null) reader.close();
}
// Modern way — try-with-resources (Java 7+)
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
System.out.println(line);
}
// reader.close() called automatically — even if exception occursFile I/O with java.nio.file
The modern java.nio.file API (Java 7+) is cleaner than the old java.io approach.
Reading Files
JAVA
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
Path path = Path.of("data.txt");
// Read entire file as string
String content = Files.readString(path);
// Read all lines
List<String> lines = Files.readAllLines(path);
for (String line : lines) {
System.out.println(line);
}
// Stream lines (memory-efficient for large files)
try (var stream = Files.lines(path)) {
stream.filter(line -> !line.isBlank())
.forEach(System.out::println);
}Writing Files
JAVA
import java.nio.file.StandardOpenOption;
// Write string to file (creates or overwrites)
Files.writeString(Path.of("output.txt"), "Hello, World!\n");
// Append to file
Files.writeString(
Path.of("log.txt"),
"New log entry\n",
StandardOpenOption.APPEND, StandardOpenOption.CREATE
);
// Write list of lines
List<String> lines = List.of("line 1", "line 2", "line 3");
Files.write(Path.of("lines.txt"), lines);Working with Paths and Directories
JAVA
Path dir = Path.of("data", "reports");
// Create directories
Files.createDirectories(dir);
// Check existence
boolean exists = Files.exists(Path.of("data.txt"));
// Copy, move, delete
Files.copy(Path.of("source.txt"), Path.of("backup.txt"));
Files.move(Path.of("old.txt"), Path.of("new.txt"));
Files.delete(Path.of("temp.txt"));
Files.deleteIfExists(Path.of("maybe.txt")); // no error if missing
// List directory contents
try (var paths = Files.list(Path.of("."))) {
paths.filter(Files::isRegularFile)
.forEach(System.out::println);
}Reading CSV Files
JAVA
import java.nio.file.Files;
import java.nio.file.Path;
Path csvPath = Path.of("students.csv");
// Parse CSV manually
try (var lines = Files.lines(csvPath)) {
lines.skip(1) // skip header
.map(line -> line.split(","))
.forEach(parts -> {
String name = parts[0].trim();
int age = Integer.parseInt(parts[1].trim());
double grade = Double.parseDouble(parts[2].trim());
System.out.printf("%-10s age=%d grade=%.1f%n", name, age, grade);
});
}Reading Properties Files
JAVA
import java.util.Properties;
import java.io.FileReader;
Properties props = new Properties();
try (FileReader reader = new FileReader("config.properties")) {
props.load(reader);
}
String host = props.getProperty("db.host", "localhost");
int port = Integer.parseInt(props.getProperty("db.port", "5432"));Practical Example: Student File Parser
JAVA
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
public record Student(String name, double grade) {}
public class StudentFileParser {
public static List<Student> parse(Path path) throws IOException {
return Files.lines(path)
.skip(1) // header
.filter(line -> !line.isBlank())
.map(StudentFileParser::parseLine)
.collect(Collectors.toList());
}
private static Student parseLine(String line) {
String[] parts = line.split(",");
String name = parts[0].trim();
double grade = Double.parseDouble(parts[1].trim());
return new Student(name, grade);
}
public static void main(String[] args) {
try {
List<Student> students = parse(Path.of("students.csv"));
double avg = students.stream()
.mapToDouble(Student::grade)
.average()
.orElse(0);
Optional<Student> top = students.stream()
.max(Comparator.comparingDouble(Student::grade));
System.out.printf("Average grade: %.2f%n", avg);
top.ifPresent(s -> System.out.println("Top student: " + s.name()));
} catch (IOException e) {
System.err.println("Failed to read file: " + e.getMessage());
}
}
}Key Takeaways
- Prefer unchecked exceptions for programming errors; checked exceptions for recoverable conditions
- Always use try-with-resources for files and connections — it guarantees cleanup
java.nio.file.FileswithPath.of()is the modern way — avoidjava.io.Filein new codeFiles.readString()andFiles.writeString()cover 90% of file needs- Custom exceptions should carry relevant data (amount, user ID) to make error handling actionable
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.