Byte Streams

Basic Java
≈ 2 min read

When data is just bytes

An image, an archive, a PDF, or a network packet cannot be correctly described as a sequence of characters. At the usual low level, Java works with such data through byte streams: InputStream reads bytes, and OutputStream writes them.

A concrete source or destination is defined by a subclass. For example, FileInputStream reads bytes from a file, while FileOutputStream writes them to a file. Code higher in the chain can still work with the general stream type and remain independent of the source.

try (InputStream in = new FileInputStream("input.bin")) {
    int first = in.read();
    System.out.println(first);
}

The read() method returns an int, not a byte: values 0..255 represent the byte that was read, and -1 means end of stream. This lets one return value distinguish every possible byte value from the special EOF marker.

Reading in blocks

Reading a large file one byte at a time is inconvenient and usually inefficient. That is why data is often read directly into a byte array.

byte[] buffer = new byte[4096];

try (InputStream in = new FileInputStream("input.bin")) {
    int read;
    while ((read = in.read(buffer)) != -1) {
        System.out.println("Read: " + read);
    }
}

The read variable tells you how many array elements were actually filled on that iteration. The last block can easily be shorter than the buffer size, so the entire array must not automatically be treated as new data.

Writing bytes

OutputStream is symmetrical: it accepts individual bytes or byte arrays. If data must be appended to the end of an existing file, that behavior is selected when the concrete file stream is created.

byte[] data = {10, 20, 30};

try (OutputStream out = new FileOutputStream("output.bin")) {
    out.write(data);
}
A byte stream does not know a text encoding. If the bytes actually represent text, they must be converted to characters using a specific encoding. The next API family — character streams — is designed for that job.