Back to blog
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
Share:𝕏

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
        └── SQLException

try / 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 occurs

File 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

  1. Prefer unchecked exceptions for programming errors; checked exceptions for recoverable conditions
  2. Always use try-with-resources for files and connections — it guarantees cleanup
  3. java.nio.file.Files with Path.of() is the modern way — avoid java.io.File in new code
  4. Files.readString() and Files.writeString() cover 90% of file needs
  5. 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?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.