01 · Java basics — note 12 of 12
Input and output
Read input and print results quickly using Scanner, BufferedReader, and PrintWriter.
Java provides Scanner and System.out for convenient console input and formatted printing, while BufferedReader and PrintWriter handle large datasets efficiently by buffering characters in memory.
The idea in real life
Think of a post office customer sending a stack of letters. Dropping each single envelope into a distant street mailbox one by one requires a separate trip for every letter. Gathering fifty letters into a satchel and carrying them all in a single run saves countless trips. Java input and output work the same way: buffered streams gather characters in memory so your program makes fewer costly requests to the operating system.
Why it exists
- Interactive programs and algorithm judges must accept problem data from standard input and write answers to standard output.
- Simple parsing tools like
Scannerwork well for small scripts but slow down noticeably on thousands of numbers because of regular expression parsing. - Unbuffered writing triggers frequent, expensive operating system calls for every tiny string or number printed.
- Understanding input streams, line terminators, and buffer flushing prevents subtle bugs such as skipped input or missing output.
How it works
Printing
Use println for lines, print for continuous text, and printf for formats.
As introduced in how a Java program runs, System.out is the standard output stream used to display text on the console. Java provides three primary methods for writing output:
System.out.println: Prints the given value and appends a line break, moving the cursor to the start of the next line.System.out.print: Prints the given value and keeps the cursor directly after the last printed character, allowing subsequent calls to append text to the same line.System.out.printf: Prints formatted text using a format string and a list of arguments, matching the convention of the classic Cprintffunction.
Format strings use conversion specifiers that begin with a percent sign % followed by a character that indicates the data type:
%d: Formats a decimal integer (byte,short,int,long).%s: Formats any value as a string.%.2f: Formats a floating-point number (float,double) rounded to 2 decimal places.%%: Prints a literal percent character.%n: Inserts a platform-specific line break. Always use%ninstead of\ninside format strings so your output uses the correct line ending on both Windows and Linux.
When you need a formatted string without printing it directly to the console, use String.format. It takes the exact same format specifiers and returns a String object.
public class Main {
public static void main(String[] args) {
String student = "Alice";
int score = 95;
double percentage = 95.456;
System.out.print("Score: ");
System.out.println(score);
System.out.printf("Student: %s | Score: %d | Mark: %.2f%%%n", student, score, percentage);
String message = String.format("Summary: %s scored %d", student, score);
System.out.println(message);
}
}Score: 95
Student: Alice | Score: 95 | Mark: 95.46%
Summary: Alice scored 95In this program:
studentstores the name string"Alice".scorestores the integer value95.percentagestores the floating-point value95.456.System.out.printfprints the student name with%s, the score with%d, the percentage rounded to two decimals with%.2f, a literal%with%%, and ends with a platform newline%n.String.formatbuilds the textSummary: Alice scored 95and assigns it tomessage.
Reading with Scanner
Scanner breaks standard input into typed tokens using whitespace delimiters.
The Scanner class in java.util is the simplest way to read input in Java. You attach it to standard input by passing System.in to its constructor: Scanner sc = new Scanner(System.in);.
By default, Scanner treats any sequence of whitespace characters (spaces, tabs, and newlines) as a delimiter. It provides specialized methods to read and parse tokens directly into primitive variables:
sc.nextInt(): Scans the next token as anint.sc.nextLong(): Scans the next token as along.sc.next(): Scans and returns the next token as aString(stopping at the first whitespace character).sc.nextLine(): Scans and returns all characters from the current position up to the next line break, advancing past the line break.sc.hasNextInt(): Returnstrueif another integer token is available in the input stream.
3
Alice 88
Bob 92
Charlie 79import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int count = sc.nextInt();
int totalScore = 0;
for (int i = 0; i < count; i++) {
String name = sc.next();
int score = sc.nextInt();
totalScore += score;
System.out.println("Read: " + name + " -> " + score);
}
System.out.println("Average: " + (totalScore / count));
sc.close();
}
}Read: Alice -> 88
Read: Bob -> 92
Read: Charlie -> 79
Average: 86In this program:
scis aScannerreading characters fromSystem.in.countreads the first integer3, indicating how many student records follow.- In each loop pass,
sc.next()reads the single-word stringname, andsc.nextInt()reads the integerscore. totalScoreaccumulates the scores, andtotalScore / countcomputes the integer average.sc.close()releases the scanner resources.
When running your program from a command line, you do not have to type inputs manually one by one. You can redirect an existing text file into standard input using the < shell operator:
java Main < input.txtYou can also pipe the output of another command directly into your program's standard input using the | operator:
cat input.txt | java MainThe nextLine trap
Reading a number leaves the trailing line break waiting in the buffer.
A frequent source of bugs when using Scanner occurs when calling nextLine() after calling nextInt(), nextLong(), or next().
Token-reading methods like sc.nextInt() scan only the characters that make up the number and stop as soon as they encounter whitespace. When you type 42 and press Enter, the input stream contains the characters 4, 2, and the line break \n. The nextInt() method consumes 4 and 2, but leaves \n sitting in the stream buffer.
If your next statement is String title = sc.nextLine();, nextLine() starts reading immediately from the current cursor position. It encounters that leftover \n at once, treats it as the end of the line, and returns an empty string "". Your program never waits for user input.
To fix this, insert an extra sc.nextLine() call immediately after reading the number. This extra call consumes and discards the leftover line break, leaving the cursor ready to read the actual text on the following line.
42
Algorithms and Data Structuresimport java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int id = sc.nextInt();
sc.nextLine(); // Consumes the leftover newline character
String title = sc.nextLine();
System.out.println("ID: " + id);
System.out.println("Title: " + title);
sc.close();
}
}ID: 42
Title: Algorithms and Data StructuresIn this program:
idreceives42fromsc.nextInt().- The first
sc.nextLine()reads and discards the newline character left in the buffer. - The second
sc.nextLine()cleanly captures the full string"Algorithms and Data Structures".
Reading until the end of input
Use hasNext in a while loop when input size is unknown.
Many algorithm problems do not specify the number of inputs in advance. Instead, they provide data continuously until the end of the input stream (often called EOF, or End of File).
To read an arbitrary stream of values, combine a while loop with a hasNext method:
sc.hasNextInt(): Checks if the stream contains another integer token before attempting to read it.sc.hasNext(): Checks if any token remains in the stream.sc.hasNextLine(): Checks if another line of text is available.
The condition sc.hasNextInt() evaluates to true as long as more integer tokens remain. When the stream reaches the end of input, hasNextInt() returns false, and the loop terminates cleanly.
12 18 24 6 10import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int total = 0;
int count = 0;
while (sc.hasNextInt()) {
int value = sc.nextInt();
total += value;
count++;
}
System.out.println("Count: " + count);
System.out.println("Total: " + total);
sc.close();
}
}Count: 5
Total: 70In this program:
while (sc.hasNextInt())keeps running as long as integer values are available in standard input.totalaccumulates the sum of all numbers read.countcounts how many numbers were processed.
When running interactively in a terminal, you signal the end of input manually:
- On Linux and macOS: press
Ctrl + D. - On Windows: press
Ctrl + Z, then pressEnter.
Going deeper
Why Scanner is slow
Scanner runs a regular expression for every token it reads.
While Scanner is simple to use, it is often too slow for algorithm problems with large inputs (such as 100,000 or 1,000,000 numbers).
According to the official Java API specification for java.util.Scanner, a Scanner tokenizes its input using regular expressions. Every time you call nextInt() or next(), Scanner executes pattern-matching logic through java.util.regex.Pattern to locate delimiters, validate characters, and check localized number formatting.
This regular expression engine creates temporary objects and performs multiple boundary checks for every single token. When reading a few dozen inputs from the console, this work takes fractions of a millisecond. However, when an algorithm judge supplies 1,000,000 integers, the repeated regex matching creates significant CPU overhead.
In one run on JDK 17 on the machine used to write this note, reading 1,000,000 space-separated integers took about 436 milliseconds with Scanner and about 59 milliseconds with BufferedReader and StringTokenizer. Your times will differ, but the gap is large enough to matter when a problem has a time limit and a big input.
Fast input with BufferedReader
BufferedReader loads large chunks of characters to reduce system call overhead.
To read large amounts of input quickly, use BufferedReader from the java.io package. It wraps standard input using a three-stage pipeline:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));Each class in this pipeline has a specific role:
System.in: A raw byte stream (InputStream) reading individual bytes directly from standard input.InputStreamReader: A character stream bridge that decodes incoming raw bytes into characters using the platform default character encoding (such as UTF-8).BufferedReader: A character wrapper that reads large chunks of characters into an internal memory buffer (defaulting to 8,192 characters in JDK 17) in a single request.
Because BufferedReader only provides methods to read raw characters and entire lines (br.readLine()), you must parse individual numbers yourself. To break a line of text into tokens, use java.util.StringTokenizer:
5
10 20 30 40 50import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int count = Integer.parseInt(br.readLine().trim());
StringTokenizer st = new StringTokenizer(br.readLine());
int sum = 0;
for (int i = 0; i < count; i++) {
sum += Integer.parseInt(st.nextToken());
}
System.out.println("Count: " + count);
System.out.println("Sum: " + sum);
}
}Count: 5
Sum: 150In this program:
br.readLine()reads the entire first line as a string,.trim()removes any trailing newline characters, andInteger.parseIntconverts the text"5"into the integer5.- The second
br.readLine()reads the entire line"10 20 30 40 50". new StringTokenizer(...)breaks the line into tokens using standard whitespace delimiters without regular expression overhead.st.nextToken()extracts each number string in order, andInteger.parseIntconverts it to an integer.
You can also split lines using line.trim().split("\\s+"). However, split compiles a regular expression and allocates a new array of strings on each call. For maximum speed on large inputs, StringTokenizer is preferred.
Fast output with PrintWriter
PrintWriter buffers text in memory instead of flushing on every line.
Just as reading with Scanner is slow on large datasets, printing with System.out.println is also slow when generating tens of thousands of lines.
By default, System.out is an instance of PrintStream created with auto-flushing enabled. As note 1 showed, every print or println call is passed on to the operating system straight away. Making 100,000 separate write requests slows a program down.
To print large volumes of output fast, wrap System.out in a PrintWriter backed by a BufferedOutputStream:
PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));BufferedOutputStream gathers printed characters inside an internal byte buffer (defaulting to 8,192 bytes). Characters sit in memory until the buffer fills up or until you explicitly command Java to flush them.
You must call out.flush() at the end of your program. If you forget out.flush(), any output remaining in the buffer when the JVM shuts down is never sent to the console, and your program appears to produce incomplete or empty output.
import java.io.BufferedOutputStream;
import java.io.PrintWriter;
public class Main {
public static void main(String[] args) {
PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
int limit = 5;
for (int i = 1; i <= limit; i++) {
out.print("Item ");
out.print(i);
out.print(": square = ");
out.println(i * i);
}
out.flush(); // Flushes remaining characters to standard output
}
}Item 1: square = 1
Item 2: square = 4
Item 3: square = 9
Item 4: square = 16
Item 5: square = 25In this program:
outwrites characters into the internal buffer ofBufferedOutputStream.- The loop calls
out.printandout.printlnrepeatedly without triggering immediate operating system write operations. out.flush()sends all accumulated characters to standard output in one batched operation.
Another approach for fast output is appending all characters into a StringBuilder and printing it with a single call to System.out.print(sb). However, PrintWriter avoids holding all output strings in memory simultaneously, keeping memory consumption low.
A reusable template
A compact main method template equips you for fast problem input.
In algorithm competitions and interview platforms, input data often spans multiple lines with an unpredictable number of tokens per line. Creating your own helper class is taught in chapter 02. Within a single main method, you can set up a compact, reliable pattern that handles any whitespace-delimited input cleanly:
4
15 25
35 45import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.BufferedOutputStream;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
StringTokenizer st = null;
String line = br.readLine();
if (line == null) {
return;
}
int count = Integer.parseInt(line.trim());
int runningTotal = 0;
for (int i = 0; i < count; i++) {
while (st == null || !st.hasMoreTokens()) {
String nextLine = br.readLine();
if (nextLine == null) {
break;
}
st = new StringTokenizer(nextLine);
}
int number = Integer.parseInt(st.nextToken());
runningTotal += number;
out.println("Added " + number + " -> running total " + runningTotal);
}
out.println("Final total: " + runningTotal);
out.flush();
}
}Added 15 -> running total 15
Added 25 -> running total 40
Added 35 -> running total 75
Added 45 -> running total 120
Final total: 120In this program:
ststores the currentStringTokenizerinstance.- The inner
while (st == null || !st.hasMoreTokens())loop checks if the tokenizer has exhausted the current line. If empty, it reads the next non-null line withbr.readLine()and creates a fresh tokenizer. - This pattern handles inputs reliably whether numbers are placed on a single line, separated by spaces, or spread across multiple lines.
Under the hood
Checked exceptions
The compiler requires methods to handle or declare possible I/O failures.
When calling br.readLine(), the Java compiler reports a compile error unless your code accounts for java.io.IOException. In Java, IOException is a checked exception.
Checked exceptions represent conditions outside the program's immediate control (such as a disconnected network stream, a deleted file, or an abrupt stream closure). The Java Language Specification requires that any method calling an operation that declares a checked exception must either catch it with a try-catch block or declare it in its own signature using the throws keyword.
In competitive programming and single-file DSA solutions, wrapping every input line in try-catch adds unnecessary boilerplate. Adding throws IOException to the main method signature informs the compiler that any unhandled I/O failure should pass directly to the JVM runtime launcher.
Standard input, output and error
Operating systems provide three standard file streams for every running process.
When an operating system launches a process, it opens three standard file descriptors and binds them to the execution environment:
- File Descriptor 0 (
stdin): Standard input, accessed in Java viaSystem.in. - File Descriptor 1 (
stdout): Standard output, accessed in Java viaSystem.out. - File Descriptor 2 (
stderr): Standard error, accessed in Java viaSystem.err.
Both System.out and System.err are instances of java.io.PrintStream. However, System.err is dedicated to diagnostic logging and error messages. Unlike standard output, System.err is typically not redirected when redirecting normal program output with >.
You can redirect and isolate these streams in your terminal:
java Main < input.txt > output.txt 2> error.logIn this command:
< input.txt: Feeds the contents ofinput.txttostdin(System.in).> output.txt: Directs normal output fromstdout(System.out) intooutput.txt, overwriting the file. Use>>to append instead of overwriting.2> error.log: Directs diagnostic messages fromstderr(System.err) intoerror.log.
Buffering
Batching character transfers avoids the steep cost of kernel context switches.
Whenever your program requests data from the console or disk, execution must transition from user space (where your Java code runs) to kernel space (where the operating system kernel controls hardware devices).
This transition requires a context switch. The CPU saves active register states, switches execution privilege levels, executes the system call (such as read or write in POSIX systems), and restores the user thread. Context switches consume CPU cycles and disrupt processor instruction caches.
If you read 100,000 characters one character at a time using an unbuffered stream, your application triggers 100,000 separate system calls.
A buffered stream solves this by allocating an internal array in memory. When you call br.readLine(), BufferedReader reads a large block (8,192 characters) from the operating system in a single system call. Subsequent requests for characters read directly from this in-memory buffer without contacting the operating system. Only when the internal buffer is completely exhausted does BufferedReader issue another system call.
Similarly, as explored in StringBuilder, building output in memory before writing to the stream avoids repetitive calls to the underlying output channel.
Line endings
Operating system conventions determine whether line breaks use LF or CRLF.
Different operating systems represent the end of a line of text using different character sequences:
- Unix, Linux, and macOS: Use a single Line Feed character (
\n, ASCII value 10, often called LF). - Windows: Uses a two-character sequence consisting of Carriage Return followed by Line Feed (
\r\n, ASCII values 13 and 10, often called CRLF). - Classic Mac OS (prior to macOS): Used a single Carriage Return character (
\r, ASCII value 13, CR).
When you write System.out.println("Hello");, Java writes the characters followed by the system-dependent line separator. Java provides two ways to inspect or produce this separator programmatically:
System.lineSeparator(): Returns the line break string defined for the current host environment (returns"\r\n"on Windows and"\n"on Linux).%ninsideprintforString.format: Dynamically emits the value ofSystem.lineSeparator().
Online judges compare your output line by line, and they accept \n as a line break. Writing \n keeps your output the same on every operating system, which is why the fast templates in this note use it.
In Java
4
101 Alice 88
102 Bob 94
103 Charlie 76
104 Diana 91import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.BufferedOutputStream;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.StringTokenizer;
public class Main {
// Returns the average score as a double from total and count.
static double calculateAverage(int total, int count) {
if (count == 0) {
return 0.0;
}
return (double) total / count;
}
// Prints a single student row formatted into fixed-width columns.
static void printRecord(PrintWriter out, int id, String name, int score) {
out.printf("ID: %-4d | Name: %-8s | Score: %3d%n", id, name, score);
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
StringTokenizer st = null;
String line = br.readLine();
if (line == null) {
out.flush();
return;
}
int count = Integer.parseInt(line.trim());
int totalScore = 0;
int highestScore = -1;
String topStudent = "";
out.println("--- Student Score Report ---");
for (int i = 0; i < count; i++) {
while (st == null || !st.hasMoreTokens()) {
String nextLine = br.readLine();
if (nextLine == null) {
break;
}
st = new StringTokenizer(nextLine);
}
int id = Integer.parseInt(st.nextToken());
String name = st.nextToken();
int score = Integer.parseInt(st.nextToken());
totalScore += score;
if (score > highestScore) {
highestScore = score;
topStudent = name;
}
printRecord(out, id, name, score);
}
double averageScore = calculateAverage(totalScore, count);
out.println("----------------------------");
out.printf("Total students: %d%n", count);
out.printf("Class average: %.2f%n", averageScore);
out.printf("Top performer: %s (%d)%n", topStudent, highestScore);
out.flush();
}
}--- Student Score Report ---
ID: 101 | Name: Alice | Score: 88
ID: 102 | Name: Bob | Score: 94
ID: 103 | Name: Charlie | Score: 76
ID: 104 | Name: Diana | Score: 91
----------------------------
Total students: 4
Class average: 87.25
Top performer: Bob (94)Traps
The nextLine leftover newline trap
nextInt()reads numeric characters and stops before the line break. CallingnextLine()immediately afterwards reads the leftover\nand returns an empty string without waiting for user input. Insert an extrasc.nextLine()call to discard the leftover line break.✕ WRONGint id = sc.nextInt(); String line = sc.nextLine();✓ RIGHTint id = sc.nextInt(); sc.nextLine(); String line = sc.nextLine();Forgetting to flush PrintWriter
PrintWriterwraps an in-memory buffer to prevent slow system calls. If you omitout.flush(), characters still held in the buffer are never sent to standard output when the program terminates, causing online judges to receive empty output.✕ WRONGout.print(answer);✓ RIGHTout.print(answer); out.flush();Calling Integer.parseInt on text with surrounding spaces
Integer.parseIntdoes not ignore leading or trailing whitespace. Passing a string with surrounding spaces throwsNumberFormatException. Call.trim()to remove extra spaces before parsing.✕ WRONGint value = Integer.parseInt(" 42 ");✓ RIGHTint value = Integer.parseInt(" 42 ".trim());Splitting strings with a single space delimiter
Calling
split(" ")on text with multiple consecutive spaces creates empty strings""in the resulting array. Use the regular expression\\s+on trimmed text or useStringTokenizerto skip arbitrary whitespace between tokens.✕ WRONGString[] parts = line.split(" ");✓ RIGHTString[] parts = line.trim().split("\\s+");Calling readLine without checking for null at end of file
When
BufferedReaderreaches the end of the input stream,readLine()returnsnull. Calling.isEmpty()or.split()directly onnullthrowsNullPointerException. Always verify that the returned line is notnullbefore processing.✕ WRONGwhile (!br.readLine().isEmpty())✓ RIGHTString line; while ((line = br.readLine()) != null)Comparing parsed input strings with == instead of equals
Tokens read from standard input are distinct
Stringobjects constructed on the heap. Comparing strings with==checks reference identity, which evaluates tofalseeven when the text matches. Always compare string contents using.equals().✕ WRONGif (sc.next() == "yes")✓ RIGHTif (sc.next().equals("yes"))Calling System.out.println repeatedly for large output
System.out.printlnflushes the stream on every invocation, triggering an expensive operating system write operation for every line. For tens of thousands of lines, this causes time limit exceeded errors on online judges. UsePrintWriteror build output withStringBuilderbefore printing.✕ WRONGfor (int i = 0; i < count; i++) System.out.println(nums[i]);✓ RIGHTfor (int i = 0; i < count; i++) out.println(nums[i]); out.flush();