Learn Java

Lesson 49Phase 5: Errors, files and the standard library

Path and Files: modern file handling

Reading and writing whole files in one call, walking folders, and processing a CSV with the java.nio.file API.

By Updated 45 min

  • Path
  • Path.of
  • Files.readAllLines
  • Files.readString
  • Files.writeString
  • Files.lines

In the last few lessons you read files with a BufferedReader and wrote them with a BufferedWriter. Those classes still work, but they make you write the same loop and the same try-with-resources block every time. Java has a newer pair for files, Path and Files, that does each everyday job in a single call: read a whole file, write a whole file, add a line to the end, copy, move, delete. This lesson teaches the pair, then puts it to work on a real task: turning a CSV file of grades into a report.

By the end of this lesson
  • Name a file with Path.of, from one part or several, and explain why making a Path never touches the disk.
  • Read a whole file with Files.readString or Files.readAllLines, and say what each one gives back.
  • Write a file with Files.writeString and Files.write, replacing it or adding to its end.
  • Catch the NoSuchFileException a missing file throws, and check for a file with Files.exists.
  • Read a CSV file, split each line into fields, skip the lines that are broken, and write a report file.
  • Copy, move and delete files, and list what is inside a folder.
From before
You read files line by line in reading files, wrote them in writing files, and learned try/catch, checked exceptions and IOException in exception handling. Three smaller tools come back too: split, which cuts a String into an array (you used it to count words in HashMap and HashSet), Integer.parseInt and its NumberFormatException from wrapper classes, and lists from ArrayList.

One call instead of a loop#

Start with a job you already know: read every line of shopping.txt into a list. The program below does it twice. The first half is the java.io way from the reading files lesson. The second half is the new way. Press Run and step through. Watch oldWay grow by one line on each pass of the loop, then watch newWay arrive whole, in a single step.

Main.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4import java.nio.file.Files;
5import java.nio.file.Path;
6import java.util.ArrayList;
7import java.util.List;
8
9public class Main {
10 public static void main(String[] args) throws IOException {
11 // The java.io way, from the reading files session
12 List<String> oldWay = new ArrayList<>();
13 try (BufferedReader reader = new BufferedReader(new FileReader("shopping.txt"))) {
14 String line = reader.readLine();
15 while (line != null) {
16 oldWay.add(line);
17 line = reader.readLine();
18 }
19 }
20
21 // The java.nio.file way
22 List<String> newWay = Files.readAllLines(Path.of("shopping.txt"));
23
24 System.out.println(oldWay);
25 System.out.println(newWay);
26 System.out.println(oldWay.equals(newWay));
27 }
28}
Files before the program runs
shopping.txt
bread
eggs
coffee
Prints
›[bread, eggs, coffee]
›[bread, eggs, coffee]
›true

Both lists print as [bread, eggs, coffee], and equals prints true. The first version needed a reader wrapped around another reader, a loop with two readLine calls, a null check and a try-with-resources block. The second version is one line. Files.readAllLines opens the file, reads every line into a list, and closes the file again before it gives you the list.

Two names do that work, and both live in the package java.nio.file. A Path holds the name and location of a file. Files is a class made only of static methods that act on files: reading, writing, copying, moving, deleting and more. You never make a Files object. You call its methods on the class name, as in Files.readAllLines(...), the same way you call Math.max(...).

Key idea

The Files methods that read or write a whole file do the job in one call. Each one opens the file, reads or writes all of it, and closes it again before it returns, so there is nothing left for you to close.

The imports
Every program in this lesson starts with import java.nio.file.Files; and import java.nio.file.Path;. The exception class IOException still comes from java.io, so that import stays.

Path: the name of a place on disk#

A Path is the name of a place on disk, written the way the operating system writes it: a file name on its own, or folder names and a file name joined by a separator. You make one with Path.of, giving it the whole name or the parts one by one. This program starts with one file on its disk, notes.txt.

Paths.java
1import java.nio.file.Files;
2import java.nio.file.Path;
3
4public class Main {
5 public static void main(String[] args) {
6 Path notes = Path.of("notes.txt");
7 Path march = Path.of("reports", "2026", "march.csv");
8
9 System.out.println(notes);
10 System.out.println(march);
11 System.out.println(march.getFileName());
12 System.out.println(march.getParent());
13
14 Path reports = Path.of("reports");
15 Path april = reports.resolve("april.csv");
16 System.out.println(april);
17
18 System.out.println(Files.exists(notes));
19 System.out.println(Files.exists(march));
20 }
21}
Files before the program runs
notes.txt
Buy milk
Walk the dog
Prints
›notes.txt
›reports/2026/march.csv
›march.csv
›reports/2026
›reports/april.csv
›true
›false

Line by line:

  • Path.of("notes.txt") names a file called notes.txt. Printing a Path prints the name.
  • Path.of("reports", "2026", "march.csv") joins its parts with the separator your system uses, so it prints reports/2026/march.csv here. On Windows the same line prints reports\2026\march.csv, and your code does not have to change.
  • getFileName() gives the last part, march.csv. getParent() gives everything before it, reports/2026. A path with only one part, like notes.txt, has no parent, and getParent() returns null.
  • reports.resolve("april.csv") joins a folder path and a name: reports/april.csv. It is handy when one folder holds many files.
  • Files.exists is the first line that looks at the disk. It prints true for notes.txt, which is there, and false for march, which is not.

Look at march again. It names a file inside two folders that do not exist, and Java made the Path anyway, printed it, and took it apart. A Path is like the address on an envelope: writing the address does not build the house, and it does not check that the house is there. Nothing happens on disk until you hand the Path to a Files method. Step through again and watch the Files section of the memory view. It shows notes.txt the whole time, and no other file ever appears.

Key idea

A Path names a place on disk; it is not the file. Making one never opens, creates or checks anything, so you can make a Path for a file that does not exist yet.

Relative and absolute paths

Every path in this lesson is relative: notes.txt means the file called notes.txt in the program's working directory, the folder it runs in, as you saw in writing files. An absolute path starts from the top of the disk, like /Users/ada/notes.txt on a Mac or C:\Users\ada\notes.txt on Windows. A program with an absolute path written into it only works on computers that have that exact folder, so prefer relative paths, built from parts with Path.of, and let Java choose the separator.

Quick check
There is no folder called data on the disk. What does Path data = Path.of("data", "grades.csv"); do?

Reading a whole file#

Files gives you two ways to read a whole file. Files.readString(path) gives you everything in it as one String. Files.readAllLines(path) cuts the text at its line breaks and gives you a List<String>, one element per line. Here is the same recipe read both ways.

Recipe.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.util.List;
5
6public class Main {
7 public static void main(String[] args) throws IOException {
8 Path recipe = Path.of("pancakes.txt");
9
10 String text = Files.readString(recipe);
11 System.out.println("readString: " + text.length() + " characters");
12 System.out.print(text);
13
14 List<String> lines = Files.readAllLines(recipe);
15 System.out.println("readAllLines: " + lines.size() + " lines");
16 for (int i = 0; i < lines.size(); i++) {
17 System.out.println((i + 1) + ". " + lines.get(i));
18 }
19 }
20}
Files before the program runs
pancakes.txt
2 eggs
1 cup of flour
1 cup of milk
Prints
›readString: 36 characters
›2 eggs
›1 cup of flour
›1 cup of milk
›readAllLines: 3 lines
›1. 2 eggs
›2. 1 cup of flour
›3. 1 cup of milk

readString found 36 characters: the 33 letters, digits and spaces you can see, plus three line breaks, because a line break is a character too. That is why System.out.print, with no ln, still prints the recipe on three lines: the line breaks are inside the text. readAllLines used those same line breaks to decide where each element ends and left them out, so the list holds three clean lines, ready for a loop.

Files.readString(path)
Files.readAllLines(path)
Gives back one String.
Gives back a List<String>, one element per line.
The line breaks stay inside the text.
The line breaks are taken out; they only mark where each element ends.
Use it when you want the text as a whole: to print it, search it or count it.
Use it when you work line by line, for example a file with one record per line.
Both read the whole file into memory
readString and readAllLines load the entire file before they return. For notes, settings, a class list or a CSV of a few thousand lines, that is no problem at all. A log file of several gigabytes may not fit in memory, and readString fails with an OutOfMemoryError on any file larger than about 2 GB. For files that big, read a line at a time instead.

Very big files: Files.lines

Files.lines(path) reads a file lazily: it fetches the next line only when it is needed, so memory holds a few lines at a time, however long the file is. It hands you the lines as a Stream<String>, a sequence you process with a chain of steps. Streams get a lesson of their own later. Here is just enough of them to count the error lines in a server log.

LogCheck.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.util.stream.Stream;
5
6public class Main {
7 public static void main(String[] args) throws IOException {
8 try (Stream<String> lines = Files.lines(Path.of("server.log"))) {
9 long errors = lines.filter(line -> line.startsWith("ERROR")).count();
10 System.out.println(errors + " lines start with ERROR");
11 }
12 }
13}
Files before the program runs
server.log
INFO  server started
INFO  user ada logged in
ERROR disk almost full
INFO  user ada logged out
ERROR disk full
Prints
›2 lines start with ERROR

filter keeps the lines that pass the test in the lambda, and count counts what is left, so the program prints 2 lines start with ERROR. One rule matters even before you learn streams. readAllLines closes the file before it returns, but Files.lines keeps the file open while you work through the lines, so the stream must be closed when you are done. Opening it in a try-with-resources block, as here, closes it for you, exactly like the readers in reading files.

Streams get their own lesson
filter, count and the rest of the stream steps are the subject of streams, later in the course. Until then, readAllLines and a for loop can handle every file in this course.

When the file is not there#

Any Files method that reads a file can fail: the file may be missing, or you may not have permission to read it. Every one of these failures is an IOException, the checked exception from exception handling, so the compiler makes you either catch it or declare throws IOException. The programs so far declared it on main, which means a missing file would crash them. This program catches the failure instead and carries on. It starts with an empty disk.

Settings.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.NoSuchFileException;
4import java.nio.file.Path;
5
6public class Main {
7 public static void main(String[] args) {
8 Path settings = Path.of("settings.txt");
9
10 try {
11 String text = Files.readString(settings);
12 System.out.println("Loaded: " + text);
13 } catch (NoSuchFileException e) {
14 System.out.println("No file called " + e.getMessage() + ", so using the defaults.");
15 } catch (IOException e) {
16 System.out.println("Could not read the settings: " + e);
17 }
18 System.out.println("The program carries on.");
19 }
20}
Prints
›No file called settings.txt, so using the defaults.
›The program carries on.

There is no settings.txt, so readString throws a NoSuchFileException. Its message is the path itself, settings.txt, which the catch block puts into a sentence before the program goes on with default settings. The older FileReader reports the same problem as a FileNotFoundException; the Files methods use NoSuchFileException.

NoSuchFileException belongs to the IOException family, so the second block, catch (IOException e), would catch it too. Catching it by name first lets you treat “the file is not there”, which is often normal (on a program's very first run, say), differently from other read errors, which usually mean something has gone wrong.

The specific catch goes first
Put catch (NoSuchFileException e) above catch (IOException e). In the other order, the IOException block would already catch every missing file, the second block could never run, and javac refuses the program with error: exception NoSuchFileException has already been caught.

Asking first with Files.exists

You can also ask before you read. Files.exists(path) returns true when there is a file at that path. This version starts with a settings.txt on its disk. Open it full screen, change the file name in the code to one that is not there, and run it again to see the other branch.

Settings.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 Path settings = Path.of("settings.txt");
8
9 if (Files.exists(settings)) {
10 System.out.println("Loaded: " + Files.readString(settings));
11 } else {
12 System.out.println("No settings yet, so using the defaults.");
13 }
14 }
15}
Files before the program runs
settings.txt
theme=dark
Prints
›Loaded: theme=dark

Writing a whole file#

Writing takes one call too. Files.writeString(path, text) creates the file if it is missing, throws away everything in it if it exists, writes your text, and closes the file. To add to the end instead, pass a third argument: StandardOpenOption.APPEND. This program starts with a diary.txt that already holds one line. Step through it and watch the Files section of the memory view, where the file a step changes is outlined.

Diary.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.nio.file.StandardOpenOption;
5
6public class Main {
7 public static void main(String[] args) throws IOException {
8 Path diary = Path.of("diary.txt");
9
10 Files.writeString(diary, "Monday: started the course\n");
11 Files.writeString(diary, "Tuesday: wrote my first file\n", StandardOpenOption.APPEND);
12 System.out.print(Files.readString(diary));
13
14 System.out.println("---");
15 Files.writeString(diary, "Wednesday: oops\n");
16 System.out.print(Files.readString(diary));
17 }
18}
Files before the program runs
diary.txt
Sunday: nothing yet
Prints
›Monday: started the course
›Tuesday: wrote my first file
›---
›Wednesday: oops

The first writeString wiped out the Sunday line and wrote Monday's. The call with APPEND added Tuesday's line after it. The last call, without APPEND, replaced both with Wednesday's line, so the second print shows only Wednesday: oops. It is the same choice you made in writing files between new FileWriter(name) and new FileWriter(name, true), written as an option instead of a true. writeString writes exactly the characters you give it and adds no line break of its own, so each string here ends with \n.

Key idea

Files.writeString replaces the whole file. Pass StandardOpenOption.APPEND when you want to keep what is there and add to the end.

Predict the outputScores.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.nio.file.StandardOpenOption;
5
6public class Main {
7 public static void main(String[] args) throws IOException {
8 Path scores = Path.of("scores.txt");
9 Files.writeString(scores, "Ada 91\n");
10 Files.writeString(scores, "Grace 88\n");
11 Files.writeString(scores, "Linus 77\n", StandardOpenOption.APPEND);
12 System.out.print(Files.readString(scores));
13 }
14}

This program writes to scores.txt three times. What does it print at the end?

A list of lines with Files.write

When your text is already a list of lines, Files.write(path, lines) writes each element and ends each one with a line break. Here is the to-do file from writing files once more. There it took a BufferedWriter, a loop of write and newLine calls, and a try-with-resources block. Here the loop only builds the list, and one call writes the file.

SaveTodos.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.util.ArrayList;
5import java.util.List;
6
7public class Main {
8 public static void main(String[] args) throws IOException {
9 String[] tasks = {"Buy milk", "Call Sam", "Finish notes"};
10
11 List<String> lines = new ArrayList<>();
12 lines.add("To do");
13 for (String task : tasks) {
14 lines.add("- " + task);
15 }
16
17 Files.write(Path.of("todos.txt"), lines);
18 System.out.println(Files.readAllLines(Path.of("todos.txt")));
19 }
20}
Prints
›[To do, - Buy milk, - Call Sam, - Finish notes]

The file appears in the Files section with four lines, and readAllLines reads them back as [To do, - Buy milk, - Call Sam, - Finish notes]. Files.writeends every line, the last one included, with your system's line separator: \n on macOS and Linux, \r\n on Windows.

Why there is nothing to close

In writing files, a BufferedWriter held your text in a buffer until flush or close pushed it to the disk, and a forgotten close lost it. Files.writeString and Files.write open the file, write everything and close it inside the one call. By the time the method returns, the text is in the file, and you never hold a writer object, so there is no close to forget.

Jobjava.io, from earlier lessonsjava.nio.file
Read every line into a listA BufferedReader, a readLine loop and try-with-resourcesFiles.readAllLines(path)
Read the whole textThe same loop, joining the lines yourselfFiles.readString(path)
Replace a file with new textnew FileWriter(name), write, then closeFiles.writeString(path, text)
Add to the end of a filenew FileWriter(name, true), write, then closeFiles.writeString(path, text, StandardOpenOption.APPEND)
Write a list of linesA BufferedWriter, a loop of write and newLine, then closeFiles.write(path, lines)
Report a missing fileFileNotFoundExceptionNoSuchFileException
The same jobs, the old way and the new way. Both ways throw IOException when something goes wrong.

Project: a grades report from a CSV file#

CSV stands for comma-separated values. It is the plainest way to keep a table in a text file: one line per row, with commas between the values in that row. The first line usually holds the column names and is called the header. Every spreadsheet program can save a sheet as CSV and open one, so a lot of data travels between programs this way. A spreadsheet shows this lesson's file, grades.csv, like this:

namemathsscienceenglishart
Aisha91857894
Ben64725970
Carmen88959190
Elif75816988
grades.csv in a spreadsheet. In the file itself, each row is one line of text, such as Aisha,91,85,78,94.

The goal is a second file, report.csv, with each student's total and average mark. You will build the program in four steps.

Step 1: split each line into fields

Three moves turn the file into data. readAllLines gives one String per line. The header is element 0, so the loop starts at 1 to skip it. Inside the loop, line.split(",") cuts the line at every comma and gives back the pieces, called fields, as a String array.

Grades.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.util.List;
5
6public class Main {
7 public static void main(String[] args) throws IOException {
8 List<String> lines = Files.readAllLines(Path.of("grades.csv"));
9 System.out.println("Header: " + lines.get(0));
10
11 for (int i = 1; i < lines.size(); i++) {
12 String[] fields = lines.get(i).split(",");
13 System.out.println(fields[0] + " has " + (fields.length - 1) + " marks. Maths: " + fields[1]);
14 }
15 }
16}
Files before the program runs
grades.csv
name,maths,science,english,art
Aisha,91,85,78,94
Ben,64,72,59,70
Carmen,88,95,91,90
Elif,75,81,69,88
Prints
›Header: name,maths,science,english,art
›Aisha has 4 marks. Maths: 91
›Ben has 4 marks. Maths: 64
›Carmen has 4 marks. Maths: 88
›Elif has 4 marks. Maths: 75

Step through and watch fields in the memory view. Each pass of the loop makes a new array of five strings: the name at index 0 and the four marks at indexes 1 to 4. The marks are still text. fields[1] is the string "91", because split only cuts text into smaller pieces of text.

Predict the outputFields.java
1public class Main {
2 public static void main(String[] args) {
3 String[] a = "Gus,70,80,90,85".split(",");
4 String[] b = "Gus,70,,90,85".split(",");
5 String[] c = "Gus,70,80,90,".split(",");
6 System.out.println(a.length + " " + b.length + " " + c.length);
7 }
8}

Three lines of a grades file, each split on commas. The second is missing a mark in the middle, and the third is missing its last mark. What are the three lengths?

A missing mark in the middle leaves an empty string, "", at its index, so b still has five fields. A missing mark at the end is different: split drops empty strings from the end of the array, so c has only four. If you need those empty fields kept, line.split(",", -1) keeps them.

Try the splitting yourself below. Edit any line, or add a tricky one with the buttons, and see how split cuts it and whether the finished report program, from step 4, keeps or skips it. The first switch changes every split to split(",", -1).

A CSV file, split line by lineline.split(",")
Add a line:
  1. 1
    0"name"1"maths"2"science"3"english"4"art"

    The header. The loop starts after it. It has 5 fields, so every other line needs 5.

  2. 2
    0"Aisha"1"91"2"85"3"78"4"94"

    Kept. It goes into the report as Aisha,348,87.0

  3. 3
    0"Ben"1"64"2"72"3"59"4"70"

    Kept. It goes into the report as Ben,265,66.25

  4. 4
    0"Carmen"1"88"2"95"3"91"4"90"

    Kept. It goes into the report as Carmen,364,91.0

  5. 5
    0"Dev"1"82"2"absent"3"77"4"80"

    Skipped: Integer.parseInt("absent") throws a NumberFormatException.

  6. 6
    0"Elif"1"75"2"81"3"69"4"88"

    Kept. It goes into the report as Elif,313,78.25

  7. 7
    0"Farah"1"90"2"85"

    Skipped: 3 fields, but the header has 5.

4 lines kept, 2 skipped.

Step 2: totals, averages and a report file

Before you can add up the marks, each one has to become an int, which is the job of Integer.parseInt. The program below adds up each student's marks, works out the average, and collects one line per student for the report. Each report line is built by joining values with commas, the reverse of split, so the report is a CSV file too and opens in any spreadsheet. At the end, one Files.write call saves every line.

Report.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.util.ArrayList;
5import java.util.List;
6
7public class Main {
8 public static void main(String[] args) throws IOException {
9 List<String> lines = Files.readAllLines(Path.of("grades.csv"));
10 List<String> report = new ArrayList<>();
11 report.add("name,total,average");
12
13 for (int i = 1; i < lines.size(); i++) {
14 String[] fields = lines.get(i).split(",");
15 int total = 0;
16 for (int j = 1; j < fields.length; j++) {
17 total += Integer.parseInt(fields[j]);
18 }
19 double average = (double) total / (fields.length - 1);
20 report.add(fields[0] + "," + total + "," + average);
21 }
22
23 Files.write(Path.of("report.csv"), report);
24 System.out.println("Wrote " + (report.size() - 1) + " students to report.csv");
25 }
26}
Files before the program runs
grades.csv
name,maths,science,english,art
Aisha,91,85,78,94
Ben,64,72,59,70
Carmen,88,95,91,90
Elif,75,81,69,88
Prints
›Wrote 4 students to report.csv

report.csv appears in the Files section when Files.writeruns, all five lines at once. Aisha's marks add up to 348 and her average is 87.0; Ben's are 265 and 66.25. The average divides by fields.length - 1, the number of marks (every field except the name), so the program still works if a subject is added to the file. The cast in (double) total matters: without it, 265 / 4 is integer division, as in operators and casting, and Ben's average would come out as 66.0.

Step 3: one bad line stops everything

Real files are messier. Here is the same program, unchanged, run on a grades.csv that a busy teacher has edited by hand. Dev missed the science test, so his mark says absent, and Farah's line is missing two marks. Run it and step to the end.

Report.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.util.ArrayList;
5import java.util.List;
6
7public class Main {
8 public static void main(String[] args) throws IOException {
9 List<String> lines = Files.readAllLines(Path.of("grades.csv"));
10 List<String> report = new ArrayList<>();
11 report.add("name,total,average");
12
13 for (int i = 1; i < lines.size(); i++) {
14 String[] fields = lines.get(i).split(",");
15 int total = 0;
16 for (int j = 1; j < fields.length; j++) {
17 total += Integer.parseInt(fields[j]);
18 }
19 double average = (double) total / (fields.length - 1);
20 report.add(fields[0] + "," + total + "," + average);
21 }
22
23 Files.write(Path.of("report.csv"), report);
24 System.out.println("Wrote " + (report.size() - 1) + " students to report.csv");
25 }
26}
Files before the program runs
grades.csv
name,maths,science,english,art
Aisha,91,85,78,94
Ben,64,72,59,70
Carmen,88,95,91,90
Dev,82,absent,77,80
Elif,75,81,69,88
Farah,90,85
Prints
!Exception in thread "main" java.lang.NumberFormatException: For input string: "absent"
! at Main.main(Main.java:17)
Stops on line 17

NumberFormatException: For input string: "absent"

The text was not a number Java could read, such as "abc" or "12.5" for an int.

Integer.parseInt("absent") throws a NumberFormatException, nothing catches it, and the program stops on Dev's line. Now look at the Files section: there is no report.csv. Aisha, Ben and Carmen were read without any trouble, but their lines were still waiting in the report list when the program died, before Files.write could run, so one bad line lost the whole report.

Quick check
Suppose someone fixes Dev's line, so the loop gets as far as Farah,90,85. What happens there?

Step 4: check every line before you trust it

The fix needs two checks. First, before parsing, compare the line's field count with the header's. The header says how many columns a row should have, so the program reads that number from line 1 instead of writing 5 into the code. Second, put the parsing inside try/catch, so a mark that is not a number skips that one line instead of ending the program. Both checks print the line number and the reason, so whoever looks after the file can fix it.

Report.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.util.ArrayList;
5import java.util.List;
6
7public class Main {
8 public static void main(String[] args) throws IOException {
9 List<String> lines = Files.readAllLines(Path.of("grades.csv"));
10 int expected = lines.get(0).split(",").length;
11 List<String> report = new ArrayList<>();
12 report.add("name,total,average");
13
14 for (int i = 1; i < lines.size(); i++) {
15 String[] fields = lines.get(i).split(",");
16 if (fields.length != expected) {
17 System.out.println("Line " + (i + 1) + " skipped: " + fields.length + " fields, expected " + expected);
18 continue;
19 }
20 try {
21 int total = 0;
22 for (int j = 1; j < fields.length; j++) {
23 total += Integer.parseInt(fields[j]);
24 }
25 double average = (double) total / (fields.length - 1);
26 report.add(fields[0] + "," + total + "," + average);
27 } catch (NumberFormatException e) {
28 System.out.println("Line " + (i + 1) + " skipped: " + e.getMessage());
29 }
30 }
31
32 Files.write(Path.of("report.csv"), report);
33 System.out.println("Wrote " + (report.size() - 1) + " students to report.csv");
34 }
35}
Files before the program runs
grades.csv
name,maths,science,english,art
Aisha,91,85,78,94
Ben,64,72,59,70
Carmen,88,95,91,90
Dev,82,absent,77,80
Elif,75,81,69,88
Farah,90,85
Prints
›Line 5 skipped: For input string: "absent"
›Line 7 skipped: 3 fields, expected 5
›Wrote 4 students to report.csv

It prints Line 5 skipped: For input string: "absent", then Line 7 skipped: 3 fields, expected 5, then Wrote 4 students to report.csv. Dev's line fails inside the try, and the catchtakes over for that line only. Farah's line fails the count check, and continue jumps straight to the next line, the same continue you met in loops. The four good students reach the report. The messages say i + 1 because the list counts from 0 while a text editor numbers lines from 1, so the header is line 1 and Dev is line 5.

Key idea

Treat every line of a file as something to check. Count its fields, parse inside a try, report and skip what fails, and the good lines still reach the report.

Real CSV files can be harder
Some CSV files put a field in double quotes when it contains a comma, as in "Ito, Ken",80,75,90,85. split cuts that name in two, the line gets six fields, and the count check skips it instead of reading it wrongly. Files made by other programs may also have spaces after the commas (trim()each field before parsing, as the widget's second switch shows) or use a semicolon as the separator. For files you do not control, a CSV library such as Apache Commons CSV handles quotes properly. split is the right tool for simple files you make yourself.
Put the lines in orderCupsSold.java
A café keeps its sales in sales.csv. Put the lines in order so the program makes the file, reads it back, skips the header and prints Cups sold: 25.

Tap a line, then tap where it should go. Or use the arrows.

ExerciseWarmest.java
temps.csvholds five days of midday temperatures, one line per day, and Wednesday's reading is missing. Finish the program: read the file, skip the header, and find the warmest day. For a line whose temperature is not a whole number, print Skipped Wed: n/a(with that line's own day and text) and carry on. The first line, which makes the file, and the last two lines, which write and print warmest.txt, are done for you.

Copy, move, delete and size#

Files also does the jobs you do by hand in a file manager. This program starts with a report.csv like the one the project wrote. Step through it and watch files appear and disappear in the Files section.

Tidy.java
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.nio.file.StandardCopyOption;
5
6public class Main {
7 public static void main(String[] args) throws IOException {
8 Path report = Path.of("report.csv");
9 Path backup = Path.of("backup.csv");
10
11 System.out.println("report.csv is " + Files.size(report) + " bytes");
12
13 Files.copy(report, backup);
14 Files.move(report, Path.of("march-report.csv"));
15 System.out.println(Files.exists(report) + " " + Files.exists(backup));
16
17 Files.copy(Path.of("march-report.csv"), backup, StandardCopyOption.REPLACE_EXISTING);
18 Files.delete(backup);
19 System.out.println(Files.deleteIfExists(backup));
20 }
21}
Files before the program runs
report.csv
name,total,average
Aisha,348,87.0
Ben,265,66.25
Carmen,364,91.0
Elif,313,78.25
Prints
›report.csv is 79 bytes
›false true
›false

Files.size says the report is 79 bytes: one for each of the 74 characters you can see, and one for each of its five line breaks. copy makes backup.csv with the same contents. move renames report.csv to march-report.csv, so afterwards there is no report.csv any more, and the program prints false true. The second copy would fail, because backup.csv already exists, if it did not pass StandardCopyOption.REPLACE_EXISTING. Finally delete removes the backup, and deleteIfExists prints false because there was nothing left to delete.

CallWhat it doesWhen it throws
Files.copy(from, to)Makes to a copy of from; from is left as it was.When from is missing (NoSuchFileException), or to already exists (FileAlreadyExistsException) and you did not pass StandardCopyOption.REPLACE_EXISTING.
Files.move(from, to)Renames from, or moves it to another folder.In the same two cases as copy.
Files.delete(path)Deletes the file.When there is no such file (NoSuchFileException).
Files.deleteIfExists(path)Deletes the file if it is there, and returns true if it deleted something.Not for a missing file: it returns false instead.
Files.size(path)Returns the size in bytes, as a long.When there is no such file.

A byte is not always a character. In UTF-8, plain English letters, digits and commas take one byte each, but a letter like é takes two and most emoji take four. So a file holding café and a line break is 6 bytes long, while readString gives you 5 characters.

Deleted means gone
Files.delete does not use the recycle bin or the trash. The file is gone the moment the call returns, and Files.writeString without APPEND wipes a file just as fast. Before a program deletes or replaces a file it did not create, make sure the path is the one you mean, or copy the file somewhere safe first.

Folders: listing and walking#

So far every file has sat in one folder. Real projects keep files in folders inside folders, and two Files methods look inside them. Files.list(folder) gives the entries directly inside one folder. Files.walk(folder) also goes down into every folder inside it, and every folder inside those, and so on. Both hand the entries back as a Stream<Path> that keeps the folder open, so they belong in try-with-resources too.

The in-browser engine keeps its files on a flat disk, without folders, so these two methods only run on a real computer. Here is a folder called school on a real disk:

Folders on disk
read along
1school/
2 grades.csv
3 notes.txt
4 2025/
5 autumn.csv
6 spring.csv
FindCsv.java
read along
1import java.io.IOException;
2import java.nio.file.Files;
3import java.nio.file.Path;
4import java.util.stream.Stream;
5
6public class Main {
7 public static void main(String[] args) throws IOException {
8 Path school = Path.of("school");
9
10 System.out.println("Directly inside school:");
11 try (Stream<Path> entries = Files.list(school)) {
12 entries.sorted().forEach(entry -> System.out.println(" " + entry.getFileName()));
13 }
14
15 System.out.println("Every CSV file, at any depth:");
16 try (Stream<Path> all = Files.walk(school)) {
17 all.filter(path -> path.toString().endsWith(".csv"))
18 .sorted()
19 .forEach(path -> System.out.println(" " + path));
20 }
21 }
22}
Output
Directly inside school:
  2025
  grades.csv
  notes.txt
Every CSV file, at any depth:
  school/2025/autumn.csv
  school/2025/spring.csv
  school/grades.csv

Files.list stopped at the first level: the folder 2025 is one of its entries, but the files inside it are not. Files.walk went into 2025 as well, and the filter step kept only the paths whose names end in .csv. The operating system hands back a folder's entries in no fixed order, so both parts sort them with sorted() before printing them. Run this on your own computer, next to a folder of your own, to see it work.

Make the folders before you write into them
Files.writeString(Path.of("reports", "2026", "march.csv"), text) fails with a NoSuchFileException if the folder reports/2026 does not exist yet, because writeString creates files, never folders. Call Files.createDirectories(Path.of("reports", "2026")) first. It makes the folder and any missing folders above it, and does nothing if they are already there.

Try it

Answer each one in your head or on paper first, then open the answer.

1
A file holds a, a line break, b and a line break. What does Files.readString give back, and what does Files.readAllLines give back?
2
What do the three lines of this program print?
3
A program runs Files.writeString(Path.of("log.txt"), "Started\n") each time it starts. After three runs, what is in log.txt, and how do you make it keep a line from every run?
4
The grades file gains the line "Lee, Ann",90,85,70,88. How many fields does split(",") give, and what does the program from step 4 do with the line?
5
Why do the step 4 messages print i + 1 instead of i?

Practice

Work through these on your own. Each one builds on this page.

1
Extend the report program from step 4 so that it also writes problems.txt, with one line for each skipped line of grades.csv saying which line it was and why. Build the problem lines in a second list and write it with Files.write. What should the program do when no lines were skipped?
2
Add the line Hana, 82, 90, 75, 88 to the grades file, with a space after each comma. Run the step 4 program and explain the message it prints. Then change one line of the program so that Hana is read correctly, and check her total (335) and average (83.75).
3
Write a program that reads expenses.csv, with the columns date,category,cents (amounts in whole cents, so every value is an int), adds up the spending in each category with a TreeMap, and writes summary.csv with the columns category,cents in alphabetical order. Make up a file of about ten lines, with at least one bad line, and have the program create it first.
4
Write a program that keeps a log from getting too big. If app.log is larger than 200 bytes, it moves the log to app.log.old (replacing any older copy) and starts a fresh app.log with a single line saying so. Test it by first writing about 300 bytes of lines to app.log, then running your check twice.

Further reading#

Short guides on this topic

Quick answers to what people search for about it, with code to run.

Spotted a mistake, or a part that did not make sense? Report it by email