Learnixo

Introduction to Java · Lesson 4 of 5

Exceptions & File I/O

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