FileInputStream reads bytes from a file, while FileOutputStream writes bytes to a file. They are useful for binary formats or when you need the exact stored byte sequence. Neither class decodes text or understands an image, archive or document format.
To read ordinary text, use a character API with an explicit charset. To copy binary content, keep it as bytes. This lesson writes a small binary fixture, appends another byte, and copies a file through a reusable buffer. Each program uses temporary files so running it does not overwrite your own documents.
Opening a file is an operation with consequences
Constructing FileInputStream opens an existing file for reading. If the path is missing or inaccessible, opening fails; it does not quietly create an empty source.
FileOutputStream normally creates a missing file or truncates an existing file when opened without append mode. Truncation happens during opening, before the first successful write. An accidentally chosen destination can therefore lose its old contents even if a later operation fails.
Parent directories must already exist. A stream constructor does not build an entire missing directory hierarchy for you. Use Files.createDirectories when that behavior is part of the application's contract, and verify that the path is a directory rather than a conflicting ordinary file.
Relative paths are resolved from the process's working directory. A path that works in an editor can fail when launched from another folder. For debugging, inspect the intended resolved path and working directory instead of assuming the source file's directory controls file lookup.
Overwrite and append are different contracts
The two-argument FileOutputStream constructor accepts a boolean append flag. Passing true writes at the file's end instead of discarding earlier content. Passing false selects the ordinary truncating behavior.
Append does not add a newline, separator or record boundary for you. Appending two text messages without a delimiter joins their bytes directly. Appending a complete file header again may corrupt a format that expects its header only once.
Also distinguish append from editing a value in the middle of a file. Sequential file streams do not provide arbitrary-position updates. SeekableByteChannel or RandomAccessFile support different access patterns when the file format permits those edits.
Concurrent writers need a coordination policy. Append mode by itself is not a portable transaction guarantee for a multi-part logical record. A single owning writer is often simpler than allowing several threads or processes to produce overlapping file operations.
Copy only the portion actually read
A fixed-size byte buffer avoids allocating storage for the entire source. Read fills some or all of the buffer and reports how many bytes were obtained. The last chunk is often smaller than the buffer.
Write that count with output.write(buffer, 0, count). Writing buffer alone writes its full capacity, including stale data beyond the newly read range. This error can leave the copied file longer than the original.
Reading a byte at a time works but can create unnecessary overhead for larger files. A reusable buffer batches the work. BufferedInputStream and BufferedOutputStream add another buffering layer when useful; measure an actual workload before choosing a buffer size based on folklore.
InputStream.transferTo and Files.copy provide simpler copying APIs for many applications. The manual loop here is valuable because it teaches the count contract used by numerous stream-processing algorithms.
Failure handling and resource lifetime
Try-with-resources closes both streams even if reading or writing throws. Resources close in reverse declaration order, so the output closes before the input in the copy example. Closing also gives buffered layers a chance to forward pending data.
An IOException should identify a failed operation to its caller. Do not print copy complete after swallowing it. A partially written destination can remain after failure; decide whether to remove it, keep it for diagnosis, or write to a temporary destination and replace the final file only after success.
Flush and close are not universal durability guarantees against a system failure. Applications with stronger persistence requirements need an explicit storage design rather than assuming every successful write is a durable transaction.
File Streams Summary
- Opening an output can truncate existing data immediately.
- Append preserves bytes but supplies no record format.
- Use the returned read count when copying a buffer.
- Close resources and report partial-write failures honestly.
- Study buffered text next.
File Streams Examples
Each example shows the code, the output it produces and what each part does.
Writing and reading exact binary bytes
Save this complete program as FileByteFixtureExample.java.
The cast for 250 stores its eight-bit pattern in Java's signed byte type. Reading it still returns 250 because the stream API preserves all byte patterns and reserves a separate int value for the end marker.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileByteFixtureExample {
public static void main(String[] args) throws Exception {
Path file = Files.createTempFile("binary-fixture-", ".bin");
try {
try (FileOutputStream output = new FileOutputStream(file.toFile())) {
output.write(new byte[]{10, 20, (byte) 250});
}
try (FileInputStream input = new FileInputStream(file.toFile())) {
int value;
while ((value = input.read()) != -1) {
System.out.println(value);
}
}
} finally {
Files.deleteIfExists(file);
}
}
}10
20
250Line-by-line explanation
- Line 8A new temporary file supplies an isolated destination.
- Lines 10 to 12The output stream writes three bytes and closes before reading begins.
- Lines 13 to 16Each read returns an unsigned byte value as int, or minus one for the end.
- Lines 19 to 20The fixture is removed after both streams have closed.
Appending one additional byte
This program demonstrates the difference through file length and contents.
Replacing true with false in the second constructor would truncate the file before writing three. The final contents would then contain only that one byte. Make this decision explicit whenever a file may already contain valuable data.
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
public class FileAppendExample {
public static void main(String[] args) throws Exception {
Path file = Files.createTempFile("append-fixture-", ".bin");
try {
try (FileOutputStream output = new FileOutputStream(file.toFile())) {
output.write(new byte[]{1, 2});
}
try (FileOutputStream output = new FileOutputStream(file.toFile(), true)) {
output.write(3);
}
System.out.println(Files.size(file));
System.out.println(Arrays.toString(Files.readAllBytes(file)));
} finally {
Files.deleteIfExists(file);
}
}
}3
[1, 2, 3]Line-by-line explanation
- Lines 10 to 12The first opening establishes two bytes.
- Lines 13 to 15Append mode preserves them and adds a third byte.
- Lines 16 to 17The file contains three bytes in their written order.
- Lines 18 to 19Cleanup occurs after the writing resources have closed.
Copying a file through a small buffer
A two-byte buffer makes the short final read visible in a three-byte source.
Never copy a source onto itself with this loop. Opening the destination would truncate the same file you intended to read. A general copy helper should reject equivalent source and destination paths before opening a truncating output.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
public class FileCopyBufferExample {
public static void main(String[] args) throws Exception {
Path source = Files.createTempFile("copy-source-", ".bin");
Path destination = Files.createTempFile("copy-destination-", ".bin");
try {
Files.write(source, new byte[]{4, 8, 12});
try (FileInputStream input = new FileInputStream(source.toFile());
FileOutputStream output = new FileOutputStream(destination.toFile())) {
byte[] buffer = new byte[2];
int count;
while ((count = input.read(buffer)) != -1) {
output.write(buffer, 0, count);
}
}
System.out.println(Arrays.equals(
Files.readAllBytes(source), Files.readAllBytes(destination)));
System.out.println(Files.size(destination));
} finally {
Files.deleteIfExists(source);
Files.deleteIfExists(destination);
}
}
}true
3Line-by-line explanation
- Lines 9 to 12Two distinct fixture paths are created and the source receives its content.
- Lines 13 to 18Each read count controls exactly how many buffer bytes are written.
- Lines 21 to 23Byte comparison checks the copied contents; size checks that nothing extra was appended.
- Lines 24 to 26Both fixtures are removed after the streams close.
File Streams FAQ
Should I use these streams for UTF-8 documents?
Use Reader and Writer adapters with UTF-8 when processing text. Byte streams remain appropriate for an exact byte-for-byte copy.
Is reading an entire file simpler?
Often, but it allocates memory proportional to file size. A buffered loop is better when large or externally supplied files should not be loaded all at once.
Should I use FileInputStream or Files.newInputStream?
For new code, Files.newInputStream(path) and Files.newOutputStream(path) are generally preferred. They work with Path, accept open options such as APPEND, and report problems with more precise exceptions. FileInputStream remains common in older code and works fine.
Do I need BufferedInputStream if I read into a byte array?
Not usually. Reading in chunks of several kilobytes already makes few system calls, which is what buffering would provide. BufferedInputStream helps most when code reads one byte at a time, such as a parser calling read() in a loop.
How do I copy a file in one line?
Files.copy(source, target) copies a file by path, with StandardCopyOption.REPLACE_EXISTING to overwrite. With streams that are already open, in.transferTo(out), available since Java 9, copies all remaining bytes for you.
File Streams Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
What does opening an existing file with ordinary FileOutputStream do?
The copy loop reads one byte into a two-byte buffer. Which write is correct?
File Streams Quiz
2 questions to check what you learned.
Does append mode add a newline automatically?
- No
- Yes, for every file extension
- Only when the appended byte is positive
Why reject a source path equal to the destination?
- Opening the destination can erase the source before copying
- Input streams sort identical filenames
- Java prohibits reading any temporary file