Using PrintWriter and Scanner for File I/O in Java

Illustration for Using PrintWriter and Scanner for File I/O in Java
By Last updated:

Input and output (I/O) form the foundation of every application. From text editors saving documents, to web servers parsing requests, to databases reading and writing structured files — efficient I/O is crucial. While Java offers low-level classes like InputStream and Reader, sometimes you need higher-level utilities that make reading and writing text files more convenient.

Two such classes are PrintWriter and Scanner.

  • PrintWriter simplifies writing formatted text to files.
  • Scanner simplifies reading and parsing text input from files.

Together, they provide developers with an easy and efficient way to perform file I/O for character data.


Basics of Java I/O

Streams, Readers, and Writers

  • InputStream/OutputStream → Binary data (images, audio).
  • Reader/Writer → Character data (text files, XML, JSON).
  • PrintWriter → Specialized writer for formatted text.
  • Scanner → High-level reader for parsing tokens.

Writing Files with PrintWriter

The PrintWriter class allows easy writing of text, formatted output, and even appending data.

Example: Writing Text

import java.io.PrintWriter;
import java.io.IOException;

public class PrintWriterExample {
    public static void main(String[] args) {
        try (PrintWriter writer = new PrintWriter("output.txt")) {
            writer.println("Hello, Java!");
            writer.printf("Number: %d, String: %s%n", 42, "example");
            writer.write("Direct write example.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Appending to a File

import java.io.FileWriter;
import java.io.PrintWriter;

public class AppendExample {
    public static void main(String[] args) throws Exception {
        try (PrintWriter writer = new PrintWriter(new FileWriter("output.txt", true))) {
            writer.println("Appended line.");
        }
    }
}

Reading Files with Scanner

The Scanner class tokenizes input and makes parsing text files simple.

Example: Reading Lines

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ScannerExample {
    public static void main(String[] args) {
        try (Scanner scanner = new Scanner(new File("input.txt"))) {
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Example: Parsing Tokens

import java.io.File;
import java.util.Scanner;

public class ScannerTokenExample {
    public static void main(String[] args) throws Exception {
        try (Scanner scanner = new Scanner(new File("data.txt"))) {
            while (scanner.hasNext()) {
                if (scanner.hasNextInt()) {
                    System.out.println("Integer: " + scanner.nextInt());
                } else {
                    System.out.println("Word: " + scanner.next());
                }
            }
        }
    }
}

Analogy: Using Scanner is like having a dictionary at hand — it splits text into words (tokens) so you don’t need to separate them manually.


Intermediate Concepts

Buffered I/O

While PrintWriter and Scanner are high-level tools, they work well with BufferedReader/BufferedWriter for more efficiency in large files.

RandomAccessFile

For non-sequential access, RandomAccessFile provides more control than Scanner.

Reading CSV, JSON, XML

  • CSV: Use Scanner with delimiters (scanner.useDelimiter(",")).
  • JSON/XML: Usually handled by libraries, but Scanner can read raw text input.

Properties Files

Scanner or BufferedReader can load configuration .properties files.


Advanced I/O with NIO and NIO.2

Files API Integration

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class FilesAPIExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("nio-example.txt");

        // Write string
        Files.writeString(path, "Hello from NIO.2!");

        // Read all lines
        List<String> lines = Files.readAllLines(path);
        lines.forEach(System.out::println);
    }
}

Channels and Buffers

For high-performance I/O, NIO’s FileChannel and ByteBuffer are used instead of Scanner/PrintWriter.

WatchService

Monitor directories and trigger Scanner/PrintWriter operations when files change.


Performance & Best Practices

  • Use try-with-resources to ensure automatic closure.
  • Always specify encodings when needed (new Scanner(file, "UTF-8")).
  • Avoid using Scanner for very large files (prefer BufferedReader).
  • Use PrintWriter for human-readable text (reports, logs).
  • For structured formats (CSV, JSON), use dedicated libraries.

Framework Case Studies

  • Spring Boot: Uses PrintWriter for generating responses.
  • Logging Frameworks: Use PrintWriter for log output.
  • Hibernate: Reads configuration files with Scanner/Readers.
  • Netty: Relies on NIO for performance but Scanner for configs.
  • Microservices: Export logs and read configs with Scanner and PrintWriter.

Real-World Scenarios

  1. Log File Generator: PrintWriter writes logs.
  2. Text Parser: Scanner reads log files line by line.
  3. CSV Importer: Scanner parses CSV tokens.
  4. Report Writer: PrintWriter generates formatted reports.
  5. REST API Export: PrintWriter saves JSON/CSV responses.

📌 What's New in Java Versions?

  • Java 7+: Introduced NIO.2 for modern file APIs.
  • Java 8: Streams API (Files.lines()) enhances text parsing.
  • Java 11: Convenience methods (Files.readString(), Files.writeString()).
  • Java 17: Performance improvements for text I/O.
  • Java 21: Virtual threads enable scalable blocking I/O.

Conclusion & Key Takeaways

PrintWriter and Scanner provide a simple yet powerful way to perform file I/O in Java. With PrintWriter for writing and Scanner for reading, developers can easily handle text data. For larger or more complex tasks, combining them with buffering, NIO.2, or specialized libraries ensures performance and scalability.

Key Takeaways:

  • Use PrintWriter for writing formatted text.
  • Use Scanner for simple text parsing.
  • Always handle resources and encodings properly.
  • Prefer NIO.2 for advanced, large-scale file operations.

FAQ

Q1. What’s the difference between Scanner and BufferedReader?
A: Scanner parses tokens, BufferedReader is faster for raw text reading.

Q2. Can PrintWriter handle binary data?
A: No, it’s designed for text. Use OutputStream for binary files.

Q3. How do I append to a file using PrintWriter?
A: Pass true to FileWriter: new FileWriter("file.txt", true).

Q4. Is Scanner thread-safe?
A: No, external synchronization is required in multi-threaded apps.

Q5. How do I change Scanner delimiters?
A: Use scanner.useDelimiter(",") for CSV files.

Q6. Can Scanner read from InputStreams other than files?
A: Yes, it can read from System.in or sockets.

Q7. How do I specify character encoding in Scanner?
A: new Scanner(file, "UTF-8").

Q8. Why does PrintWriter not throw exceptions directly?
A: It sets an internal error flag instead. Use checkError() to verify.

Q9. How do frameworks use PrintWriter?
A: Spring and logging frameworks use PrintWriter for writing responses/logs.

Q10. What should I use for large text parsing instead of Scanner?
A: Prefer BufferedReader or Files.lines() with Streams for efficiency.