Count Lines in a File in Java (Fast and Correct Ways)

Count lines in a file in Java with Files.lines(), LineNumberReader, readAllLines() or a fast byte counter, with edge cases like a missing final newline.

Four ways to count lines in a Java file compared by memory use and charset handling

The call Files.lines(path).count() inside a try-with-resources block is the shortest way to count lines in a file in Java, and it reads the file as a lazy stream that never holds more than one line in memory. The count matches what a text editor shows, including a last line without a trailing newline.

We count lines to show progress during an import, to check that an export has the expected number of records, to reject uploads that are too large, or to report the size of a log file.

The following example writes a three-line file and counts its lines with Files.lines().

Path orders = Files.writeString(Files.createTempFile("orders", ".csv"), "id,item\n1,tea\n2,cake\n");
try (Stream<String> lines = Files.lines(orders)) {
    long count = lines.count();                       // 3
}

Notice that the stream is closed by try-with-resources, because an open stream keeps the file handle open. We also cover LineNumberReader, Files.readAllLines(), a fast byte-level count for very large files, the edge cases that make counts differ, and counting only the lines that match a condition.

1. Counting Lines Using a Stream of Lines

The method Files.lines() returns a Stream<String> that reads the file lazily as the stream pulls each line. The terminal operation count() pulls all lines and returns a long, so the method works for files of any size.

Four ways to count lines in a Java file compared by memory use and charset handling
Files.lines() and LineNumberReader stream the file, readAllLines() loads it, and the byte counter skips decoding
static long countLines(Path file) throws IOException {
    try (Stream<String> lines = Files.lines(file)) {
        return lines.count();
    }
}
Path report = Files.writeString(Files.createTempFile("report", ".txt"), "a\nb\nc");
long count = countLines(report);                      // 3, the last line has no newline

The method decodes the file as UTF-8. When the file contains bytes that are not valid UTF-8, for example a Windows-1252 export with accented letters, the stream throws an UncheckedIOException caused by MalformedInputException. We pass the right charset as the second argument, such as Files.lines(file, StandardCharsets.ISO_8859_1).

Path legacy = Files.write(Files.createTempFile("legacy", ".txt"), new byte[] {'c', 'a', 'f', (byte) 0xE9, '\n'});
long strict = countLines(legacy);                                        // UncheckedIOException: java.nio.charset.MalformedInputException: Input length = 1
long lenient = Files.lines(legacy, StandardCharsets.ISO_8859_1).count(); // 1

The lenient line leaves the stream open to keep the comparison short, so real code wraps it in try-with-resources like countLines() does.

2. Using LineNumberReader

The LineNumberReader class is a BufferedReader that counts line terminators as it reads. We skip to the end of the file with skip(Long.MAX_VALUE), and getLineNumber() returns the count without creating a String for each line.

static int countWithLineNumberReader(Path file) throws IOException {
    try (LineNumberReader reader = new LineNumberReader(Files.newBufferedReader(file))) {
        reader.skip(Long.MAX_VALUE);
        return reader.getLineNumber();
    }
}
Path noNewline = Files.writeString(Files.createTempFile("a", ".txt"), "a\nb\nc");
Path withNewline = Files.writeString(Files.createTempFile("b", ".txt"), "a\nb\nc\n");
int first = countWithLineNumberReader(noNewline);       // 3
int second = countWithLineNumberReader(withNewline);    // 3

We do not add 1 to getLineNumber(), because on Java 16 and later that returns 4 for both files. Since Java 16, LineNumberReader counts the end of the file as a terminator when the last line has no newline, so the result needs no adjustment. The method returns an int, which overflows after about 2.1 billion lines, whereas count() returns a long.

3. Using Files.readAllLines() for Small Files

When the program needs the lines anyway, for example to validate and import them, we read them once with Files.readAllLines() and take the size of the list. The method loads the whole file into memory, so it fits files of a few megabytes, not multi-gigabyte logs.

Path students = Files.writeString(Files.createTempFile("students", ".txt"), "Alex\nMaria\nJohn\n");
List<String> names = Files.readAllLines(students);
int count = names.size();                              // 3
String last = names.get(count - 1);                    // "John"

Like Files.lines(), the method readAllLines() decodes the file as UTF-8 by default and throws a MalformedInputException when a byte sequence is not valid in that charset.

4. Counting Line Breaks in Bytes for Large Files

All the methods above decode bytes into characters and check each character for a line terminator. For multi-gigabyte files in UTF-8 or ASCII, we can skip the decoding and count the ‘\n’ bytes, because in these encodings the byte 0x0A appears only as a line feed.

static long countLineBreaks(Path file) throws IOException {
    long lines = 0;
    byte last = '\n';
    byte[] buffer = new byte[64 * 1024];
    try (InputStream in = Files.newInputStream(file)) {
        int read;
        while ((read = in.read(buffer)) != -1) {
            for (int i = 0; i < read; i++) {
                if (buffer[i] == '\n') {
                    lines++;
                }
            }
            if (read > 0) {
                last = buffer[read - 1];
            }
        }
    }
    return last == '\n' ? lines : lines + 1;             // count a last line without newline
}
Path big = Files.writeString(Files.createTempFile("big", ".log"), "x\n".repeat(100_000) + "end");
long count = countLineBreaks(big);                     // 100001
Path empty = Files.createTempFile("empty", ".log");
long none = countLineBreaks(empty);                    // 0

The method counts \n only, so it gives the same result for Linux and Windows files, but not for old Mac files that end lines with a lone \r. It does not work for UTF-16 files, where every character takes two bytes. The command wc -l on Linux and macOS counts the same ‘\n’ bytes but does not add the last line when it has no newline, so its result can be one lower.

5. Edge Cases That Change the Count

Line counts differ between tools mostly because of the last line and of empty lines. The three Java methods agree on every case except the old Mac format, where the byte counter sees no \n at all.

File contentFiles.lines().count()LineNumberReadercountLineBreaks()wc -l
Empty file0000
a\nb\nc (no newline at the end)3332
a\nb\nc\n3333
a\n\nb\n (blank line)3333
a\r\nb\r\n (Windows)2222
a\rb (old Mac)2210

A blank line counts as a line in every method. When the requirement is the number of records in a file, we often want to skip blank lines and a header row, which the next section shows.

6. Counting Non-Empty or Matching Lines

A stream of lines makes conditional counts a one-line change. We add a filter() before count(), and the file is still read only once.

Path export = Files.writeString(Files.createTempFile("export", ".csv"), "id,item\n1,tea\n\n2,cake\n# end\n");
try (Stream<String> lines = Files.lines(export)) {
    long records = lines.skip(1)                                   // skip the header
                        .filter(line -> !line.isBlank())
                        .filter(line -> !line.startsWith("#"))
                        .count();                                  // 2
}

For a CSV file whose quoted values can contain line breaks, a line count is not a record count. In that case we count records with a CSV parser, as shown in parsing CSV files in Java.

7. Picking a Way to Count Lines in a File

Each method fits a different file size and charset situation. For most code, Files.lines() is the default, and the others cover the cases where its UTF-8 check or its per-line String objects get in the way.

MethodMemoryBad bytes in the charsetBest for
Files.lines(path).count()One line at a timeThrows UncheckedIOExceptionDefault choice, conditional counts
LineNumberReader.skip()One bufferThrows from Files.newBufferedReader(), ignored with FileReaderCounting without creating strings
Files.readAllLines().size()Whole fileThrows MalformedInputExceptionSmall files whose lines we also use
countLineBreaks()One byte bufferNot affected, no decodingVery large UTF-8 or ASCII files

The article on reading a file line by line covers processing each line after counting, and the Java IO tutorial groups the related file tasks.

8. Real-World Example of Import Progress

Say a shop admin panel imports a product feed of a few hundred thousand lines each night. The import page shows “row 1,200 of 250,000”, so the job counts the data rows first and passes the total to the progress tracker before it starts the import loop.

record ImportPlan(Path file, long totalRows) {}
static ImportPlan planImport(Path feed) throws IOException {
    long rows = countLineBreaks(feed) - 1;               // minus the header line
    if (rows <= 0) {
        throw new IllegalArgumentException("Feed has no data rows: " + feed.getFileName());
    }
    return new ImportPlan(feed, rows);
}
Path feed = Files.writeString(Files.createTempFile("feed", ".csv"), "sku,price\n" + "p1,10\n".repeat(1_200));
ImportPlan plan = planImport(feed);
long total = plan.totalRows();                           // 1200

The byte counter reads the file once more before the import, which adds a short pass over the data. In return, the job can reject an empty feed before it touches the database, and the progress bar has a real total.

9. Line Counting FAQs

Speed, memory use and the last line of the file are the points that come up next once a line count works.

9.1. What is the fastest way to count lines in a Java file?

Counting ‘\n’ bytes with a large byte buffer, as in countLineBreaks(), skips the character decoding that the other methods do. It works only for UTF-8, ASCII and similar encodings. For files smaller than a few hundred megabytes, Files.lines().count() is fast enough and shorter.

9.2. Does Files.lines().count() read every line of the file?

Yes. The stream has no size information, so count() pulls and decodes every line, but it keeps only the current line in memory. Only Files.readAllLines() keeps all lines at once.

9.3. Why does my count differ from wc -l by one?

The command wc -l counts newline characters, so a last line without a trailing newline is not counted. The Java methods in this article count that line, so for such a file they return one more than wc -l.

9.4. Is the result of LineNumberReader different on Java 8 and Java 17?

Yes, for files whose last line has no newline. On Java 8 to 15, getLineNumber() after skip(Long.MAX_VALUE) did not count that last line, and since Java 16 it does. Code that adds 1 for all files was wrong before Java 16 for files that end with a newline, and is wrong for all files since Java 16.

10. Conclusion

The shortest correct way to count lines is Files.lines(path).count() in try-with-resources, with an explicit charset when the file is not UTF-8. The LineNumberReader approach gives the same count without an extra 1 on Java 16 and later, and readAllLines() suits small files we read anyway.

For very large UTF-8 files, counting ‘\n’ bytes avoids decoding, and it needs the extra check for a last line without a newline. When blank lines, comments or a header must not count, a filter() before count() gives the record count in the same pass.

11. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.