1. Introduction
Let’s start with the basics: how does a binary file differ from a text file? A text file is a file you can open in a regular text editor and see letters, digits, spaces, and other characters. For example, my_notes.txt or poem.txt.
A binary file is a file that contains not text but arbitrary bytes. It could be an image (.jpg, .png), music (.mp3), an archive (.zip), an executable (.exe), video (.mp4), a database file, and so on. If you open such a file in a text editor, you’ll see something like ÿØÿà or a wall of gibberish. That’s normal! A computer “understands” only bytes — to it, text, images, and video are just sequences of bytes. In text files those bytes can be interpreted as characters, while in binary files they are “raw” data not intended for human reading.
Core classes for working with binary files
In Java, byte streams are used for working with binary files:
- InputStream — the base class for reading bytes.
- OutputStream — the base class for writing bytes.
For files, there are concrete implementations:
- FileInputStream — reads bytes from a file.
- FileOutputStream — writes bytes to a file.
If you’ve heard of FileReader and FileWriter, be aware: they work with characters and are suitable only for text. For binary files use only InputStream/OutputStream and their subclasses.
2. Reading binary files
Reading byte by byte
The simplest approach is to read a file one byte at a time. It’s straightforward but very slow.
try (FileInputStream in = new FileInputStream("image.jpg")) {
int b;
while ((b = in.read()) != -1) {
// b is a number from 0 to 255 (a byte), -1 means end of file
// You can process the byte, for example, compute the sum of all bytes
}
}
The read() method returns the next byte as an int (from 0 to 255), and when the file ends it returns -1. Usually you read one byte at a time only for very specific tasks (for example, analyzing a file format).
Reading in blocks (buffered)
Reading one byte at a time is like going to the store for each apple separately. It’s much more efficient to take a whole bag at once! In Java, there’s the read(byte[] buffer) method, which fills an array with bytes from the file.
try (FileInputStream in = new FileInputStream("image.jpg")) {
byte[] buffer = new byte[4096]; // 4 KB buffer
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
// buffer contains bytesRead bytes from the file
// You can process these bytes, e.g., save them somewhere else
}
}
The read(buffer) method returns how many bytes were actually read (it can be less than the buffer size, especially on the last read). This approach is much faster because there are fewer disk accesses.
Example: copying a file
Let’s write a simple program that copies any binary file (for example, an image) from one location to another.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BinaryCopyExample {
public static void main(String[] args) {
String source = "cat.jpg";
String dest = "cat_copy.jpg";
try (FileInputStream in = new FileInputStream(source);
FileOutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192]; // 8 KB — an optimal size for most tasks
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
System.out.println("Copy completed!");
} catch (IOException e) {
System.out.println("Error during copy: " + e.getMessage());
}
}
}
It’s simple: read blocks from the source file and immediately write them to the new file. This approach works with any files: images, archives, videos.
3. Writing binary files
Writing a byte array
If you have a byte array (for example, you received it from the network or generated it in your program), you can write it to a file like this:
byte[] data = new byte[] {1, 2, 3, 4, 5}; // sample array
try (FileOutputStream out = new FileOutputStream("data.bin")) {
out.write(data); // writes the entire array to the file
}
The write(byte[]) method writes all bytes from the array. You can also write only part of the array: out.write(data, offset, length).
Writing a file in chunks (e.g., when copying)
As with reading, you typically use a buffer:
try (FileInputStream in = new FileInputStream("source.bin");
FileOutputStream out = new FileOutputStream("dest.bin")) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
Here, everything we read from the file is immediately written to another file. Such code is common in archivers, downloaders, image processors, etc.
4. Useful details
Why you must not use Reader/Writer for binary files
Reader and Writer work with characters (char), not bytes. They automatically convert bytes to characters according to a charset (for example, UTF-8). That’s convenient for text, but for binary files it’s deadly dangerous!
If you try to write an image via FileWriter, you’ll get a corrupted file that cannot be opened. Remember: for any non-text files use only InputStream/OutputStream!
Important differences and nuances when working with binary files
- Buffer size: A buffer that’s too small will slow things down (too many disk accesses), one that’s too large will waste memory. 4–16 KB is usually optimal.
- Error handling: Always handle IOException — the file may not exist, be locked, or the disk may run out of space.
- Closing streams: Use try-with-resources — it guarantees files are closed even in case of errors.
- Overwriting a file: If you open a file via new FileOutputStream("file.bin"), it will be overwritten. To append to the end, use the constructor with append = true.
- Permissions: If the program cannot open a file, check read/write permissions.
- readAllBytes(): Lets you read the entire file into a byte array in one call. For large files — don’t use it, so you don’t run out of memory!
5. Common mistakes when working with binary files
Error #1: Using FileReader/FileWriter for binary files. This will corrupt the data because these classes convert bytes to characters and back, which is disastrous for images, archives, etc.
Error #2: Ignoring the return value of read(). The read(byte[]) method may read fewer bytes than you request, especially in the last block. Always use the returned value to know how many bytes were actually processed.
Error #3: Forgetting to close the stream. If you don’t close a file, it may remain locked, and data may not be fully written (especially when writing!). Use try-with-resources.
Error #4: Trying to read an entire file into memory without considering its size. For large files this will lead to OutOfMemoryError. Use a buffer and read in parts.
Error #5: Not handling exceptions. Working with files can always fail: file not found, insufficient permissions, disk full. Don’t forget to handle IOException.
GO TO FULL VERSION