FileInputStream
In short: A byte stream for reading raw binary data from a file — e.g. for images, PDFs, or other non-text formats.
In more detail: For pure text content, a Reader (e.g. via FileReader/BufferedReader) is usually the better choice, since it automatically takes character encoding into account — FileInputStream only delivers raw bytes with no encoding knowledge.
In Depth
try (FileInputStream fis = new FileInputStream("image.jpg");
FileOutputStream fos = new FileOutputStream("copy.jpg")) {
byte[] buffer = new byte[4096]; // read in blocks, not byte by byte
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead); // only write the bytes actually read
}
} catch (IOException e) {
System.err.println("Copy failed: " + e.getMessage());
}read() either returns the number of bytes read or -1 once the end of the file is reached — analogous to readLine() in BufferedReader, which uses null instead of -1 as the exit signal. Reading bytes in blocks into a buffer (instead of one at a time with argument-less read()) is significantly more performant, because every call potentially triggers a system call — a single byte per call would cause an extreme number of such expensive calls for a large file. For modern codebases, Files.copy(source, target) from java.nio.file offers the same functionality much more compactly, without having to write the buffer logic by hand.
Multiple resources in one try-with-resources
As shown in the copy example above, multiple resources (here: source AND target) can be opened in a single try (...) header, separated by semicolons. Important: they’re closed in REVERSE order of their declaration when leaving the block (here, fos first, then fis) — relevant when one resource depends on another and has to be released in a specific order.
For small files: Files.readAllBytes()
If the entire file fits into memory anyway (e.g. a configuration file in the kilobyte range), manually handling FileInputStream and buffer loops is often unnecessary effort:
byte[] content = Files.readAllBytes(Path.of("config.bin")); // reads everything at onceThe trade-off: for large files (several hundred MB or GB), this method would hold the entire content in working memory at once — for such cases, reading in blocks with FileInputStream (or an NIO channel with memory mapping) remains the more memory-efficient choice.
Buffered vs. unbuffered
A raw FileInputStream reads directly from the operating system, with no buffer of its own — if read() is often called with small amounts of data, a BufferedInputStream wrapper can significantly reduce the number of expensive system calls:
try (InputStream in = new BufferedInputStream(new FileInputStream("large-file.bin"))) {
int b;
while ((b = in.read()) != -1) { // now reads from the internal buffer, not from the OS every time
// process single byte
}
}BufferedInputStream internally buffers itself in larger blocks, even if the calling code reads byte by byte — the pattern “always buffer when reading byte by byte or character by character” applies consistently throughout Java, whether for files, network connections, or other streams.
See also: I/O Streams, FileOutputStream, Files, try-with-resources