How To Read File In Java

9 min read

Reading files is a fundamental operation in almost every Java application, whether you are building a simple command-line tool, a complex enterprise system, or a high-performance data processing pipeline. Here's the thing — understanding the various ways to read a file in Java allows you to choose the right approach for performance, memory efficiency, and code readability. That said, this guide explores the evolution of file reading in Java, covering the modern java. nio API, the classic java.io streams, and specialized techniques for handling large files or specific data formats.

The Modern Approach: Java NIO.2 (java.nio.file)

Introduced in Java 7, the NIO.2 (New I/O) API is now the standard for file system operations. So it offers a more intuitive, powerful, and often more performant way to handle files compared to the legacy java. io package. The Files utility class is the entry point for most operations.

Reading All Content at Once

For small to medium-sized files where you need the entire content in memory, Files.readString() (Java 11+) and Files.readAllLines() are the cleanest solutions Surprisingly effective..

Reading as a Single String (Java 11+): This method decodes bytes into characters using UTF-8 by default, returning a single String instance Most people skip this — try not to..

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

public class ReadFileExample {
    public static void main(String[] args) {
        Path path = Path.Practically speaking, readString(path);
            System. txt");
        try {
            String content = Files.out.of("data/config.println(content);
        } catch (IOException e) {
            e.

**Reading as a List of Lines (Java 8+):**
If you need to process the file line by line but want the convenience of a `List`, use `Files.readAllLines()`. Note that this loads the *entire* file into heap memory.

```java
List lines = Files.readAllLines(path);
lines.forEach(System.out::println);

Streaming Lines for Memory Efficiency

When dealing with larger files, loading everything into a List or String risks an OutOfMemoryError. The method Files.lines() returns a Stream<String>, which is lazy. It reads lines only as they are consumed by the stream pipeline, allowing you to process massive files with a tiny memory footprint.

try (Stream stream = Files.lines(path)) {
    stream.filter(line -> line.startsWith("ERROR"))
          .limit(10)
          .forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}

Critical Note: Because Files.lines() opens an underlying I/O resource, you must wrap it in a try-with-resources block to ensure the file handle is closed promptly Turns out it matters..

Reading Raw Bytes

For binary files (images, PDFs, serialized objects), use Files.readAllBytes() (small files) or Files.newInputStream(path) for streaming binary data.

byte[] fileBytes = Files.readAllBytes(path); // Only for files fitting in memory

The Classic Approach: java.io Package

Before Java 7, FileReader, BufferedReader, and Scanner were the primary tools. While NIO.2 is preferred for new projects, you will encounter this code extensively in legacy codebases.

BufferedReader: The Workhorse

BufferedReader wraps a FileReader (or InputStreamReader) to provide efficient reading of characters, arrays, and lines. It minimizes native I/O calls by reading large chunks into an internal buffer That's the part that actually makes a difference..

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class LegacyReadExample {
    public static void main(String[] args) {
        // try-with-resources ensures automatic closing (Java 7+)
        try (BufferedReader reader = new BufferedReader(new FileReader("data/log.txt"))) {
            String line;
            while ((line = reader.= null) {
                System.That's why out. readLine()) !println(line);
            }
        } catch (IOException e) {
            e.

**Why use `BufferedReader` over `FileReader` directly?**
`FileReader` reads one character at a time from the disk (extremely slow). `BufferedReader` reads 8KB (default) at a time into memory, serving subsequent `readLine()` calls from that buffer.

### Scanner: Parsing Made Easy

`Scanner` is ideal when you need to **parse** primitive types (int, double) or strings using regular expressions (delimiters) rather than just reading raw lines.

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

public class ScannerExample {
    public static void main(String[] args) {
        try (Scanner scanner = new Scanner(new File("data/numbers.Because of that, hasNextInt()) {
                int number = scanner. nextInt();
                System.txt"))) {
            scanner.In practice, useDelimiter("\\s+"); // Split by whitespace
            while (scanner. Day to day, out. println("Parsed: " + number);
            }
        } catch (FileNotFoundException e) {
            e.

**Performance Caveat:** `Scanner` is significantly slower than `BufferedReader` due to its parsing overhead and internal regex processing. Avoid it for high-throughput line-by-line reading of large logs.

## Handling Character Encoding Explicitly

A common source of bugs is relying on the platform's *default charset*. The default encoding varies by OS (UTF-8 on Linux/macOS, often Cp1252 on Windows). This leads to "works on my machine" failures when reading files containing special characters (accents, emojis, Asian scripts).

**Always specify the charset explicitly.**

**NIO.2 (Java 11+):**
```java
// Explicitly use UTF-8
String content = Files.readString(path, StandardCharsets.UTF_8);
List lines = Files.readAllLines(path, StandardCharsets.UTF_8);

Legacy IO:

// InputStreamReader bridges byte streams to character streams with a specific charset
try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8))) {
    // ...
}

Advanced Scenarios

Reading Large Files: Memory-Mapped Files

For very large files (gigabytes), standard streaming still involves copying data from kernel space to user space (JVM heap). Now, Memory-Mapped Files (MappedByteBuffer) map a region of the file directly into virtual memory. The OS handles paging; the JVM accesses it like a giant byte[] Most people skip this — try not to..

import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.nio.MappedByteBuffer;

try (FileChannel channel = FileChannel.And open(path, StandardOpenOption. Plus, rEAD)) {
    // Map the first 1GB (adjust size as needed)
    long size = channel. Now, size();
    MappedByteBuffer buffer = channel. map(FileChannel.On top of that, mapMode. READ_ONLY, 0, size);
    
    // Access bytes directly: buffer.In practice, get(), buffer. getInt(), etc.
    // Or decode to CharBuffer for text
} catch (IOException e) {
    e.printStackTrace();
}

*Warning: Mapping huge files consumes virtual address space. Because of that, on 32-bit JVMs this is severely limited. Unmapping relies on Cleaner/GC, which can be non-deterministic.

Some disagree here. Fair enough.

Reading from the Classpath (Resources)

In packaged applications (JARs), files are not on the file system but inside the JAR archive. Which means use ClassLoader. Practically speaking, you cannot use PathorFile directly. getResourceAsStream() Simple, but easy to overlook..


Here's a thinking process:

1.  **Analyze the User's Request:**
   - **Input:** A snippet of Java code ending with `}` and a section titled "Performance Caveat: Scanner is significantly slower than BufferedReader...". The user says: "Continue the article naturally. Do not repeat previous text. Finish with a proper conclusion."
   - **Context:** The provided text appears to be part of an article about Java file reading, comparing `Scanner` vs `BufferedReader`, discussing character encoding, NIO.2, memory-mapped files, and classpath resources. The input ends abruptly with `}` and then the performance caveat section.
   - **Task:** Continue the article from where the input left off, without repeating previous text, and finish with a proper conclusion.

2.  **Identify the Current State:**
   - The input ends with a `}` closing a try-with-resources block or method, followed by a "Performance Caveat" section about `Scanner`.
   - Wait, looking closer at the input structure:
     ```
                 System.out.println("Parsed: " + number);
             }
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         }
     }
     ```
   - Then: `**Performance Caveat:** `Scanner` is significantly slower...`
   - The input already includes the Performance Caveat, NIO.2 section, Advanced Scenarios (Memory-Mapped Files), and then "### Reading from the Classpath (Resources)" with a code snippet that cuts off at `````.
   - Actually, the input ends with ````" maybe incomplete.
   - Let me re-read the user's message carefully:
     ```
                 System.out.println("Parsed: " + number);
             }
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         }
     }
     ```
     **Performance Caveat:** `Scanner` is significantly slower than `BufferedReader` due to its parsing overhead and internal regex processing. Avoid it for high-throughput line-by-line reading of large logs.
   
   ## Handling Character Encoding Explicitly
   
   A common source of bugs is relying on the platform's *default charset*. The default encoding varies by OS (UTF-8 on Linux/macOS, often Cp1252 on Windows). This leads to "works on my machine" failures when reading files containing special characters (accents, emojis, Asian scripts).
   
   **Always specify the charset explicitly.**
   
   **NIO.2 (Java 11+):**
   ```java
   // Explicitly use UTF-8
   String content = Files.readString(path, StandardCharsets.UTF_8);
   List lines = Files.readAllLines(path, StandardCharsets.UTF_8);

Legacy IO:

// InputStreamReader bridges byte streams to character streams with a specific charset
try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8))) {
    // ...
}

Advanced Scenarios

Reading Large Files: Memory-Mapped Files

For very large files (gigabytes), standard streaming still involves copying data from kernel space to user space (JVM heap). Memory-Mapped Files (MappedByteBuffer) map a region of the file directly into virtual memory. The OS handles paging; the JVM accesses it like a giant byte[] And that's really what it comes down to. That alone is useful..

import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.nio.MappedByteBuffer;

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
    // Map the first 1GB (adjust size as needed)
    long size = channel.size();
    MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
    
    // Access bytes directly: buffer.get(), buffer.getInt(), etc.
    // Or decode to CharBuffer for text
} catch (IOException e) {
    e.printStackTrace();
}

Warning: Mapping huge files consumes virtual address space. On 32-bit JVMs this is severely limited. Unmapping relies on Cleaner/GC, which can be non-deterministic.

Reading from the Classpath (Resources)

In packaged applications (JARs), files are not on the file system but inside the JAR archive. You cannot use Path or File directly. Use ClassLoader.getResourceAsStream().


The input ends with ````" inside a code block that seems cut off. The user wants me to continue the article smoothly, not repeat previous text, and finish with a proper conclusion.

I need to figure out what's missing. Which means the section "### Reading from the Classpath (Resources)" has a code snippet that ends with ````" but the content after that is missing. The user wants me to continue from there, or from the end of the provided text, easily.

Let me look at the structure:
- The text starts with some code snippet (try-catch with Scanner/FileNotFoundException).
- Then "Performance Caveat" section.
- "Handling Character Encoding Explicitly" section.
- "Advanced Scenarios" section with:
  - "Reading Large Files: Memory-Mapped Files"
  - "Reading from the Classpath (Resources)" - this is where it cuts off.

Honestly, this part trips people up more than it should.

The user says: "Continue the article easily

```java
try (InputStream is = getClass().getResourceAsStream("/data/config.properties");
     BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // Process each line
    }
} catch (NullPointerException e) {
    // Resource not found - handle appropriately
    e.printStackTrace();
}

When working with resources, always handle the potential NullPointerException that occurs if the resource doesn't exist. For production applications, consider using Optional or defensive checks with getResource() returning null when not found.

Binary File Handling

Not all files are text-based. For binary data (images, serialized objects, etc.), use InputStream/OutputStream directly without character decoding:

try (InputStream is = new FileInputStream("image.jpg");
     OutputStream os = new FileOutputStream("copy.jpg")) {
    
    byte[] buffer = new byte[8192]; // 8KB buffer
    int bytesRead;
    while ((bytesRead = is.read(buffer)) != -1) {
        os.write(buffer, 0, bytesRead);
    }
} catch (IOException e) {
    e.printStackTrace();
}

Best Practices Summary

For text files:

  • Use BufferedReader with explicit charset for better performance and correctness
  • Prefer StandardCharsets.UTF_8 over platform-default charsets
  • Consider memory-mapped files for extremely large files (gigabytes+)

For binary files:

  • Use buffered streams (BufferedInputStream/BufferedOutputStream) for efficiency
  • Always specify buffer sizes appropriate for your use case

For resources in JARs:

  • Use ClassLoader.getResourceAsStream() for classpath resources
  • Handle missing resources gracefully

General recommendations:

  • Always use try-with-resources for automatic cleanup
  • Prefer NIO.2 (java.nio.file) for modern Java applications
  • Be cautious with memory-mapped files on 32-bit JVMs

Conclusion

Java's file I/O ecosystem provides dependable tools for virtually every file handling scenario. The key to effective file processing lies in choosing the right abstraction for your specific needs: Scanner for simple tokenized reading, BufferedReader for efficient line-by-line text processing, memory-mapped files for massive datasets, and classpath resources for packaged applications. By understanding these different approaches and their trade-offs in performance, memory usage, and complexity, you can make informed decisions that balance development speed with runtime efficiency. Remember that proper exception handling and resource management are just as important as selecting the right I/O strategy, ensuring your applications remain reliable and maintainable across diverse deployment environments But it adds up..

What's New

Just Went Live

Branching Out from Here

In the Same Vein

Thank you for reading about How To Read File In Java. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home