Java Scanner Unclosed Stream — Too Many Open Files Error
Unclosed Scanner exhausts file descriptors after ~1020 files, causing 'Too many open files' error.
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
- Java I/O is how programs read input and write output using streams.
- The main output tools are System.out.print, println, and printf.
- Input is handled by Scanner (for user input) or BufferedReader (for speed).
- Performance insight: BufferedReader is ~3x faster than Scanner for bulk reads because it buffers large chunks.
- Production insight: Unclosed Scanner objects leak file handles — always close or use try-with-resources.
- Biggest mistake: Mixing nextInt() with nextLine() without flushing the leftover newline.
Java I/O (Input/Output) is the system for moving data between your program and the outside world — files, network sockets, the console, or even in-memory buffers. At its core, I/O in Java is built on streams: ordered sequences of bytes or characters that flow from a source (like a file or keyboard) to a sink (like a console or another file).
The java.io package provides classes like InputStream, OutputStream, Reader, and Writer to handle this, while java.nio offers non-blocking alternatives for high-throughput scenarios. Without I/O, your program is an island — it can't read configuration, log errors, or interact with users.
The Scanner class, introduced in Java 5, is a high-level utility for parsing primitive types and strings from input streams. It's commonly used for reading user input from System.in (the console) or parsing data from files. Under the hood, Scanner wraps a Readable (like FileReader or InputStreamReader) and uses a delimiter pattern (default: whitespace) to tokenize input.
Its convenience methods like nextInt(), nextLine(), and hasNext() make it beginner-friendly, but it carries hidden costs: it's slower than BufferedReader for bulk reads, and its internal buffer can cause the infamous nextInt()/nextLine() bug when mixing token-based and line-based reads.
The "Too Many Open Files" error you're asking about is a classic resource leak pitfall. Every time you create a Scanner on a file (new Scanner(new File("data.txt"))), it opens a file descriptor. If you don't call — or fail to use try-with-resources — those descriptors accumulate.close()
On Linux, the default per-process limit is often 1024 (check with ulimit -n); on macOS, it's 256. Exceed that, and FileNotFoundException with "Too many open files" kills your process. The fix: always close Scanner (and any InputStream/Reader) in a finally block or use Java 7+'s try-with-resources.
For console input (System.in), never close the Scanner — that closes System.in permanently, breaking all subsequent reads.
Think of your Java program as a chef in a kitchen. Input is the customer's order — information coming IN to the chef so they know what to cook. Output is the finished meal placed on the table — information going OUT from the chef back to the customer. Just like a restaurant needs a system for taking orders and serving food, Java needs a system for reading data and printing results. That system is what Input and Output (I/O) is all about.
Every useful program in the world does at least one of two things: it takes information in, or it sends information out. Your phone's calculator takes in numbers you tap and outputs an answer. A login screen takes in your password and outputs either a welcome screen or an error. Without input and output, a program just runs silently in a box and does nothing anyone can see. That's why I/O is the very first real-world skill you need as a Java developer — it's how your program talks to the outside world.
What Java I/O Actually Does — Streams, Sources, and Sinks
Java I/O (Input/Output) is the mechanism for reading data from a source and writing data to a destination. The core mechanic is the stream — a sequential, ordered flow of bytes or characters. Input streams pull data from a source (file, socket, keyboard), output streams push data to a sink (file, socket, console). The java.io package provides abstract classes (InputStream, OutputStream, Reader, Writer) that define the contract, with concrete implementations for specific sources and sinks.
Every stream must be explicitly closed to release the underlying system resource (file handle, socket descriptor). The JVM does not automatically close streams on garbage collection — relying on finalization is unreliable and deprecated. The try-with-resources statement (Java 7+) ensures deterministic cleanup by calling close() automatically, even if an exception occurs. Without it, each unclosed stream leaks a file descriptor, and the OS limit (typically 1024 per process on Linux) is quickly exhausted.
Use Java I/O whenever your application reads or writes data to persistent storage, network endpoints, or inter-process communication channels. In production systems, every file read, every socket connection, every database result set traversal depends on correct I/O stream management. The difference between a robust service and one that crashes with 'Too many open files' is whether every stream is closed in a finally block or try-with-resources.
Printing to the Console — Java's Built-In Megaphone
Before your program can accept input, it needs to be able to speak. Printing to the console is Java's way of shouting a message to whoever is running the program. Think of it like a scoreboard at a sports game — it's how the program announces what's happening.
Java gives you three closely related tools for this, and beginners often use them interchangeably without understanding the differences. System.out.println() prints your message and then moves the cursor to a new line — like pressing Enter after typing. System.out.print() prints your message but stays on the same line, so the next thing printed appears right next to it. System.out.printf() is the most powerful — it lets you format your output precisely, like lining up numbers in a table.
System is a built-in Java class that represents your computer's environment. out is the output stream attached to your console. And println, print, and printf are the methods you call on it. You don't need to import anything — Java includes System automatically in every program.
public class PrintingExamples { public static void main(String[] args) { // println adds a newline after printing — cursor moves to the next line System.out.println("Welcome to TheCodeForge!"); // print does NOT add a newline — the next output continues on the same line System.out.print("First name: "); System.out.print("Ada "); System.out.print("Lovelace"); // We need a manual newline here to move to the next line System.out.println(); // prints an empty line // printf lets you format values inline using format specifiers // %s = String placeholder, %d = integer placeholder, %n = newline String productName = "Java Book"; int quantityInStock = 42; double priceInDollars = 29.99; System.out.printf("Product : %s%n", productName); System.out.printf("In Stock : %d units%n", quantityInStock); // %.2f means: print a floating-point number with exactly 2 decimal places System.out.printf("Price : $%.2f%n", priceInDollars); } }
Reading User Input with Scanner — Java's Ears
Printing is only half the story. A program that can't accept input is like a vending machine with no buttons — it just sits there. Java's Scanner class is your go-to tool for reading what a user types into the console.
Scanner lives in the java.util package, so you must import it at the top of your file before you can use it. You create one Scanner object and keep it for the whole program — creating a new Scanner every time you want to read input is wasteful and causes bugs.
Once you have a Scanner, you call different methods depending on what type of data you're expecting. nextLine() reads a whole line of text including spaces. reads only one word (stops at a space). next()nextInt() reads an integer. nextDouble() reads a decimal number. The Scanner pauses the program and waits for the user to type something and press Enter — that pause is called blocking, and it's intentional.
Always close your Scanner when you're done using it. It's connected to a resource (the keyboard input stream), and leaving it open is a memory leak waiting to happen.
import java.util.Scanner; // We MUST import Scanner before using it public class UserProfileInput { public static void main(String[] args) { // System.in is the keyboard input stream — we wrap it in Scanner to read it easily Scanner keyboardInput = new Scanner(System.in); // Prompt the user — always print a message BEFORE reading input System.out.print("Enter your full name: "); // nextLine() reads everything the user types until they press Enter String fullName = keyboardInput.nextLine(); System.out.print("Enter your age: "); // nextInt() reads a whole number — it will crash if the user types letters! int userAge = keyboardInput.nextInt(); System.out.print("Enter your account balance: "); // nextDouble() reads a decimal number like 1250.75 double accountBalance = keyboardInput.nextDouble(); // Print a formatted summary back to the user System.out.println("\n--- Profile Summary ---"); System.out.printf("Name : %s%n", fullName); System.out.printf("Age : %d years old%n", userAge); System.out.printf("Balance : $%.2f%n", accountBalance); // Close the Scanner to release the keyboard resource keyboardInput.close(); } }
Handling the nextInt/nextLine Bug and Input Validation
This bug trips up almost every beginner, so it deserves its own section. When you mix nextInt() or nextDouble() with nextLine(), the Scanner's internal buffer causes a silent but nasty problem. Let's look at exactly what happens and how to fix it cleanly.
Imagine the user types 25 and presses Enter. What's actually in the buffer is 25 . When you call nextInt(), it reads 25 but leaves the sitting in the buffer. The very next nextLine() sees that and says 'I found a newline — my job is done!' and returns an empty string before the user has a chance to type anything.
The reliable fix is to call keyboardInput.nextLine() immediately after any nextInt() or nextDouble() call — think of it as 'draining the leftover newline from the pipe.' Alternatively, you can read everything as strings using nextLine() and then parse the number yourself with Integer.parseInt() or Double.parseDouble(). The second approach also lets you show a friendly error message when the user types something unexpected instead of crashing.
import java.util.Scanner; public class SafeInputReading { public static void main(String[] args) { Scanner keyboardInput = new Scanner(System.in); // APPROACH 1 — Flush the buffer manually after nextInt() System.out.print("Enter your birth year: "); int birthYear = keyboardInput.nextInt(); // This nextLine() drains the leftover '\n' from pressing Enter // Without this line, the next nextLine() would return "" (empty string) keyboardInput.nextLine(); // <--- the buffer flush System.out.print("Enter your home city: "); String homeCity = keyboardInput.nextLine(); // now works correctly System.out.printf("Born in %d, living in %s.%n", birthYear, homeCity); System.out.println(); // APPROACH 2 — Read everything as String, then parse (safer & more flexible) System.out.print("Enter your salary: "); String salaryInput = keyboardInput.nextLine(); // read raw text double salary; try { // Parse the string into a number — throws NumberFormatException if invalid salary = Double.parseDouble(salaryInput); System.out.printf("Annual salary: $%.2f%n", salary); } catch (NumberFormatException conversionError) { // Friendly message instead of a crash System.out.println("That doesn't look like a valid number. Please enter digits only."); } keyboardInput.close(); } }
BufferedReader — The Faster, More Powerful Alternative
Scanner is perfect for learning and small programs, but professional Java developers often use BufferedReader for console input instead. The key difference is speed and control — BufferedReader reads large chunks of data at once (buffering them in memory), which is significantly faster when processing big files or high-throughput input.
BufferedReader requires a bit more setup. You wrap System.in in an InputStreamReader (which converts raw bytes to characters) and then wrap that in a BufferedReader (which adds efficient line-by-line reading). It sounds complex, but it's always the same three lines of setup code and you'll memorise them quickly.
The main trade-off: BufferedReader only reads strings — you always get back a String from readLine(), and you have to parse it yourself to get numbers. It also throws a checked IOException which means Java forces you to either handle it with try-catch or declare it with throws IOException. For beginners, Scanner is the right starting point. As you grow into professional Java, reach for BufferedReader.
In the code below, notice how both approaches produce the same result — the choice is about performance and context, not correctness.
import java.io.BufferedReader; // for fast, buffered reading import java.io.InputStreamReader; // converts raw bytes (System.in) into characters import java.io.IOException; // must handle this checked exception public class BufferedReaderExample { // 'throws IOException' tells Java: this method might throw an IO error // Java requires us to either handle it or declare it — we're declaring it here public static void main(String[] args) throws IOException { // Step 1: Wrap System.in in InputStreamReader to handle character encoding // Step 2: Wrap that in BufferedReader for efficient line reading BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Enter the item name: "); String itemName = consoleReader.readLine(); // readLine() always returns a String System.out.print("Enter the quantity: "); // readLine() gives us "10" as a String — we parse it to int manually int quantity = Integer.parseInt(consoleReader.readLine()); System.out.print("Enter the unit price: "); // Similarly, parse the string to a double double unitPrice = Double.parseDouble(consoleReader.readLine()); double totalCost = quantity * unitPrice; System.out.println("\n--- Order Receipt ---"); System.out.printf("Item : %s%n", itemName); System.out.printf("Quantity : %d%n", quantity); System.out.printf("Unit Price: $%.2f%n", unitPrice); System.out.printf("Total : $%.2f%n", totalCost); consoleReader.close(); // always close your reader } }
close() manually (and risking forgetting it), wrap your BufferedReader in a try-with-resources block: try (BufferedReader reader = new BufferedReader(...)) { ... }. Java will automatically close it when the block ends, even if an exception is thrown. This is the professional pattern.Reading from Files with Scanner and Writing with PrintWriter
Console input is just the start. In real applications, input often comes from files — configuration files, data logs, CSV exports. Java's Scanner can read from files just as easily as from the keyboard. You just pass a File object instead of System.in. For writing output to files, PrintWriter is the equivalent of System.out.
Scanner automatically handles different data types when reading from files. hasNextLine() is your friend here — it returns true if there's another line in the file, letting you loop safely without hitting NoSuchElementException. For writing, PrintWriter provides println, print, and printf just like System.out. But you must flush or close it to ensure data actually hits the disk.
The common mistake is assuming Scanner will skip empty lines or that PrintWriter automatically flushes. PrintWriter's autoFlush is false by default — if you forget to close, you lose output.
Let's see a complete example: reading customer data from a CSV file and writing a formatted report to another file.
import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.util.Scanner; public class FileIOExample { public static void main(String[] args) { // Input file: customers.csv // Each line: id,name,balance File inputFile = new File("customers.csv"); File outputFile = new File("report.txt"); // Use try-with-resources to auto-close both Scanner and PrintWriter try (Scanner fileScanner = new Scanner(inputFile); PrintWriter writer = new PrintWriter(outputFile)) { // Skip header line if (fileScanner.hasNextLine()) { fileScanner.nextLine(); } writer.println("Customer Report"); writer.println("==============="); while (fileScanner.hasNextLine()) { String line = fileScanner.nextLine(); String[] parts = line.split(","); if (parts.length == 3) { int id = Integer.parseInt(parts[0].trim()); String name = parts[1].trim(); double balance = Double.parseDouble(parts[2].trim()); writer.printf("ID: %d | Name: %s | Balance: $%.2f%n", id, name, balance); } else { System.err.println("Skipping malformed line: " + line); } } System.out.println("Report generated: " + outputFile.getAbsolutePath()); } catch (FileNotFoundException e) { System.err.println("File not found: " + e.getMessage()); } catch (NumberFormatException e) { System.err.println("Number format error in file: " + e.getMessage()); } } }
flush() means data loss.Standard Streams: Where Java Gets Its Input and Output From
Before you write another line of I/O code, understand the three default streams Java gives you. System.in is an InputStream that reads raw bytes from the keyboard or any standard input device. System.out is a PrintStream that writes normal output to the console. System.err is also a PrintStream, but it's for error messages — and it's unbuffered. Why does that matter? In production, System.out and System.err go to different log files. If you mix them, your error logs get polluted with debug output. Always use System.err for failures, not System.out. The WHY is simple: your operations team needs clean separation to triage incidents.
// io.thecodeforge import java.io.IOException; public class StandardStreamsExample { public static void main(String[] args) throws IOException { System.out.println("Normal output: Application started"); System.err.println("Error output: File not found"); // Reading raw byte from System.in System.out.print("Enter a character: "); int data = System.in.read(); System.out.println("You entered: " + (char) data); System.out.println("ASCII value: " + data); } }
Byte vs Character Streams: Pick the Right Tool for the Data
Java I/O splits into two families: byte streams and character streams. Byte streams (InputStream, OutputStream) handle raw binary data — images, audio, zip files. Character streams (Reader, Writer) handle text — human-readable files, network messages. The cardinal sin? Using a Reader on a binary file. It corrupts data by decoding bytes into characters. Always inspect your data type first. For text files, BufferedWriter and BufferedReader are your production workhorses. For binary, FileInputStream and FileOutputStream. Don't get fancy. The WHY: character streams apply encoding, byte streams don't. Mixing them creates silent payload corruption that only surfaces in production.
// io.thecodeforge import java.io.*; public class StreamTypeExample { public static void main(String[] args) { // Correct: character stream for text try (BufferedReader reader = new BufferedReader(new FileReader("config.properties"))) { String line = reader.readLine(); System.out.println("Config: " + line); } catch (IOException e) { System.err.println("Read failed: " + e.getMessage()); } // Correct: byte stream for binary try (FileInputStream fis = new FileInputStream("logo.png")) { byte[] header = new byte[8]; fis.read(header); System.out.println("First 8 bytes of PNG: " + java.util.Arrays.toString(header)); } catch (IOException e) { System.err.println("Binary read failed: " + e.getMessage()); } } }
Unclosed Scanner in Batch Job Exhausts File Handles
- Always close I/O resources — never rely on garbage collection.
- Use try-with-resources for any resource that implements AutoCloseable.
- Monitor file descriptor count on production servers.
System.in.close() unless you intend to. Use hasNextLine/hasNextInt before reading.lsof -p <pid> | wc -l to check open file handles. Find unclosed Scanner/BufferedReader instances and wrap them in try-with-resources.scanner.nextLine() after nextInt to flush the newline buffer. Alternatively, switch to reading all input as strings.BufferedReader.readLine() returns null unexpectedly(line = reader.readLine()) != null. Also verify that the InputStream hasn't been closed externally.scanner.nextLine(); // drain newlineString text = scanner.nextLine(); // now reads correctlyscanner.nextLine(); immediately after every nextInt/nextDouble/nextFloat call before reading a line.lsof -p $(pgrep -f YourMainClass) | wc -lgrep -r "new Scanner" src/ --include="*.java" | wc -ltry (Scanner sc = new Scanner(file)) { ... }if (scanner.hasNextInt()) { int val = scanner.nextInt(); }String raw = scanner.nextLine(); try { int val = Integer.parseInt(raw.trim()); } catch (NumberFormatException e) { ... }| Feature | Scanner | BufferedReader |
|---|---|---|
| Import needed | java.util.Scanner | java.io.BufferedReader + InputStreamReader |
| Reads numbers directly? | Yes — nextInt(), nextDouble() | No — must parse strings manually |
| Handles newline after nextInt? | No — requires manual buffer flush | Not an issue — always reads whole lines |
| Performance | Slower (tokenises input) | Faster (buffers large chunks) |
| Exception handling required? | No checked exceptions | Yes — must handle IOException |
| Best for | Beginner projects, simple console apps | Professional apps, file reading, performance-critical code |
| Setup complexity | Simple — one line | Moderate — three wrapped classes |
Key takeaways
System.out.println() adds a newline after printing; System.out.print() doesn'timport java.util.Scanner and must be wrapped around System.inCommon mistakes to avoid
4 patternsCalling nextLine() immediately after nextInt() without buffer flush
Forgetting to import Scanner
import java.util.Scanner; as the very first line after your package declaration. This import is mandatory; Scanner is not automatically available like System is.Using nextLine() to read a number and skipping parseInt/parseDouble conversion
Not closing Scanner when reading a file
try (Scanner sc = new Scanner(new File("data.txt"))) { ... } — Java closes it automatically.Interview Questions on This Topic
What is the difference between System.out.print(), System.out.println(), and System.out.printf() in Java? When would you choose each one?
print outputs text without a newline. println appends a newline after output. printf uses format specifiers for structured formatting. Use printf for reports or tables where alignment matters. Use print for prompts. Use println for general output.Why does calling nextLine() immediately after nextInt() on the same Scanner object often return an empty string, and what are two ways to fix it?
nextInt() leaves the newline character in the buffer. The subsequent nextLine() consumes that newline immediately and returns an empty string. Fix 1: add an extra nextLine() after nextInt() to drain the buffer. Fix 2: read all input as strings with nextLine() and parse numbers using Integer.parseInt().What are the advantages of using BufferedReader over Scanner for reading console input in a production Java application?
Frequently Asked Questions
The easiest way is to use Java's Scanner class. Import it with import java.util.Scanner;, create an instance with Scanner input = new Scanner(System.in);, then call methods like input.nextLine() for text or input.nextInt() for whole numbers. Always close the Scanner with when you're done.input.close()
Both print to the console, but System.out.println() automatically adds a newline character at the end, moving the cursor to the next line. System.out.print() prints the value and leaves the cursor right where it stopped — the next print will appear on the same line. Use println for most output; use print when you want to build output on one line piece by piece.
Yes, always. Scanner lives in the java.util package which is not automatically imported. You must add import java.util.Scanner; at the top of your file before your class declaration. The only classes Java imports automatically are those in java.lang, such as String, Math, and System.
Use BufferedReader when performance matters — it's faster for large inputs and file reading. Also use it when you need full control over character encoding. Use Scanner for small console apps or when you need to read mixed data types easily.
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
That's Java Basics. Mark it forged?
6 min read · try the examples if you haven't