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.");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:
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()); } }}Buy milkPay the power bill by the 15thPathis 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.Fileshas static methods for everything to do with files.writeStringwrites text (and creates the file if it is not there),readStringreads all of it.IOExceptionis 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());}Lunch: €12.50Taxi: €8.20Book: €18.903 expenses, total €39.60readAllLines 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
APPENDon its own does not create the file. If it is not there, you getNoSuchFileException. 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). readAllLinesandreadStringread the whole file into memory. For a file of several gigabytes it is better to read line by line. See the card onBufferedReader.
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());}writeString without APPEND deletes the old contents and writes the new ones. The first line is gone even before the reading. This is the most common reason for "my file got erased".
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:
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()); }}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")));No file config.txt. Using the default settings.falseFiles.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());}Day 1: €120Day 2: €240Day 3: €360writewrites text,newLine()moves to a new line.readLine()returns the next line, ornullwhen 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
- Path is only the address of the file. On the left is the memory of the program, on the right is the disk.
- newBufferedWriter creates the empty file report.txt and an object with a buffer in memory.
- write and newLine write into the buffer. Nothing reaches the disk yet.
- The file on the disk is still 0 bytes.
- The block ends and try calls close(). The buffer goes to the disk: the file becomes 42 bytes.
- 42 bytes for 36 characters with the new lines: in UTF-8 each Latin letter is 1 byte, and € is 3.
- 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.
BufferedWritercollects 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,BufferedReaderreads a lot at once. - Bytes and characters. Underneath, a file is a sequence of bytes.
InputStreamandOutputStream(for exampleFiles.newInputStream) read and write bytes: for photos, archives, anything that is not text.ReaderandWriterturn bytes into characters by the encoding.BufferedReaderis aReaderwith a buffer. - The older way. In other people's code you will see
new BufferedReader(new FileReader("report.txt"))andnew Scanner(new File("report.txt")). They work, butFilesis newer and more convenient.Scannerreads from a file too, with thehasNextLineandnextLineyou 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.");}parseInt("three") throws, and the "Quantity" line is skipped. On the way out of try, the resource is closed right away, even before catch. That is why "free" comes before "Wrong quantity." By the time catch runs, the printer is already released.Try it
Find the bug
0. Which line has the bug?Pick the line with the bug.
BufferedWriter is never closed. The text is still in the buffer in memory and has not reached the file, so the file is empty. The program gives no error. It quietly loses data. Open it in try (BufferedWriter out = ...) { ... } and read the file after the block: then it is closed and the line is there.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());}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.deletethrowsDirectoryNotEmptyExceptionif the folder is not empty, andNoSuchFileExceptionif it is not there.Files.deleteIfExistsdoes not complain if it is not there.Files.list(dir)is the new way to get the contents of a folder, but it returns aStream, which is in the module "Modern Java".Files.copyandFiles.movecopy and move.- In older code you will see the class
java.io.File:new File("notes.txt")with the methodsexists(),length(),delete(),renameTo(...),createNewFile()andgetAbsolutePath(). It came beforePathandFiles, andpath.toFile()andfile.toPath()turn one into the other. There, appending at the end isnew 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 settingsmax_connections=500log_file_path=/var/log/server.log
db_timeout=30max_connections: 500log_file_path: /var/log/server.logdb_timeout: 302. 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.
ERRORERROR: Failed to load configurationERROR: Null pointer exceptionWrote 2 lines to errors.log3. 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.
backup/document.txtimage.jpgscript.js4. 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.
hello worldworld hellohello again, hellohello1:02:63:03:13More 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