| Lesson 1 | Writing Data Using Java Streams |
| Objective | Understand how to write data using Javaâs OutputStream classes and apply efficient stream-handling practices. |
Writing data in Java is built around a simple yet powerful abstraction - the OutputStream.
This class represents an ordered flow of bytes that can be directed to various destinations such as files, sockets, or even in-memory buffers.
In this lesson, you will learn how to create, use, and close output streams efficiently, starting with the fundamentals and progressing toward practical usage patterns.
The java.io.OutputStream class is the foundation for all output operations in Java.
It provides three essential write() methods for writing bytes, along with utility methods for flush() and close():
public abstract void write(int b) throws IOException
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
public void flush() throws IOException
public void close() throws IOException
Since OutputStream is an abstract class, it cannot be used directly. Subclasses such as FileOutputStream or BufferedOutputStream provide concrete implementations that know how to write data to specific destinations.
For example, the getOutputStream() method of URLConnection always returns an OutputStream object, but the actual subclass depends on the underlying protocol. You might get a TelnetOutputStream, SmtpPrintStream, or KeepAliveStream, depending on whether the URL is telnet, SMTP, or HTTP-based.
Regardless of which subclass you receive, the standard write(), flush(), and close() methods work consistently - a key advantage of Javaâs stream abstraction.
One of the most common uses of output streams is writing data to files. In classic Java (pre-Java 2), the typical pattern used FileOutputStream and DataOutputStream.
The following example demonstrates how to write text to a file using these classes.
import java.io.*;
public class WriteToFileExample {
public static void main(String[] args) {
try {
// Create a file output stream
FileOutputStream fos = new FileOutputStream("output.txt");
// Wrap with DataOutputStream for convenience methods
DataOutputStream dos = new DataOutputStream(fos);
// Write data as bytes
dos.writeBytes("Hello, world!");
// Always close streams
dos.close();
fos.close();
System.out.println("Data has been written to the file.");
} catch (IOException e) {
System.err.println("Error writing to file: " + e.getMessage());
}
}
}
This example writes the phrase "Hello, world!" to output.txt.
The FileOutputStream handles the actual writing to disk, while DataOutputStream provides convenient methods for writing primitives like int, double, and boolean.
Itâs critical to close your streams once writing is complete to ensure that any buffered data is flushed and system resources are released properly.
In practice, output data is often buffered - that is, temporarily stored in memory before being written to the destination.
Flushing ensures that any buffered data is physically written out.
Some streams, such as BufferedOutputStream, automatically flush their contents when they are full, while others may require an explicit call to flush().
Closing a stream also performs an implicit flush, so most programs simply call close() at the end of the write operation.
try (BufferedOutputStream bos =
new BufferedOutputStream(new FileOutputStream("log.txt"))) {
bos.write("Buffered stream example".getBytes());
bos.flush(); // optional, but explicit flush is good practice
}
Using buffering improves performance by reducing the number of disk or network I/O operations. However, excessive or unnecessary flushing can degrade performance, especially when writing large data sets.
The power of Javaâs stream model lies in its uniformity. Whether writing to a file, socket, or HTTP connection, the same OutputStream methods apply.
For instance, when sending data through a URLConnection:
OutputStream out = urlConnection.getOutputStream();
out.write(requestData);
out.close();
The concrete type of the stream depends on the protocol handler - HTTP, SMTP, or Telnet - but the interface remains consistent. This uniformity allows developers to write code that is both modular and protocol-independent.
OutputStream is the abstract superclass for all byte output in Java.By understanding these principles, you can handle output in Java confidently - from writing text files to streaming binary data across a network - all with a single, consistent API.