File Navigation and I/O ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-31

File Navigation and I/O. Practical Tasks

Write the euro sign to a file through FileOutputStream and read it back: instead of € you get ¬, and no exception is thrown anywhere:

try (OutputStream out = new FileOutputStream("a.txt")) {
    out.write('€');                        // write(int) keeps only the low byte: 0x20AC -> 0xAC
}
try (InputStream in = new FileInputStream("a.txt")) {
    System.out.println((char) in.read());  // Output: ¬
}

These Java I/O streams exercises practice the module «Java I/O»: you will rewrite byte-by-byte file copying with try-with-resources, copy a text file line by line with BufferedReader and BufferedWriter, walk a directory tree recursively with the File class, and write reports to a file with PrintWriter in two course projects — the Employee Report and the Online Store. All six tasks have hints, and the section after the tasks lists the mistakes that leave a file empty, truncated or garbled without a single exception.

What to Review Before You Start

The six tasks need three skills: reading and writing byte and character streams, closing them automatically with try-with-resources, and listing a directory with the File class. If any of them feels shaky, open the lesson section from the table first — each rule is explained there with a runnable example.

Lesson What you need from it Tasks
Java Byte Streams: FileInputStream and FileOutputStream FileInputStream, FileOutputStream, reading until -1 FileCopy, FileInputOutputStreamDemo
Try-with-resources: multiple resources Closing one or several streams automatically instead of finally All tasks
BufferedReader and BufferedWriter readLine(), newLine(), FileReader and FileWriter with a charset Copy a Text File Line by Line
PrintWriter print(), println(), printf() and checkError() Employee Report, Purchase Receipt
Java File Class: working with directories listFiles() and isDirectory() for a recursive walk Recursive Directory Listing
Java NIO.2: walking a directory Optional: Files.walk() and Files.newBufferedReader() to compare with your solution Line by Line, Directory Listing
Java I/O Streams: open, read or write, close The general pattern of working with any stream All tasks

FileCopy: Rewrite with try-with-resources

Rewrite the class FileCopy, which copies a file byte by byte:

package io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy {
    public static void main(String[] args) throws IOException {
        FileInputStream fileIn = null;
        FileOutputStream fileOut = null;

        try {
            fileIn = new FileInputStream("src/io/file.txt");
            fileOut = new FileOutputStream("src/io/copied_file.txt");

            int a;
            while ((a = fileIn.read()) != -1) {
                fileOut.write(a);
            }
        } finally {
            if (fileIn != null) {
                fileIn.close();
            }
            if (fileOut != null) {
                fileOut.close();
            }
        }
    }
}
  1. Replace the manual closing of the streams in the finally block with try-with-resources.
  2. Add a catch block that handles IOException and remove throws IOException from the signature of main.

Hint

Declare both streams in one pair of parentheses, separated by a semicolon: try (FileInputStream fileIn = new FileInputStream(...); FileOutputStream fileOut = new FileOutputStream(...)). They are closed automatically in reverse order, even if copying fails, so the null checks and the finally block are no longer needed.

FileInputOutputStreamDemo: Write and Read Bytes

Rewrite the class FileInputOutputStreamDemo:

package io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;

public class FileInputOutputStreamDemo {
    public static void main(String[] args) {
        try {
            char[] symbols = {'a', 'b', 'c'};
            OutputStream output = new FileOutputStream("a.txt");
            for (int i = 0; i < symbols.length; i++) {
                // write each character to the text file
                output.write(symbols[i]);
            }
            output.close();

            InputStream input = new FileInputStream("a.txt");
            int size = input.available();

            for (int i = 0; i < size; i++) {
                // read the text file character by character
                System.out.print((char) input.read() + " ");
            }
            input.close();
        } catch (IOException e) {
            System.out.print("Exception");
        }
    }
}
  1. Rewrite the code with try-with-resources for OutputStream and InputStream.
  2. Replace the writing loop for (int i = 0; i < symbols.length; i++) with a for-each loop.
  3. Write the file a.txt to the src/io directory: the file path is "src/io/a.txt".

Expected output: a b c

A subtle point

It is easier to write and read the file in two consecutive try-with-resources blocks: then the output stream is guaranteed to be closed before reading starts. The reading loop cannot become a for-each, and available() is not required to return the full amount of data — it is safer to read until the end of the stream: while ((b = input.read()) != -1).

Copy a Text File Line by Line

  1. Write a class that copies the contents of one text file to another, line by line.
  2. Read with BufferedReader on top of FileReader, and write with BufferedWriter on top of FileWriter.
  3. Close the streams with try-with-resources.

The readLine() catch

readLine() returns a line without its line terminator, and null at the end of the file. Call newLine() after every line you write, or the whole text is glued into one line. newLine() writes the line separator of the current system and also adds one after the last line, so the copy can be slightly larger than the original. To keep characters such as é or ü intact, pass the charset explicitly: new FileReader(file, StandardCharsets.UTF_8) (Java 11+).

Write the Employee Report to a File

Employee Report · part 4 of 4 · ← part 2

  1. Add a method to the Employee class that writes the employee report to a file.
  2. Use the PrintWriter class.

Where to start

PrintWriter has the same print(), println() and printf() methods as System.out: if the report is already printed to the console, it is enough to send the output to a file instead. The write methods of PrintWriter never throw IOException — call checkError() after writing.

Recursive Directory Listing with the File Class

  1. Write a method that recursively walks a given directory and prints information about all files and directories in it and in all of its subdirectories.
  2. Use the File class and recursion.

Hint

Pass the method a File and a nesting level: for every element of listFiles() that is a directory, call the same method with the level increased by one, and build the indentation of the output from the level. listFiles() returns null if the path is not a directory or cannot be read — without a check you get NullPointerException (see Working with directories).

Save the Purchase Receipt to a File

Online Store · part 8 of 8 · ← part 7

Earlier parts of the project: part 1, part 2, part 3, part 4, part 5, part 6, part 7.

  1. After the purchase is completed, write a report on the contents of the shopping cart to a file.
  2. Use the PrintWriter class to write and format the report.

How to lay out the receipt

Open the PrintWriter in try-with-resources so that the contents of the buffer are guaranteed to reach the file. Print every cart item with printf() and fixed column widths, for example "%-20s %5d %10.2f%n", and the total at the end. The decimal separator of %.2f depends on the default locale (1500,78 on a German system), so pass the locale as the first argument for a stable result: writer.printf(Locale.US, ...).

Where I/O Solutions Fail Silently

Most broken I/O solutions fail without an exception: the program finishes, but the file is empty, truncated or full of wrong characters. The cause is almost always the wrong kind of stream, a buffer that was never flushed, or the default charset. The table lists the typical slips and how to fix each one.

Mistake What happens How to fix it
outputStream.write('€') Only the low byte is written (write(int)), so the character is read back as ¬ Write text through a Writer with an explicit charset: new FileWriter(file, StandardCharsets.UTF_8)
File size from input.available() The method is not required to return the whole size; for a network stream it often returns 0 Read until the end of the stream: while ((b = in.read()) != -1) — see byte stream mistakes
BufferedWriter or PrintWriter without close() The data stays in the buffer; the file is empty or truncated Open the stream in try-with-resources — see Which stream to close
Closing two streams one after another in finally If the first close() throws IOException, the second stream is never closed Declare both streams in one try-with-resources
No check after writing with PrintWriter A write error (a full disk, for example) does not throw IOException — the report is lost silently Call checkError() after writing
for (File f : dir.listFiles()) without a check NullPointerException if the path is not a directory or cannot be read Check the result of listFiles() for null
new FileWriter("report.txt") without a charset Up to Java 17 the platform charset is used (windows-1252 on US and Western European Windows), since Java 18 (JEP 400) — UTF-8; the same file opens differently on different machines Pass StandardCharsets.UTF_8 explicitly (the constructors with a Charset exist since Java 11)

Check your code

Before you call a task done, go through the code style checklist: name stream variables by their role (reader, writer, not a and b), leave no empty catch blocks, and do not declare throws IOException on main when the task asks you to handle the exception.

Frequently Asked Questions

In what order should I solve these Java I/O exercises?

Start with FileCopy and FileInputOutputStreamDemo: they replace manual close() calls in finally with try-with-resources, and every later task relies on that. Then copy a text file line by line, list a directory recursively, and finish with the two project tasks — the employee report and the online store receipt written with PrintWriter — which build on the earlier parts of those projects.

Why not just use Files.copy() in these tasks?

In real code you can: Files.copy(Path.of("src/io/file.txt"), Path.of("src/io/copy.txt"), StandardCopyOption.REPLACE_EXISTING) copies a file in one line (Files.copy since Java 7, Path.of since Java 11). The tasks ask for a manual loop on purpose, so that you see how read() signals the end of a stream, why a buffer must be flushed and how the streams get closed. After solving, compare your code with Create, copy, move and delete files.

Where should I put src/io/file.txt so the program finds it?

Under the working directory of the JVM, which in IntelliJ IDEA is the project root by default; System.getProperty("user.dir") prints it. If the src/io directory does not exist, new FileOutputStream("src/io/a.txt") throws FileNotFoundException: an output stream creates the file but never its parent directories, so create them first with new File("src/io").mkdirs(). More on relative paths — in File class constructors.

Can I solve these exercises on Java 8?

Yes, but without a few shortcuts. The FileReader and FileWriter constructors that take a Charset appeared in Java 11, Path.of in Java 11, and an existing variable inside try (reader) in Java 9. On Java 8 wrap the byte stream yourself: new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8), or use Files.newBufferedReader(path), which reads UTF-8 by default.

Comments

Please log in or register to have a possibility to add comment.