Skip to content
wannacode

Files

Problem

The program forgets

You are writing a program for your expenses. You enter them in the evening and close it, and the next day it knows nothing about yesterday. Here is the shortest example:

int visits = 0;
visits++;
System.out.println("Started " + visits + " time.");
Output
Started 1 time.

No matter how many times you run it, it says "1 time". Variables live in memory while the program runs. When it ends, the memory is freed and everything disappears.

To remember, the program has to write the data where it stays after the program ends: in a file on the disk.

The idea

Path and file

Here is a whole program that writes a note to a file and reads it back:

Main.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) {
Path notes = Path.of("notes.txt");
try {
Files.writeString(notes, "Buy milk\nPay the power bill by the 15th\n");
String text = Files.readString(notes);
System.out.print(text);
} catch (IOException e) {
System.out.println("Problem with the file: " + e.getMessage());
}
}
}
Output
Buy milk
Pay the power bill by the 15th
  • Path is the path to the file: "where it is". Path.of("notes.txt") is a file with this name in the folder the program was started from.
  • Files has static methods for everything to do with files. writeString writes text (and creates the file if it is not there), readString reads all of it.
  • IOException is a checked exception, from the module on exceptions. The disk may be full, the file may be locked or missing, and the compiler makes you say what should happen then.

Run the program, then open the folder: notes.txt is there, and you can open it with any text editor. The next run will find it.

In the examples from here on, the imports are not shown. Everything is from java.nio.file and java.io, and List is from java.util.

The idea

Add and read the lines

Expenses are added one by one. StandardOpenOption.APPEND adds at the end, instead of writing the file from scratch. readAllLines reads the file as lines:

Path expenses = Path.of("expenses.txt");
try {
Files.writeString(expenses, "Lunch;12.50\n");
Files.writeString(expenses, "Taxi;8.20\n", StandardOpenOption.APPEND);
Files.writeString(expenses, "Book;18.90\n", StandardOpenOption.APPEND);
List<String> lines = Files.readAllLines(expenses);
double total = 0;
for (String line : lines) {
String[] parts = line.split(";");
total += Double.parseDouble(parts[1]);
System.out.println(parts[0] + ": €" + parts[1]);
}
System.out.printf("%d expenses, total €%.2f%n", lines.size(), total);
} catch (IOException e) {
System.out.println("Problem with the file: " + e.getMessage());
}
Output
Lunch: €12.50
Taxi: €8.20
Book: €18.90
3 expenses, total €39.60

readAllLines returns a List<String>: a list of strings, one for each line. Lists are in the next module, "Collections". For now you need only two things: going through it with for-each, like an array, and size() for the number of lines.

More precisely, if you are curious
  • APPEND on its own does not create the file. If it is not there, you get NoSuchFileException. For "create or append", pass both: StandardOpenOption.CREATE, StandardOpenOption.APPEND.
  • Since Java 18, text in files is in UTF-8 by default, and letters like "é" and "€" are read and written correctly. In older versions it depended on the computer's settings. If you read a file from another system, you can give the encoding explicitly: Files.readString(path, StandardCharsets.UTF_8).
  • readAllLines and readString read the whole file into memory. For a file of several gigabytes it is better to read line by line. See the card on BufferedReader.

Try it

Two shifts

What will the program print?

Path shift = Path.of("shift.txt");
try {
Files.writeString(shift, "First shift: Ivo\n");
Files.writeString(shift, "Second shift: Mila\n");
System.out.print(Files.readString(shift));
} catch (IOException e) {
System.out.println(e.getMessage());
}
Answer

The idea

When the file is not there

The program's settings are in config.txt, and someone deleted it. If nobody catches the exception:

Main.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
static String readConfig() throws IOException {
return Files.readString(Path.of("config.txt"));
}
public static void main(String[] args) throws IOException {
System.out.println(readConfig());
}
}
Error
Exception in thread "main" java.nio.file.NoSuchFileException: config.txt
at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92)
...
at java.base/java.nio.file.Files.readString(Files.java:3318)
at Main.readConfig(Main.java:7)
at Main.main(Main.java:11)

The lines from java.base are the insides of Java (here they are shortened with "..."). Your code is at the bottom: line 7 in readConfig, called from line 11 in main. main has also declared throws IOException, so the exception reaches the JVM and the program stops.

NoSuchFileException is a subclass of IOException. Catch it separately, before the general case:

static String readConfig() throws IOException {
return Files.readString(Path.of("config.txt"));
}
try {
System.out.println(readConfig());
} catch (NoSuchFileException e) {
System.out.println("No file " + e.getMessage() + ". Using the default settings.");
} catch (IOException e) {
System.out.println("Cannot read the settings: " + e.getMessage());
}
System.out.println(Files.exists(Path.of("config.txt")));
Output
No file config.txt. Using the default settings.
false

Files.exists(path) checks whether the file exists. The check does not replace catch: the file may disappear between the check and the reading, and there are other reasons the reading may fail.

The idea

Line by line and try-with-resources

For a big file, or for writing in parts, there are BufferedWriter and BufferedReader. They keep the file open while you work with it, so they have to be closed at the end:

Path report = Path.of("report.txt");
try (BufferedWriter out = Files.newBufferedWriter(report)) {
for (int day = 1; day <= 3; day++) {
out.write("Day " + day + ": €" + day * 120);
out.newLine();
}
} catch (IOException e) {
System.out.println("Cannot write: " + e.getMessage());
}
try (BufferedReader in = Files.newBufferedReader(report)) {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("Cannot read: " + e.getMessage());
}
Output
Day 1: €120
Day 2: €240
Day 3: €360
  • write writes text, newLine() moves to a new line.
  • readLine() returns the next line, or null when the file ends.
  • try (...) with a resource created in the parentheses is try-with-resources. When the block ends, in whatever way, the resource closes by itself.

Closing is not a formality: while the BufferedWriter is open, the text may still be in its buffer, not on the disk. To keep the code short, here main has throws IOException:

Path report = Path.of("report.txt");
try (BufferedWriter out = Files.newBufferedWriter(report)) {
for (int day = 1; day <= 3; day++) {
out.write("Day " + day + ": €" + day * 120);
out.newLine();
}
System.out.println("In the block: " + Files.size(report) + " bytes");
}
System.out.println("After the block: " + Files.size(report) + " bytes");
for (String line : Files.readAllLines(report)) {
System.out.println(line);
}
Step by step, in words
  1. Path is only the address of the file. On the left is the memory of the program, on the right is the disk.
  2. newBufferedWriter creates the empty file report.txt and an object with a buffer in memory.
  3. write and newLine write into the buffer. Nothing reaches the disk yet.
  4. The file on the disk is still 0 bytes.
  5. The block ends and try calls close(). The buffer goes to the disk: the file becomes 42 bytes.
  6. 42 bytes for 36 characters with the new lines: in UTF-8 each Latin letter is 1 byte, and € is 3.
  7. readAllLines reads the three lines back from the disk.

We promised this in the lesson on finally. Here is the same writing with finally:

BufferedWriter out = null;
try {
out = Files.newBufferedWriter(report);
out.write("Day 1: €120");
} catch (IOException e) {
System.out.println("Cannot write: " + e.getMessage());
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
System.out.println("Cannot close: " + e.getMessage());
}
}
}

Fifteen lines for one write, and close() can also throw an IOException. try (...) does exactly this for you.

More precisely, if you are curious
  • The parentheses can hold several resources, separated with ;. They are closed in reverse order: the last one opened is closed first.
  • Buffer. BufferedWriter collects the text in memory and sends it to the disk in big chunks. Each separate write to the disk is slow, and this way there are few of them. In the same way, BufferedReader reads a lot at once.
  • Bytes and characters. Underneath, a file is a sequence of bytes. InputStream and OutputStream (for example Files.newInputStream) read and write bytes: for photos, archives, anything that is not text. Reader and Writer turn bytes into characters by the encoding. BufferedReader is a Reader with a buffer.
  • The older way. In other people's code you will see new BufferedReader(new FileReader("report.txt")) and new Scanner(new File("report.txt")). They work, but Files is newer and more convenient. Scanner reads from a file too, with the hasNextLine and nextLine you already know:
try (Scanner in = new Scanner(Path.of("report.txt"))) {
while (in.hasNextLine()) {
System.out.println(in.nextLine());
}
}

Try it

When does it close?

Any class that implements the AutoCloseable interface (the close() method) can go in a try (...). Here is the receipt printer from the lesson on finally, as a resource:

What will the program print?

class Printer implements AutoCloseable {
Printer() {
System.out.println("The printer is busy.");
}
public void close() {
System.out.println("The printer is free.");
}
}
try (Printer printer = new Printer()) {
System.out.println("Printing the receipt...");
int qty = Integer.parseInt("three");
System.out.println("Quantity: " + qty);
} catch (NumberFormatException e) {
System.out.println("Wrong quantity.");
}
Answer

Try it

Find the bug

The program writes an order and counts the lines in the file. It prints 0. Which line has the bug?

Pick the line with the bug.

The idea

Folders

The register's backups go into a folder, one per day:

Path backup = Path.of("backup");
try {
Files.createDirectories(backup.resolve("old"));
Files.writeString(backup.resolve("2026-10-01.csv"), "Café,2.40\n");
Files.writeString(backup.resolve("2026-10-02.csv"), "Café,2.40\nTea,1.80\n");
String[] names = backup.toFile().list();
Arrays.sort(names);
for (String name : names) {
Path p = backup.resolve(name);
if (Files.isDirectory(p)) {
System.out.println(name + "/");
} else {
System.out.println(name + " (" + Files.size(p) + " bytes)");
}
}
Files.delete(backup.resolve("2026-10-01.csv"));
System.out.println(Files.exists(backup.resolve("2026-10-01.csv")));
} catch (IOException e) {
System.out.println(e.getMessage());
}
Output
2026-10-01.csv (11 bytes)
2026-10-02.csv (20 bytes)
old/
false
Method What it does
Files.createDirectories(p) creates the folder and all the folders above it, if they are not there
dir.resolve("name") the path to name inside dir
Files.isDirectory(p), Files.exists(p) is it a folder, is it there
Files.size(p) the size in bytes
Files.delete(p) deletes a file or an empty folder
dir.toFile().list() an array with the names of everything in the folder

The sizes are 11 and 20 bytes, though there are fewer characters: in UTF-8, the "é" in "Café" is 2 bytes.

list() gives the names in no particular order, so we sort them with java.util.Arrays.sort.
More precisely, if you are curious
  • Files.delete throws DirectoryNotEmptyException if the folder is not empty, and NoSuchFileException if it is not there. Files.deleteIfExists does not complain if it is not there.
  • Files.list(dir) is the new way to get the contents of a folder, but it returns a Stream, which is in the module "Modern Java". Files.copy and Files.move copy and move.
  • In older code you will see the class java.io.File: new File("notes.txt") with the methods exists(), length(), delete(), renameTo(...), createNewFile() and getAbsolutePath(). It came before Path and Files, and path.toFile() and file.toPath() turn one into the other. There, appending at the end is new FileWriter("output.txt", true).
  • The path "notes.txt" is relative: to the folder the program was started from. Path.of("/home/ana/notes.txt") is absolute. path.toAbsolutePath() shows exactly where the file is.

Exercise

Programs that remember

Solve the tasks in your editor. In each task, the program first creates the input file itself with Files.writeString, so you can try it right away.

1. A configuration file. Create server.conf with these contents, then read it and print each setting as key: value. Skip the empty lines and the lines that start with #. Hint: split("=", 2) splits only at the first =.

server.conf
# server settings
max_connections=500
log_file_path=/var/log/server.log
db_timeout=30
Output
max_connections: 500
log_file_path: /var/log/server.log
db_timeout: 30

2. A log file filter. Create app.log with four lines (two INFO and two ERROR). Read a level from the keyboard, for example ERROR, and write the lines with that level to errors.log with a BufferedReader and a BufferedWriter in one try (...). Print errors.log and how many lines you wrote.

Input
ERROR
Output
ERROR: Failed to load configuration
ERROR: Null pointer exception
Wrote 2 lines to errors.log

3. Folder contents. Create a folder project with the files document.txt, image.jpg, script.js and the folder backup. Write their names, sorted in alphabetical order, to directory_contents.txt, with a / after the names of folders. Print the file.

directory_contents.txt
backup/
document.txt
image.jpg
script.js

4. An index file. Create text.txt with the three lines below. Read a word from the keyboard and write to index.txt each place it appears, in the form line:position: the line number (from 1) and the index in the line (from 0). If the word is on one line twice, that is two entries.

text.txt
hello world
world hello
hello again, hello
Input
hello
index.txt
1:0
2:6
3:0
3:13

More tasks

  • Run counter. Fix the program from the first card: count the runs in visits.txt. If the file is not there, start from 0.
  • Find and replace in a file. The task from the lesson "Search and replace": read a file, replace "old" with "new", and write the result to a new file.
  • Dates from a file. The date task from the lesson "Regular expressions": read the dates from a file, one per line.
  • A copy with line numbers. Read a file and write a copy in which each line starts with its number: 1: ..., 2: ....

Test

Check yourself

1. Why does the compiler want a try/catch around Files.readString?
2. Files.writeString(file, "A\n"); then Files.writeString(file, "B\n"); What is in the file?
3. When does try-with-resources call close()?
4. What does in.readLine() return when the file ends?
5. You write with a BufferedWriter, but you do not close it. What may be in the file?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·