Input-output streams (I/O streams) are one of the fundamental concepts in Java, introduced in the early versions of the language. This concept was initially designed to abstract the processes of reading and writing data: a stream can be linked to a file, a network, or even the console — to the code it all looks the same.
The problem arises when a developer mismanages streams, forgets to close them, or confuses different types of streams (character and byte), often leading to resource leaks, data corruption, or runtime errors.
The solution is a proper understanding and application of the hierarchy of input-output streams (InputStream/OutputStream for bytes, Reader/Writer for characters), as well as the mandatory closure of streams after use, preferably through try-with-resources starting from Java 7.
Example code for reading and writing a file:
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt")); BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) { String line; while ((line = reader.readLine()) != null) { writer.write(line); writer.newLine(); } }
Key features:
What happens if you don't close an I/O stream in Java?
Streams deal with low-level OS resources, and not closing a stream can lead to memory leaks, file locks, application crashes, or even depletion of file descriptors at the OS level.
Can you use the same OutputStream to write to different files?
No, a classic OutputStream is tightly associated with one source/receiver of data. For different files — different OutputStream objects.
What is the difference between PrintWriter and BufferedWriter? When to use which?
PrintWriter specializes in enhancing output with formatted printing methods, like println(), and can work with auto-flushing. BufferedWriter, on the other hand, mainly increases performance through buffering. In most application scenarios, PrintWriter is preferable for text output, but if you only need to quickly write characters or strings without formatting—BufferedWriter is the better choice.
A developer reads data from a large file using FileInputStream, does not use buffering, and forgets to close the stream.
Pros:
Cons:
A combination of BufferedReader with try-with-resources is used, reading and processing occur in batches by lines, and the stream automatically closes.
Pros:
Cons: