Question

Reading a Plain Text File in Java: Common Ways and Differences

javatextfile-ioasciitext-files

Question

I want to read plain text data from a file in Java, such as ASCII text. There seem to be several ways to read and write file data in Java. What are the common ways to read a text file, and what are the differences between them?

Short Answer

By the end of this page, you will understand the main ways to read plain text files in Java, when to use each one, and how they differ in simplicity, performance, and control. You will also see practical examples using BufferedReader, Scanner, and the Files API.

Concept

In Java, reading a plain text file means converting the bytes stored in a file into characters you can work with in your program.

Even if your file contains ASCII text, Java typically handles text using character-based readers rather than raw byte streams. This matters because:

  • Files are stored as bytes
  • Text is used in Java as characters and String values
  • A character encoding tells Java how to convert bytes into characters

ASCII is a simple encoding, but in modern programs you will often use UTF-8, because it can represent ASCII and much more.

Main ways to read text files in Java

1. BufferedReader

A classic and efficient way to read text line by line.

  • Good for large files
  • Efficient because it buffers input
  • Gives you explicit control
  • Common in older and modern Java code

2. Scanner

A convenient way to parse text into tokens, lines, integers, and other values.

  • Easy for beginners
  • Useful when reading formatted text
  • Can split input by spaces or lines
  • Usually slower than BufferedReader for large files

3. Files.readString() / Files.readAllLines()

Modern utility methods from java.nio.file.Files.

  • Very concise
  • Great for small and medium files
  • Easy to read whole file at once
  • Not ideal for very large files because everything is loaded into memory

4. Files.lines()

A modern streaming approach.

  • Reads lines lazily
  • Good for large files
  • Works well with stream operations like filter, map, and count
  • Requires proper resource handling

Why the differences matter

Different approaches are useful in different situations:

  • If you want simple code for a small file, use Files.readString() or Files.readAllLines()
  • If you want line-by-line efficiency, use BufferedReader
  • If you want token parsing, use Scanner
  • If you want stream-based processing, use Files.lines()

In real programs, the best choice depends on:

  • file size
  • whether you need lines or tokens
  • performance needs
  • encoding requirements
  • coding style and Java version

Mental Model

Think of a text file like a long roll of paper.

  • BufferedReader is like reading the roll one line at a time with a steady pace.
  • Scanner is like reading the roll and breaking it into words or numbers for you.
  • Files.readAllLines() is like unrolling the entire paper onto a table so you can inspect everything at once.
  • Files.lines() is like having an assistant hand you one line at a time only when you ask for it.

Another important idea: the file itself stores bytes, but you want text. Encoding is the translator between those two worlds.

Take Quiz

Syntax and Examples

1. Read line by line with BufferedReader

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("data.txt");

        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

This is one of the most common ways to read a text file efficiently.

2. Read with Scanner

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("data.txt");

        try (Scanner scanner = new Scanner(path, StandardCharsets.UTF_8)) {
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }
        }
    }
}

Scanner is simple and readable. It is especially useful if you want to parse values like integers or words.

3. Read the entire file as one String

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("data.txt");
        String content = Files.readString(path, StandardCharsets.UTF_8);

        System.out.println(content);
    }
}

This is very concise and ideal when the file is small enough to fit comfortably in memory.

4. Read all lines into a list

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("data.txt");
        List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);

        for (String line : lines) {
            System.out.println(line);
        }
    }
}

This is convenient if you need random access to lines later.

5. Stream lines with Files.lines()

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("data.txt");

        try (var lines = Files.lines(path, StandardCharsets.UTF_8)) {
            lines.forEach(System.out::println);
        }
    }
}

This is powerful when combined with stream operations.

ASCII note

If the file is truly ASCII, you can use:

StandardCharsets.US_ASCII

However, UTF_8 is often preferred unless you specifically need ASCII-only behavior, because UTF-8 correctly handles ASCII too.

Step by Step Execution

Consider this file data.txt:

Apple
Banana
Cherry

Now this program:

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("data.txt");

        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Read: " + line);
            }
        }
    }
}

What happens step by step

  1. Path path = Path.of("data.txt");

    • Creates a Path object pointing to the file.
  2. Files.newBufferedReader(path, StandardCharsets.UTF_8)

    • Opens the file for reading.
    • Tells Java how to decode bytes into characters.
    • Wraps the file in a buffered reader for efficiency.
  3. try (...) { ... }

    • Ensures the file is automatically closed.
  4. String line;

    • Creates a variable to hold each line.
  5. reader.readLine()

    • Reads the next line of text.
    • Returns "Apple", then "Banana", then "Cherry".
    • After the last line, it returns null.
  6. while ((line = reader.readLine()) != null)

    • Keeps looping while there is another line.
  7. System.out.println("Read: " + line);

    • Prints each line.

Output

Read: Apple
Read: Banana
Read: Cherry

The key detail is that readLine() returns null when the file ends. That is how the loop knows when to stop.

Real World Use Cases

Common practical uses

Reading configuration files

Programs often load text settings from files such as:

  • app settings
  • environment-specific configuration
  • simple key-value files

Importing CSV or log files

Applications often read text files line by line to process:

  • server logs
  • CSV exports
  • transaction records
  • reports

Reading templates or static content

Programs may load:

  • email templates
  • SQL scripts
  • text resources
  • documentation snippets

Parsing command output or generated text

Some tools save text output to files that another Java program reads and processes.

Data cleaning scripts

Small Java utilities often read text files, transform each line, and write a cleaned version back out.

Take Quiz

Real Codebase Usage

In real projects, developers usually pick the file-reading approach based on the shape of the problem.

Common patterns

Guard clauses for file checks

Path path = Path.of("data.txt");
if (!Files.exists(path)) {
    throw new IllegalArgumentException("File does not exist: " + path);
}

This avoids trying to read a missing file.

Explicit encoding

String text = Files.readString(path, StandardCharsets.UTF_8);

Real code should usually avoid relying on the platform default encoding.

Line-by-line validation

try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        if (line.isBlank()) {
            continue;
        }
        System.out.println("Valid line: " + line);
    }
}

This is common when processing user-supplied files.

Stream processing

try (var lines = Files.lines(path, StandardCharsets.UTF_8)) {
    long errorCount = lines
            .filter(line -> line.contains("ERROR"))
            .count();

    System.out.println(errorCount);
}

Useful in log analysis and reporting tools.

Error handling

try {
    String content = Files.readString(path, StandardCharsets.UTF_8);
    System.out.println(content);
} catch (IOException e) {
    System.err.println("Could not read file: " + e.getMessage());
}

Production code often logs the error or wraps it in a custom exception.

Common Mistakes

1. Forgetting to specify the encoding

Broken example:

BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"));

This may work, but it uses the platform default charset, which can vary.

Better:

BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"), StandardCharsets.UTF_8);

2. Reading a huge file all at once

Broken approach for large files:

String content = Files.readString(Path.of("huge-file.txt"));

This can use too much memory.

Better for large files:

try (BufferedReader reader = Files.newBufferedReader(Path.of("huge-file.txt"), StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        // process one line at a time
    }
}

3. Not closing the file

Broken example:

BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"), StandardCharsets.UTF_8);
System.out.println(reader.readLine());

This leaves resource management up to you.

Better:

try (BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"), StandardCharsets.UTF_8)) {
    System.out.println(reader.readLine());
}

4. Using byte streams for text without understanding encoding

Broken or incomplete idea:

import java.io.FileInputStream;

FileInputStream in = new FileInputStream("data.txt");
int b;
while ((b = in.read()) != -1) {
    System.out.print((char) b);
}

This may appear to work for pure ASCII, but it is not the right general solution for text.

Better to use a reader:

try (BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"), StandardCharsets.UTF_8)) {
    System.out.println(reader.readLine());
}

5. Confusing lines with tokens

Scanner can read words, numbers, or lines. Beginners sometimes expect next() to return a full line.

Scanner scanner = new Scanner(Path.of("data.txt"), StandardCharsets.UTF_8);
while (scanner.hasNext()) {
    System.out.println(scanner.next());
}

This reads tokens separated by whitespace, not lines.

If you want full lines, use nextLine().

Comparisons

ApproachBest forReads all at once?Efficient for large files?Easy to parse tokens?Notes
BufferedReaderLine-by-line readingNoYesNoClassic, fast, flexible
ScannerSimple parsing of words/numbers/linesNoUsually less efficientYesBeginner-friendly
Files.readString()Small text filesYesNoNoVery concise
Files.readAllLines()Small to medium filesYesNoNoGives List<String>
Files.lines()Large files with stream processingNoYesNot directlyGreat with filter, map, count

Reader vs InputStream

TypeUse forExample
ReaderText dataBufferedReader, FileReader
InputStreamRaw bytesFileInputStream

If you are reading plain text, prefer Reader-based APIs.

FileReader vs Files.newBufferedReader

OptionRecommendationWhy
FileReaderLess preferredOlder style, less explicit about charset
Files.newBufferedReaderPreferredModern API, clear charset handling
Take Quiz

Cheat Sheet

Quick reference

Read line by line

try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

Read entire file as a string

String text = Files.readString(path, StandardCharsets.UTF_8);

Read all lines into a list

List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);

Read using a stream

try (var lines = Files.lines(path, StandardCharsets.UTF_8)) {
    lines.forEach(System.out::println);
}

Read with Scanner

try (Scanner scanner = new Scanner(path, StandardCharsets.UTF_8)) {
    while (scanner.hasNextLine()) {
        System.out.println(scanner.nextLine());
    }
}

Rules of thumb

  • Use UTF_8 unless you specifically need another encoding
  • Use US_ASCII only when ASCII is truly required
  • Use BufferedReader for efficient line-by-line reading
  • Use Files.readString() for small files
  • Use Files.lines() for large-file stream processing
  • Use try-with-resources to close files automatically

Edge cases

  • Missing file: throws IOException
  • Wrong charset: text may look corrupted
  • Large files: avoid reading everything into memory
  • Scanner.next() reads tokens, not full lines

FAQ

What is the best way to read a text file in Java?

For most line-by-line cases, BufferedReader is a strong default. For small files, Files.readString() is often the simplest.

Should I use BufferedReader or Scanner in Java?

Use BufferedReader for efficient line reading. Use Scanner when you want easy parsing of tokens, numbers, or lines.

How do I read an ASCII file in Java?

Use a text reader and specify StandardCharsets.US_ASCII if you specifically need ASCII:

Files.readString(path, StandardCharsets.US_ASCII);

Can I use UTF-8 for an ASCII file?

Yes. ASCII text is valid UTF-8 text, so UTF_8 usually works perfectly.

Why should I specify a charset when reading files?

Because otherwise Java may use the system default charset, which can differ across machines and cause incorrect text decoding.

What is the difference between readAllLines() and lines()?

readAllLines() loads everything into memory immediately. lines() processes lines lazily through a stream.

Is FileReader still used in Java?

Yes, but modern code often prefers Files.newBufferedReader() because it works more cleanly with Path and explicit charsets.

Take Quiz

Mini Project

Description

Build a small Java program that reads a plain text file of names and prints useful information about it. This project demonstrates multiple text-file reading tasks that appear in real programs: counting lines, skipping blank lines, and processing text safely with an explicit character encoding.

Goal

Create a program that reads a text file, prints each non-blank line, and shows the total number of non-empty lines.

Requirements

  1. Read a text file using Java.
  2. Use an explicit charset when opening the file.
  3. Ignore blank lines.
  4. Print each non-blank line with a line number.
  5. Print the total count of non-blank lines at the end.
Take Quiz

Keep learning

Related questions

View more