BufferedReader
BufferedReader wraps any Reader with an in-memory character buffer, dramatically reducing system calls for character-by-character or line-by-line reading. Its defining method is readLine(), which reads a complete line of text terminated by \n, \r, or \r\n and returns it without the terminator, or returns null at end-of-file. Beyond buffering, BufferedReader also exposes a lines() method (Java 8+) that returns a lazy Stream<String> of lines, enabling the full Stream API for file processing without loading the entire file into memory. BufferedReader supports mark/reset with a caller-specified readAheadLimit. It is obtained either by wrapping a Reader (new BufferedReader(new FileReader(...))) or directly from Files.newBufferedReader(path, charset), which is the preferred idiom in modern Java. This entry covers construction and buffer sizing, all read methods and their contracts, readLine() edge cases (empty lines, last line without terminator), the lines() stream and its relationship to reader lifecycle, mark/reset semantics with readAheadLimit, and the use of BufferedReader as a base for protocol parsing.
Construction, Buffer Sizing, and Core Read Methods
// ── Construction ─────────────────────────────────────────────────────
// Standard: wrap FileReader (prefer the Java-11 charset constructor):
BufferedReader br1 = new BufferedReader(
new FileReader("text.txt", StandardCharsets.UTF_8));
// Custom buffer size: 64KB for large file processing
BufferedReader br2 = new BufferedReader(
new FileReader("large.txt", StandardCharsets.UTF_8), 65_536);
// From Files.newBufferedReader — the preferred modern idiom:
BufferedReader br3 = Files.newBufferedReader(
Path.of("text.txt"), StandardCharsets.UTF_8);
// From InputStreamReader for non-file sources (classpath, network, etc.):
InputStream is = HttpURLConnection.openStream(new URL("https://example.com"));
BufferedReader br4 = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8));
// ── read() single character ────────────────────────────────────────────
try (BufferedReader br = Files.newBufferedReader(
Path.of("text.txt"), StandardCharsets.UTF_8)) {
int ch;
while ((ch = br.read()) != -1) {
System.out.print((char) ch); // cast int to char for output
}
}
// ── read(char[], offset, len) bulk read ───────────────────────────────
try (BufferedReader br = Files.newBufferedReader(
Path.of("text.txt"), StandardCharsets.UTF_8)) {
char[] buffer = new char[4096];
int charsRead;
StringBuilder sb = new StringBuilder();
while ((charsRead = br.read(buffer, 0, buffer.length)) != -1) {
sb.append(buffer, 0, charsRead);
}
String content = sb.toString();
}
// ── skip() skips characters efficiently ──────────────────────────────
try (BufferedReader br = Files.newBufferedReader(
Path.of("text.txt"), StandardCharsets.UTF_8)) {
br.skip(100); // skip first 100 characters
System.out.println(br.readLine()); // read line 101+ chars in
}
// ── ready() for availability check ───────────────────────────────────
try (BufferedReader br = Files.newBufferedReader(
Path.of("text.txt"), StandardCharsets.UTF_8)) {
System.out.println("Ready: " + br.ready()); // true if buffer or file has content
// Do NOT use as: while (br.ready()) { br.readLine(); } — may stop mid-file
// if buffer is momentarily empty between fills. Use readLine() != null loop instead.
}readLine(), Edge Cases, and the lines() Stream
// ── readLine() canonical loop ─────────────────────────────────────────
try (BufferedReader br = Files.newBufferedReader(
Path.of("text.txt"), StandardCharsets.UTF_8)) {
String line;
while ((line = br.readLine()) != null) { // null = EOF
System.out.println(line); // line has NO trailing newline
}
}
// ── Edge cases ────────────────────────────────────────────────────────
// File: "Hello
World
" (trailing newline)
// readLine() calls: "Hello", "World", null
// Two non-null calls, then null.
// File: "Hello
World" (NO trailing newline)
// readLine() calls: "Hello", "World", null
// Same result — last line without terminator is returned normally.
// File: "
" (three empty lines)
// readLine() calls: "", "", "", null
// Empty strings, not nulls.
// File: "" (empty file)
// readLine() calls: null immediately.
// ── Common WRONG pattern: checking isEmpty instead of null ─────────────
// WRONG: stops at the first empty line
while (!(line = br.readLine()).isEmpty()) { ... } // NullPointerException at EOF too
// CORRECT:
while ((line = br.readLine()) != null) {
if (!line.isEmpty()) { /* process non-empty line */ }
}
// ── lines() stream: lazy line-by-line processing ──────────────────────
try (BufferedReader br = Files.newBufferedReader(
Path.of("access.log"), StandardCharsets.UTF_8)) {
long errorCount = br.lines()
.filter(line -> line.contains("ERROR"))
.count();
System.out.println("Errors: " + errorCount);
} // br.close() also closes the stream
// ── lines() with Files.lines shorthand ────────────────────────────────
// Equivalent: Files.lines(path, charset) returns Stream<String> directly
try (Stream<String> lines = Files.lines(
Path.of("large.log"), StandardCharsets.UTF_8)) {
lines.filter(l -> l.startsWith("WARN"))
.map(l -> l.substring(5))
.forEach(System.out::println);
} // stream.close() closes the underlying BufferedReader
// ── lines() parallel: decouple I/O from processing ───────────────────
// Sequential I/O into a list, then parallel processing:
List<String> allLines = Files.readAllLines(
Path.of("data.csv"), StandardCharsets.UTF_8);
long count = allLines.parallelStream()
.filter(line -> line.contains(",error,"))
.count();
// ── lines() with UncheckedIOException ────────────────────────────────
try (Stream<String> lines = Files.lines(Path.of("text.txt"))) {
lines.forEach(line -> {
// IOException during stream reading wrapped in UncheckedIOException:
System.out.println(line);
});
} catch (UncheckedIOException e) {
System.out.println("I/O error during streaming: " + e.getCause());
}mark/reset, Protocol Parsing, and Advanced Patterns
// ── mark/reset with BufferedReader ────────────────────────────────────
try (BufferedReader br = Files.newBufferedReader(
Path.of("text.txt"), StandardCharsets.UTF_8)) {
System.out.println("markSupported: " + br.markSupported()); // true
br.mark(200); // mark current position; can read up to 200 chars before invalidation
String first = br.readLine();
String second = br.readLine();
System.out.println("First two lines: " + first + " | " + second);
br.reset(); // return to marked position
String again = br.readLine();
System.out.println("First line again: " + again); // same as 'first'
}
// ── Protocol parsing: HTTP response headers ────────────────────────────
public static Map<String, String> parseHttpHeaders(BufferedReader br)
throws IOException {
Map<String, String> headers = new LinkedHashMap<>();
// First line: "HTTP/1.1 200 OK"
String statusLine = br.readLine();
if (statusLine == null) throw new IOException("No status line");
System.out.println("Status: " + statusLine);
// Headers: "Content-Type: application/json" until blank line
String header;
while ((header = br.readLine()) != null && !header.isEmpty()) {
int colon = header.indexOf(':');
if (colon > 0) {
String name = header.substring(0, colon).trim();
String value = header.substring(colon + 1).trim();
headers.put(name, value);
}
}
// After the blank line: body (not read here — caller handles)
return headers;
}
// ── PushbackReader for single-character look-ahead ─────────────────────
try (PushbackReader pbr = new PushbackReader(
new BufferedReader(new FileReader("data.txt", StandardCharsets.UTF_8)))) {
int ch = pbr.read();
if (ch == '{') {
pbr.unread(ch); // push '{' back — next read returns '{' again
parseJson(pbr);
} else if (ch == '<') {
pbr.unread(ch);
parseXml(pbr);
}
}
// ── Reading entire file into String efficiently ───────────────────────
// Option 1: BufferedReader + StringBuilder
public static String readAllChars(Path path, Charset charset) throws IOException {
try (BufferedReader br = Files.newBufferedReader(path, charset)) {
StringBuilder sb = new StringBuilder();
char[] buf = new char[8192];
int n;
while ((n = br.read(buf)) != -1) {
sb.append(buf, 0, n);
}
return sb.toString();
}
}
// Option 2: Files.readString (Java 11+) — most concise
String content = Files.readString(Path.of("text.txt"), StandardCharsets.UTF_8);
// Option 3: lines() joined (slightly less efficient — creates list of strings)
String joined = Files.lines(Path.of("text.txt"), StandardCharsets.UTF_8)
.collect(Collectors.joining("
"));
// ── Counting words in a large file without loading all into memory ────
try (BufferedReader br = Files.newBufferedReader(
Path.of("novel.txt"), StandardCharsets.UTF_8)) {
long wordCount = br.lines()
.flatMap(line -> Arrays.stream(line.split("\s+")))
.filter(word -> !word.isEmpty())
.count();
System.out.println("Words: " + wordCount);
}