
Java Read Text File Tutorial
Reading text files in Java is one of those fundamental tasks that every developer runs into sooner or later, especially when you’re dealing with server configurations, log files, or data processing scripts. Whether you’re parsing nginx access logs, reading configuration files on your production server, or processing batch data uploads, knowing how to efficiently read text files in Java can save you countless hours and headaches. This guide will walk you through various methods to read text files in Java, from simple one-liners to robust enterprise-ready solutions that handle edge cases gracefully. We’ll cover everything from basic file I/O to modern NIO.2 approaches, with real-world examples you can actually use in your server management scripts.
How Does Java File Reading Actually Work?
Under the hood, Java provides several layers of abstraction for file operations. At the lowest level, you’ve got the traditional I/O streams that have been around since Java 1.0, and then there’s the newer NIO (New I/O) and NIO.2 packages that offer more efficient and flexible approaches.
The classic approach uses FileInputStream
and BufferedReader
classes. When you create a FileInputStream
, Java opens a file descriptor at the OS level and creates a stream of bytes. The BufferedReader
wraps this stream and adds buffering (typically 8192 bytes by default), which dramatically improves performance by reducing system calls.
The newer NIO.2 approach (introduced in Java 7) uses the Files
class and Path
objects. This is generally more efficient and provides better error handling. Here’s why:
- Memory mapping: NIO.2 can map files directly into memory for ultra-fast access
- Non-blocking I/O: Better for concurrent operations
- Better exception handling: More specific exceptions help with debugging
- Atomic operations: Safer for concurrent file access scenarios
Performance-wise, here’s what you can expect on a typical server setup:
Method | Small Files (<1MB) | Large Files (>100MB) | Memory Usage | Best For |
---|---|---|---|---|
Files.readAllLines() | ~2ms | High memory usage | Loads entire file | Small config files |
BufferedReader | ~3ms | ~200ms | Low, streaming | Log processing |
Scanner | ~5ms | ~400ms | Medium | Parsing structured data |
Files.lines() | ~2ms | ~150ms | Very low, lazy | Stream processing |
Step-by-Step Setup and Implementation
Let’s dive into the practical stuff. I’ll show you several approaches, starting with the most commonly used ones.
Method 1: Files.readAllLines() – The Quick and Dirty Approach
This is probably the easiest method if you’re dealing with relatively small files (think configuration files, small logs, etc.):
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.List;
public class FileReaderExample {
public static void readSmallFile() {
try {
Path filePath = Paths.get("/var/log/application.log");
List<String> lines = Files.readAllLines(filePath);
for (String line : lines) {
System.out.println(line);
}
// Or use streams for more advanced processing
lines.stream()
.filter(line -> line.contains("ERROR"))
.forEach(System.out::println);
} catch (IOException e) {
System.err.println("Failed to read file: " + e.getMessage());
}
}
}
Pros: Simple, clean code. Great for small files.
Cons: Loads entire file into memory. Will crash with OutOfMemoryError on large files.
Method 2: BufferedReader – The Reliable Workhorse
This is your go-to method for most scenarios, especially when processing server logs or data files:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class BufferedReaderExample {
// Modern approach with try-with-resources
public static void readWithBufferedReader(String filePath) {
try (BufferedReader reader = Files.newBufferedReader(Paths.get(filePath))) {
String line;
int lineNumber = 0;
while ((line = reader.readLine()) != null) {
lineNumber++;
// Process line here - example: find error logs
if (line.toLowerCase().contains("error")) {
System.out.printf("Line %d: %s%n", lineNumber, line);
}
// Add rate limiting for large files
if (lineNumber % 10000 == 0) {
System.out.printf("Processed %d lines...%n", lineNumber);
}
}
} catch (IOException e) {
System.err.println("Error reading file: " + e.getMessage());
}
}
// Legacy approach (still works, but less clean)
public static void readWithLegacyBufferedReader(String filePath) {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
}
}
}
Method 3: Files.lines() – The Stream Processing Champion
This is the most modern and efficient approach, especially powerful when combined with Java 8+ stream operations:
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.stream.Stream;
public class StreamFileReader {
public static void readWithStreams(String filePath) {
try (Stream<String> lines = Files.lines(Paths.get(filePath))) {
// Example 1: Count error occurrences
long errorCount = lines
.filter(line -> line.contains("ERROR"))
.count();
System.out.println("Found " + errorCount + " errors");
} catch (IOException e) {
System.err.println("Failed to read file: " + e.getMessage());
}
// Example 2: More complex processing
try (Stream<String> lines = Files.lines(Paths.get(filePath))) {
lines
.filter(line -> !line.trim().isEmpty()) // Skip empty lines
.filter(line -> line.startsWith("[")) // Only log entries
.map(String::trim)
.limit(100) // Process only first 100 matching lines
.forEach(System.out::println);
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
}
}
// Advanced example: Parse nginx access logs
public static void parseNginxLogs(String logPath) {
try (Stream<String> lines = Files.lines(Paths.get(logPath))) {
lines
.filter(line -> line.contains(" 404 "))
.map(line -> {
String[] parts = line.split(" ");
return parts[0] + " - " + parts[6]; // IP and requested URL
})
.distinct()
.forEach(System.out::println);
} catch (IOException e) {
System.err.println("Error processing nginx logs: " + e.getMessage());
}
}
}
Method 4: Scanner – The Swiss Army Knife
Scanner is particularly useful when you need to parse structured data or when you need more control over how the input is tokenized:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ScannerFileReader {
public static void readWithScanner(String filePath) {
try (Scanner scanner = new Scanner(new File(filePath))) {
// Read line by line
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}
} catch (FileNotFoundException e) {
System.err.println("File not found: " + filePath);
}
}
// Parse structured data (e.g., CSV-like files)
public static void parseStructuredData(String filePath) {
try (Scanner scanner = new Scanner(new File(filePath))) {
scanner.useDelimiter(",|\\n"); // Split on commas or newlines
while (scanner.hasNext()) {
String token = scanner.next().trim();
System.out.println("Token: " + token);
}
} catch (FileNotFoundException e) {
System.err.println("File not found: " + filePath);
}
}
// Read numbers from a file
public static void readNumbers(String filePath) {
try (Scanner scanner = new Scanner(new File(filePath))) {
while (scanner.hasNextDouble()) {
double number = scanner.nextDouble();
System.out.println("Number: " + number);
}
} catch (FileNotFoundException e) {
System.err.println("File not found: " + filePath);
}
}
}
Real-World Examples and Use Cases
Let’s look at some practical scenarios you might encounter when managing servers or processing data.
Use Case 1: Log File Analysis
Here’s a complete example that processes Apache/Nginx access logs to find the most frequently accessed pages:
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.regex.Pattern;
public class LogAnalyzer {
// Pattern for common log format: IP - - [timestamp] "GET /path HTTP/1.1" status size
private static final Pattern LOG_PATTERN = Pattern.compile(".*?\"\\w+ (.*?) HTTP.*?\" (\\d+) .*");
public static void analyzeAccessLogs(String logPath) {
try (Stream<String> lines = Files.lines(Paths.get(logPath))) {
Map<String, Long> urlCounts = lines
.filter(line -> line.contains("GET"))
.map(LOG_PATTERN::matcher)
.filter(matcher -> matcher.matches())
.map(matcher -> matcher.group(1)) // Extract URL path
.collect(Collectors.groupingBy(
url -> url,
Collectors.counting()
));
// Print top 10 most accessed URLs
urlCounts.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.forEach(entry ->
System.out.printf("%s: %d requests%n", entry.getKey(), entry.getValue()));
} catch (Exception e) {
System.err.println("Error analyzing logs: " + e.getMessage());
}
}
// Find 404 errors
public static void find404Errors(String logPath) {
try (Stream<String> lines = Files.lines(Paths.get(logPath))) {
lines
.filter(line -> line.contains(" 404 "))
.limit(50) // Don't spam the output
.forEach(System.out::println);
} catch (Exception e) {
System.err.println("Error finding 404s: " + e.getMessage());
}
}
}
Use Case 2: Configuration File Processing
Processing configuration files is a bread-and-butter task for server management:
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class ConfigProcessor {
// Parse key=value configuration files
public static Map<String, String> loadConfig(String configPath) {
Map<String, String> config = new HashMap<>();
try (Stream<String> lines = Files.lines(Paths.get(configPath))) {
lines
.filter(line -> !line.trim().isEmpty()) // Skip empty lines
.filter(line -> !line.trim().startsWith("#")) // Skip comments
.filter(line -> line.contains("=")) // Only key=value pairs
.forEach(line -> {
String[] parts = line.split("=", 2);
if (parts.length == 2) {
config.put(parts[0].trim(), parts[1].trim());
}
});
} catch (Exception e) {
System.err.println("Error loading config: " + e.getMessage());
}
return config;
}
// Validate database connection settings
public static boolean validateDbConfig(String configPath) {
Map<String, String> config = loadConfig(configPath);
String[] requiredKeys = {"db.host", "db.port", "db.name", "db.user", "db.password"};
for (String key : requiredKeys) {
if (!config.containsKey(key) || config.get(key).isEmpty()) {
System.err.println("Missing or empty config key: " + key);
return false;
}
}
// Validate port is numeric
try {
Integer.parseInt(config.get("db.port"));
} catch (NumberFormatException e) {
System.err.println("Invalid port number: " + config.get("db.port"));
return false;
}
return true;
}
}
Use Case 3: CSV Data Processing
Here’s how to handle CSV files efficiently:
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class CsvProcessor {
public static void processCsvFile(String csvPath) {
try (Stream<String> lines = Files.lines(Paths.get(csvPath))) {
lines
.skip(1) // Skip header row
.map(line -> line.split(","))
.filter(columns -> columns.length >= 3) // Ensure minimum columns
.forEach(columns -> {
// Process each row
System.out.printf("ID: %s, Name: %s, Value: %s%n",
columns[0].trim(),
columns[1].trim(),
columns[2].trim());
});
} catch (Exception e) {
System.err.println("Error processing CSV: " + e.getMessage());
}
}
// Handle CSV with quoted fields and commas inside quotes
public static void processComplexCsv(String csvPath) {
try (Stream<String> lines = Files.lines(Paths.get(csvPath))) {
lines
.skip(1)
.map(CsvProcessor::parseCsvLine)
.filter(fields -> !fields.isEmpty())
.forEach(fields -> {
System.out.println("Fields: " + fields);
});
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
// Simple CSV parser that handles quoted fields
private static List<String> parseCsvLine(String line) {
// This is a simplified parser - for production use a library like OpenCSV
return Arrays.asList(line.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"));
}
}
Error Handling and Edge Cases
Here’s a robust file reader that handles common edge cases:
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.stream.Stream;
public class RobustFileReader {
public static void safeFileRead(String filePath) {
Path path = Paths.get(filePath);
// Check if file exists
if (!Files.exists(path)) {
System.err.println("File does not exist: " + filePath);
return;
}
// Check if it's actually a file (not a directory)
if (!Files.isRegularFile(path)) {
System.err.println("Path is not a regular file: " + filePath);
return;
}
// Check if file is readable
if (!Files.isReadable(path)) {
System.err.println("File is not readable: " + filePath);
return;
}
// Check file size before loading into memory
try {
long fileSize = Files.size(path);
if (fileSize > 100_000_000) { // 100MB limit
System.err.println("File too large for in-memory processing: " + fileSize + " bytes");
processLargeFile(path);
return;
}
} catch (IOException e) {
System.err.println("Cannot determine file size: " + e.getMessage());
return;
}
// Process the file with explicit charset
try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
lines.forEach(System.out::println);
} catch (IOException e) {
System.err.println("Error reading file: " + e.getMessage());
} catch (OutOfMemoryError e) {
System.err.println("File too large for available memory");
processLargeFile(path);
}
}
private static void processLargeFile(Path path) {
System.out.println("Processing large file in chunks...");
try (Stream<String> lines = Files.lines(path)) {
lines
.limit(1000) // Process in batches
.forEach(System.out::println);
} catch (IOException e) {
System.err.println("Error processing large file: " + e.getMessage());
}
}
// Handle different encodings
public static void readWithEncoding(String filePath, String encoding) {
try {
Path path = Paths.get(filePath);
// Try to detect charset or use specified one
java.nio.charset.Charset charset = java.nio.charset.Charset.forName(encoding);
try (Stream<String> lines = Files.lines(path, charset)) {
lines.forEach(System.out::println);
}
} catch (java.nio.charset.UnsupportedCharsetException e) {
System.err.println("Unsupported encoding: " + encoding);
} catch (IOException e) {
System.err.println("IO Error: " + e.getMessage());
}
}
}
Performance Comparison Example
Here’s a benchmarking utility to compare different methods:
import java.nio.file.*;
import java.io.*;
import java.util.Scanner;
import java.util.stream.Stream;
public class FileReadingBenchmark {
public static void benchmarkMethods(String filePath) {
System.out.println("Benchmarking file reading methods for: " + filePath);
// Method 1: Files.readAllLines()
long start = System.currentTimeMillis();
try {
Files.readAllLines(Paths.get(filePath));
long end = System.currentTimeMillis();
System.out.println("Files.readAllLines(): " + (end - start) + "ms");
} catch (Exception e) {
System.out.println("Files.readAllLines() failed: " + e.getMessage());
}
// Method 2: BufferedReader
start = System.currentTimeMillis();
try (BufferedReader reader = Files.newBufferedReader(Paths.get(filePath))) {
while (reader.readLine() != null) {
// Just read, don't process
}
long end = System.currentTimeMillis();
System.out.println("BufferedReader: " + (end - start) + "ms");
} catch (Exception e) {
System.out.println("BufferedReader failed: " + e.getMessage());
}
// Method 3: Files.lines()
start = System.currentTimeMillis();
try (Stream<String> lines = Files.lines(Paths.get(filePath))) {
lines.count(); // Force evaluation
long end = System.currentTimeMillis();
System.out.println("Files.lines(): " + (end - start) + "ms");
} catch (Exception e) {
System.out.println("Files.lines() failed: " + e.getMessage());
}
// Method 4: Scanner
start = System.currentTimeMillis();
try (Scanner scanner = new Scanner(new File(filePath))) {
while (scanner.hasNextLine()) {
scanner.nextLine();
}
long end = System.currentTimeMillis();
System.out.println("Scanner: " + (end - start) + "ms");
} catch (Exception e) {
System.out.println("Scanner failed: " + e.getMessage());
}
}
}
Related Tools and Integration Possibilities
Java file reading becomes even more powerful when combined with other tools and libraries:
- Apache Commons IO: Provides utilities like
FileUtils.readLines()
andIOUtils.toString()
- Google Guava: Offers
Files.readLines()
andCharStreams.toString()
- Jackson: Perfect for reading JSON configuration files
- OpenCSV: Industry-standard CSV parsing library
- Log4j/Logback: For structured log file processing
Here’s an interesting integration example with system monitoring:
import java.nio.file.*;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class LogMonitor {
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public static void startLogMonitoring(String logPath) {
scheduler.scheduleAtFixedRate(() -> {
try {
long errorCount = Files.lines(Paths.get(logPath))
.filter(line -> line.contains("ERROR"))
.count();
if (errorCount > 10) {
System.out.printf("[%s] WARNING: %d errors found in log%n",
LocalDateTime.now(), errorCount);
// Could send email, webhook, etc.
}
} catch (Exception e) {
System.err.println("Monitoring error: " + e.getMessage());
}
}, 0, 60, TimeUnit.SECONDS); // Check every minute
}
}
For server deployments, you might want to consider containerized solutions. If you’re looking to set up a proper development or production environment, check out these hosting options: VPS hosting for smaller applications or dedicated servers for high-performance requirements.
Automation and Scripting Possibilities
File reading in Java opens up tons of automation possibilities. Here are some creative uses:
- Automated log rotation: Read, process, and archive log files based on size or age
- Configuration hot-reloading: Monitor config files for changes and update application settings
- Data ETL pipelines: Extract data from various text formats and transform for databases
- Server health monitoring: Parse system logs to detect anomalies
- Automated report generation: Process data files to generate summary reports
Here’s a practical example of a file watcher that automatically processes new files:
import java.nio.file.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class FileWatcher {
public static void watchDirectory(String dirPath) throws IOException, InterruptedException {
Path dir = Paths.get(dirPath);
WatchService watcher = FileSystems.getDefault().newWatchService();
dir.register(watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Watching directory: " + dirPath);
while (true) {
WatchKey key = watcher.poll(10, TimeUnit.SECONDS);
if (key == null) continue;
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
Path filename = (Path) event.context();
if (filename.toString().endsWith(".log")) {
System.out.println("Processing new log file: " + filename);
processLogFile(dir.resolve(filename));
}
}
if (!key.reset()) break;
}
}
private static void processLogFile(Path logFile) {
try (var lines = Files.lines(logFile)) {
long errorCount = lines
.filter(line -> line.contains("ERROR"))
.count();
System.out.println("Found " + errorCount + " errors in " + logFile.getFileName());
} catch (IOException e) {
System.err.println("Error processing " + logFile + ": " + e.getMessage());
}
}
}
Performance Tips and Best Practices
Based on extensive testing and real-world usage, here are some performance tips:
- Use appropriate buffer sizes: Default 8KB is good for most cases, but 64KB can be better for large files
- Prefer Files.lines() for large files: It’s lazy and memory-efficient
- Always use try-with-resources: Prevents resource leaks
- Consider parallel processing: Use
.parallel()
on streams for CPU-intensive operations - Handle encoding explicitly: Always specify charset to avoid platform-dependent behavior
// Optimized large file processing
public static void processLargeFileOptimized(String filePath) {
try (Stream<String> lines = Files.lines(Paths.get(filePath), StandardCharsets.UTF_8)) {
lines
.parallel() // Enable parallel processing
.filter(line -> !line.trim().isEmpty())
.filter(line -> line.contains("ERROR"))
.map(String::toUpperCase)
.forEach(System.out::println);
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
}
}
Conclusion and Recommendations
After working with Java file I/O for years in production environments, here’s my take on when to use each approach:
Use Files.readAllLines() when:
- Files are small (<10MB)
- You need to access lines randomly or multiple times
- Simplicity is more important than memory efficiency
Use BufferedReader when:
- You’re dealing with legacy code or Java versions < 8
- You need line-by-line processing with complex logic
- Memory usage is a concern
Use Files.lines() when:
- Files are large or size is unknown
- You want to leverage Stream API features
- Performance and memory efficiency are priorities
Use Scanner when:
- You need to parse structured data
- Input format is complex (mixed types, delimiters)
- You’re prototyping or learning
For production server environments, I recommend sticking with Files.lines() as your default choice. It’s modern, efficient, and handles most edge cases gracefully. Always include proper error handling and consider the file size before choosing your approach.
Remember that file I/O is often a bottleneck in server applications, so profiling your specific use case is crucial. The methods shown here should handle most scenarios you’ll encounter when processing configuration files, logs, or data files on your servers.
If you’re planning to deploy Java applications that do heavy file processing, consider the infrastructure requirements carefully. For development and testing, a VPS solution might be sufficient, but for production workloads with large files, you might need dedicated server resources to ensure consistent performance.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.