algoz
Chapters/01 Java basics/Input and output

Input and output

Read input and print results quickly using Scanner, BufferedReader, and PrintWriter.

Builds on
You'll gain
Read input and print results fastwith Scanner and fast I/O
Practice
Sheet coming sooncoding problems for this note
Summary

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

  1. Interactive programs and algorithm judges must accept problem data from standard input and write answers to standard output.
  2. Simple parsing tools like Scanner work well for small scripts but slow down noticeably on thousands of numbers because of regular expression parsing.
  3. Unbuffered writing triggers frequent, expensive operating system calls for every tiny string or number printed.
  4. 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 C printf function.

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 %n instead of \n inside 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.

Main.java
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);
    }
}
Output
Score: 95
Student: Alice | Score: 95 | Mark: 95.46%
Summary: Alice scored 95

In this program:

  • student stores the name string "Alice".
  • score stores the integer value 95.
  • percentage stores the floating-point value 95.456.
  • System.out.printf prints 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.format builds the text Summary: Alice scored 95 and assigns it to message.

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 an int.
  • sc.nextLong(): Scans the next token as a long.
  • sc.next(): Scans and returns the next token as a String (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(): Returns true if another integer token is available in the input stream.
Input
3
Alice 88
Bob 92
Charlie 79
Main.java
import 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();
    }
}
Output
Read: Alice -> 88
Read: Bob -> 92
Read: Charlie -> 79
Average: 86

In this program:

  • sc is a Scanner reading characters from System.in.
  • count reads the first integer 3, indicating how many student records follow.
  • In each loop pass, sc.next() reads the single-word string name, and sc.nextInt() reads the integer score.
  • totalScore accumulates the scores, and totalScore / count computes 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:

Terminal
java Main < input.txt

You can also pipe the output of another command directly into your program's standard input using the | operator:

Terminal
cat input.txt | java Main

The 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.

Input
42
Algorithms and Data Structures
Main.java
import 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();
    }
}
Output
ID: 42
Title: Algorithms and Data Structures

In this program:

  • id receives 42 from sc.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.

Input
12 18 24 6 10
Main.java
import 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();
    }
}
Output
Count: 5
Total: 70

In this program:

  • while (sc.hasNextInt()) keeps running as long as integer values are available in standard input.
  • total accumulates the sum of all numbers read.
  • count counts 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 press Enter.

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:

Java
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:

Input
5
10 20 30 40 50
Main.java
import 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);
    }
}
Output
Count: 5
Sum: 150

In this program:

  • br.readLine() reads the entire first line as a string, .trim() removes any trailing newline characters, and Integer.parseInt converts the text "5" into the integer 5.
  • 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, and Integer.parseInt converts 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:

Java
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.

Main.java
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
    }
}
Output
Item 1: square = 1
Item 2: square = 4
Item 3: square = 9
Item 4: square = 16
Item 5: square = 25

In this program:

  • out writes characters into the internal buffer of BufferedOutputStream.
  • The loop calls out.print and out.println repeatedly 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:

Input
4
15 25
35 45
Main.java
import 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();
    }
}
Output
Added 15 -> running total 15
Added 25 -> running total 40
Added 35 -> running total 75
Added 45 -> running total 120
Final total: 120

In this program:

  • st stores the current StringTokenizer instance.
  • 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 with br.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 via System.in.
  • File Descriptor 1 (stdout): Standard output, accessed in Java via System.out.
  • File Descriptor 2 (stderr): Standard error, accessed in Java via System.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:

Terminal
java Main < input.txt > output.txt 2> error.log

In this command:

  • < input.txt: Feeds the contents of input.txt to stdin (System.in).
  • > output.txt: Directs normal output from stdout (System.out) into output.txt, overwriting the file. Use >> to append instead of overwriting.
  • 2> error.log: Directs diagnostic messages from stderr (System.err) into error.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).
  • %n inside printf or String.format: Dynamically emits the value of System.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

Input
4
101 Alice 88
102 Bob 94
103 Charlie 76
104 Diana 91
Main.java
import 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();
    }
}
Output
--- 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

  1. The nextLine leftover newline trap

    nextInt() reads numeric characters and stops before the line break. Calling nextLine() immediately afterwards reads the leftover \n and returns an empty string without waiting for user input. Insert an extra sc.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();
  2. Forgetting to flush PrintWriter

    PrintWriter wraps an in-memory buffer to prevent slow system calls. If you omit out.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();
  3. Calling Integer.parseInt on text with surrounding spaces

    Integer.parseInt does not ignore leading or trailing whitespace. Passing a string with surrounding spaces throws NumberFormatException. Call .trim() to remove extra spaces before parsing.

    ✕ WRONGint value = Integer.parseInt(" 42 ");
    ✓ RIGHTint value = Integer.parseInt(" 42 ".trim());
  4. 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 use StringTokenizer to skip arbitrary whitespace between tokens.

    ✕ WRONGString[] parts = line.split(" ");
    ✓ RIGHTString[] parts = line.trim().split("\\s+");
  5. Calling readLine without checking for null at end of file

    When BufferedReader reaches the end of the input stream, readLine() returns null. Calling .isEmpty() or .split() directly on null throws NullPointerException. Always verify that the returned line is not null before processing.

    ✕ WRONGwhile (!br.readLine().isEmpty())
    ✓ RIGHTString line; while ((line = br.readLine()) != null)
  6. Comparing parsed input strings with == instead of equals

    Tokens read from standard input are distinct String objects constructed on the heap. Comparing strings with == checks reference identity, which evaluates to false even when the text matches. Always compare string contents using .equals().

    ✕ WRONGif (sc.next() == "yes")
    ✓ RIGHTif (sc.next().equals("yes"))
  7. Calling System.out.println repeatedly for large output

    System.out.println flushes 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. Use PrintWriter or build output with StringBuilder before 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();
AlgozSet in Geist & Geist MonoJava track