Real applications read configuration, write logs, and process uploaded files. Java's I/O classes handle all of this through the concept of streams - a sequence of data flowing in or out.

Writing to a file

try (BufferedWriter writer = new BufferedWriter(new FileWriter("notes.txt"))) {
    writer.write("Learning Java I/O");
    writer.newLine();
    writer.write("Second line");
} catch (IOException e) {
    System.out.println("Failed to write: " + e.getMessage());
}

Reading from a file

try (BufferedReader reader = new BufferedReader(new FileReader("notes.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    System.out.println("Failed to read: " + e.getMessage());
}

Why try-with-resources

The try (... = ...) syntax automatically closes the file (or any Closeable resource) when the block finishes, even if an exception is thrown - before this existed, developers had to remember to close resources manually in a finally block, and it was easy to forget and leak file handles.

Reading a file more simply

For smaller files, Files (from java.nio.file) offers a shorter option:

List<String> lines = Files.readAllLines(Path.of("notes.txt"));
lines.forEach(System.out::println);