Working with FileInputStream and FileOutputStream in Java
Course: Core Java Training
Topic: Working with FileInputStream and FileOutputStream
Course Link: Core Java Training Course
Demo Link: Register for a Free Course Demo
Website: JustAcademy Official Website
1. Introduction to FileInputStream and FileOutputStream
Java provides several classes for reading data from files and writing data to files. Two important classes in the java.io package are FileInputStream and FileOutputStream. They work with raw bytes rather than directly processing text characters.
- FileInputStream: Reads bytes from an existing file.
- FileOutputStream: Writes bytes to a file.
These classes are useful when working with binary data, such as images, audio files, PDFs, and other files that must be processed as bytes. They can also read and write text files, but character-oriented classes are usually more convenient when the goal is to process text.
2. Learning Objectives
After completing this topic, you will be able to:
- Understand byte streams in Java.
- Explain the purpose of FileInputStream and FileOutputStream.
- Read file contents using different read methods.
- Write byte data into files.
- Copy files from one location to another.
- Append data without overwriting existing content.
- Handle IOException and FileNotFoundException.
- Use try-with-resources to close streams safely.
- Apply file streams in practical Java programs.
3. Understanding Byte Streams
A stream represents a flow of data between a source and a destination. In Java, byte streams process data one byte at a time or in groups of bytes.
For example, when a program reads an image file, it receives bytes representing the image data. When a program writes those bytes into another file, it can create a copy of the original image.
Types of Java Streams
| Stream Type |
Purpose |
Examples |
| Input Stream |
Reads data into a program. |
FileInputStream, BufferedInputStream |
| Output Stream |
Writes data from a program. |
FileOutputStream, BufferedOutputStream |
| Reader |
Reads character-based data. |
FileReader, BufferedReader |
| Writer |
Writes character-based data. |
FileWriter, BufferedWriter |
Important: FileInputStream and FileOutputStream are byte-stream classes. For text files, use character streams or APIs such as Files.readString() and Files.writeString() when appropriate.
4. What Is FileInputStream?
FileInputStream is a class in the java.io package that reads raw bytes from a file. It is commonly used to read binary data or to process a file at the byte level.
Syntax
FileInputStream input = new FileInputStream("input.txt");
The constructor opens the specified file for reading. If the file cannot be opened, Java may throw a FileNotFoundException.
Common Constructors
| Constructor |
Description |
FileInputStream(String name) |
Opens a file using its path. |
FileInputStream(File file) |
Opens the file represented by a File object. |
Important FileInputStream Methods
| Method |
Description |
read() |
Reads one byte and returns it as an integer from 0 to 255, or -1 when the end of the stream is reached. |
read(byte[] b) |
Reads bytes into the supplied byte array. |
read(byte[] b, int off, int len) |
Reads bytes into a specified section of an array. |
available() |
Estimates how many bytes can be read without blocking; it is not a reliable general-purpose file-size method. |
skip(long n) |
Attempts to skip a number of bytes. |
close() |
Closes the stream and releases associated resources. |
5. Reading a File Using FileInputStream
The simplest approach is to call read() repeatedly until it returns -1. The value -1 indicates that the end of the stream has been reached.
Example 1: Reading One Byte at a Time
import java.io.FileInputStream;
import java.io.IOException;
public class ReadFileExample {
public static void main(String[] args) {
try (FileInputStream input =
new FileInputStream("input.txt")) {
int data;
while ((data = input.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
System.out.println("Error reading file: "
+ e.getMessage());
}
}
}
Explanation
- Import FileInputStream and IOException.
- Open the file named
input.txt.
- Call
read() to retrieve one byte at a time.
- Continue until the method returns
-1.
- Cast each byte value to a character for this simple text example.
- The try-with-resources statement closes the stream automatically.
Note: Casting individual bytes to characters is suitable only for simple text that uses compatible single-byte characters. It does not correctly decode every character encoding, such as UTF-8 text containing many non-ASCII characters.
6. Reading Data into a Byte Array
Reading one byte at a time is easy to understand but can be inefficient for large files. A byte array allows the program to process a group of bytes in each read operation.
Example 2: Reading in Blocks
import java.io.FileInputStream;
import java.io.IOException;
public class ReadBytesExample {
public static void main(String[] args) {
byte[] buffer = new byte[1024];
try (FileInputStream input =
new FileInputStream("input.txt")) {
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
System.out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
How It Works
byte[] buffer = new byte[1024] creates a buffer that can hold up to 1024 bytes.
input.read(buffer) fills the buffer with available bytes.
- The returned value indicates the actual number of bytes read.
System.out.write(buffer, 0, bytesRead) outputs only the bytes that were read, not the unused portion of the buffer.
7. What Is FileOutputStream?
FileOutputStream is a Java class used to write raw bytes to a file. If the destination file does not exist, Java attempts to create it. If the file already exists, the standard non-append constructor truncates its existing contents when the stream is opened.
Syntax
FileOutputStream output =
new FileOutputStream("output.txt");
Common Constructors
| Constructor |
Description |
FileOutputStream(String name) |
Opens a file for writing and truncates it if it already exists. |
FileOutputStream(File file) |
Opens the specified File object for writing and truncates an existing file. |
FileOutputStream(String name, boolean append) |
Writes to the end of an existing file when append is true; otherwise, truncates it. |
FileOutputStream(File file, boolean append) |
Provides append or overwrite behavior for a File object. |
Important FileOutputStream Methods
| Method |
Description |
write(int b) |
Writes the lowest eight bits of the supplied integer as one byte. |
write(byte[] b) |
Writes all bytes from the supplied array. |
write(byte[] b, int off, int len) |
Writes a selected range of bytes from an array. |
flush() |
Requests that buffered output be written; FileOutputStream itself does not add a separate application-level buffer. |
close() |
Closes the stream and releases resources. |
8. Writing Data Using FileOutputStream
To write text with FileOutputStream, convert the text into bytes using a defined character encoding, such as UTF-8.
Example 3: Writing Text to a File
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class WriteFileExample {
public static void main(String[] args) {
String message = "Welcome to Core Java File Handling!";
try (FileOutputStream output =
new FileOutputStream("output.txt")) {
byte[] data =
message.getBytes(StandardCharsets.UTF_8);
output.write(data);
System.out.println("Data written successfully.");
} catch (IOException e) {
System.out.println("Error writing file: "
+ e.getMessage());
}
}
}
Expected Result
The file output.txt contains:
Welcome to Core Java File Handling!
If output.txt already contains data, opening it with this constructor removes the old contents before the new data is written. Use append mode if you need to preserve the existing contents.
9. Appending Data to an Existing File
Appending means adding new data at the end of a file instead of replacing its existing contents. FileOutputStream supports this through the boolean append argument.
Example 4: Appending Text
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class AppendFileExample {
public static void main(String[] args) {
String message = System.lineSeparator()
+ "This line has been appended.";
try (FileOutputStream output =
new FileOutputStream("output.txt", true)) {
output.write(message.getBytes(StandardCharsets.UTF_8));
System.out.println("Data appended successfully.");
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
Passing true to the constructor enables append mode. The existing data is retained, and the new bytes are written at the end.
10. Copying a File Using Both Streams
A common practical use of FileInputStream and FileOutputStream is copying a file. The input stream reads bytes from the source, while the output stream writes those bytes to the destination.
Example 5: Copying a File
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFileExample {
public static void main(String[] args) {
String source = "source.jpg";
String destination = "copy.jpg";
byte[] buffer = new byte[8192];
try (FileInputStream input =
new FileInputStream(source);
FileOutputStream output =
new FileOutputStream(destination)) {
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
System.out.println("File copied successfully.");
} catch (IOException e) {
System.out.println("File copy failed: "
+ e.getMessage());
}
}
}
Step-by-Step Explanation
- Specify the source and destination file paths.
- Create a buffer to hold 8192 bytes at a time.
- Open the source file using FileInputStream.
- Open the destination file using FileOutputStream.
- Read a block of bytes into the buffer.
- Write exactly the number of bytes returned by the read operation.
- Repeat until the source stream reaches the end of the file.
- Close both streams automatically with try-with-resources.
Important: Choose a destination path different from the source path. Opening the same file as both the source and destination can truncate the source before it is read.
11. Understanding Try-With-Resources
File streams use operating-system resources. If a stream is left open, it can cause resource leaks and may prevent files from being accessed or modified as expected.
Java's try-with-resources statement automatically closes resources that implement AutoCloseable, including FileInputStream and FileOutputStream.
Recommended Pattern
try (FileInputStream input =
new FileInputStream("input.txt")) {
int data;
while ((data = input.read()) != -1) {
System.out.print(data + " ");
}
} catch (IOException e) {
System.out.println("I/O error: " + e.getMessage());
}
This approach is preferred over manually calling close() in a finally block for most standard file-stream operations. Resources are closed even when an exception occurs within the try block.
12. Exception Handling in File Streams
File operations can fail for several reasons, including missing files, insufficient permissions, invalid paths, unavailable storage, or other input/output problems.
| Exception |
Possible Cause |
Recommended Action |
FileNotFoundException |
The file does not exist for reading, is a directory, or cannot be opened. |
Verify the path and permissions. |
IOException |
A general input/output operation fails. |
Handle the error and provide a useful message. |
SecurityException |
A security restriction prevents the operation. |
Review access restrictions and application permissions. |
NullPointerException |
A null reference is supplied where an object is required. |
Validate variables before use. |
Example 6: Handling a Missing File
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionExample {
public static void main(String[] args) {
try (FileInputStream input =
new FileInputStream("missing.txt")) {
int data;
while ((data = input.read()) != -1) {
System.out.print((char) data);
}
} catch (FileNotFoundException e) {
System.out.println("The requested file was not found.");
} catch (IOException e) {
System.out.println("An I/O error occurred: "
+ e.getMessage());
}
}
}
Specific exceptions can be caught before more general exceptions. Because FileNotFoundException extends IOException, its catch block must appear first when both are handled separately.
13. FileInputStream vs FileOutputStream
| Feature |
FileInputStream |
FileOutputStream |
| Purpose |
Reads bytes from a file. |
Writes bytes to a file. |
| Direction |
File to program. |
Program to file. |
| Main method |
read() |
write() |
| End-of-file behavior |
read() returns -1 when the end is reached. |
There is no equivalent read end-of-file return value. |
| Missing file |
Cannot open a missing file for reading. |
Can create a missing destination file when the path and permissions allow. |
| Existing file behavior |
Reads existing contents. |
Normally truncates the file unless append mode is enabled. |
| Typical applications |
Reading and copying files. |
Creating, saving, appending, and copying files. |
14. Byte Streams vs Character Streams
Choosing the right stream type improves correctness and makes code easier to maintain.
| Requirement |
Suitable Choice |
| Copy an image or PDF without changing its bytes |
FileInputStream and FileOutputStream |
| Read plain text as characters |
FileReader or BufferedReader |
| Write text with a specific character encoding |
OutputStreamWriter or Files.writeString |
| Copy files with modern NIO APIs |
Files.copy |
| Read large files efficiently |
Buffered streams or suitable NIO APIs |
15. Common Mistakes and How to Avoid Them
- Forgetting to close streams: Use try-with-resources to release resources automatically.
- Accidentally overwriting data: Remember that the standard FileOutputStream constructor truncates an existing file.
- Using append mode incorrectly: Pass
true only when new data should be added to the end.
- Reading a byte array incorrectly: Use the actual number of bytes returned by
read() rather than processing the whole buffer every time.
- Using byte-to-character casts for all text: Decode bytes using the correct character encoding for reliable text handling.
- Using an incorrect file path: Check whether a relative path is resolved from the application's current working directory.
- Copying a file onto itself: Make sure the destination does not point to the same file as the source.
- Ignoring exceptions: Report useful error messages and take appropriate recovery steps.
- Loading a large file entirely into memory: Use a buffer and process data in blocks.
16. Best Practices
- Use try-with-resources for streams that need to be closed.
- Prefer buffered block reads and writes over repeated single-byte operations for large files.
- Use explicit character encodings such as UTF-8 when converting text to bytes.
- Check paths and file permissions when an operation fails.
- Do not assume that opening an output stream preserves the old contents.
- Use append mode for log files or other files that must retain previous data.
- Handle IOException instead of silently ignoring file-operation failures.
- For straightforward file-copy tasks, consider the modern
java.nio.file.Files.copy() API.
- Do not use FileInputStream and FileOutputStream when a character-oriented API would express the task more clearly.
17. Practical Exercises
- Read a Text File: Write a program that reads a file using FileInputStream and displays its contents.
- Write Text to a File: Store a message in a new file using FileOutputStream and UTF-8 encoding.
- Append a Message: Add a new line to an existing file without removing its current contents.
- Copy an Image: Use both streams to create a byte-for-byte copy of an image.
- Handle Missing Files: Try reading a file that does not exist and display a meaningful error message.
- Copy a Large File: Use a buffer to copy a large file without reading the entire file into memory.
- Compare File Contents: Read the bytes of two small files and determine whether their contents match.
18. Mini Project: File Backup Utility
Objective: Create a simple Java program that copies a file to a backup location using FileInputStream and FileOutputStream.
Project Requirements
- Accept a source file path and a destination file path.
- Read the source file in blocks of bytes.
- Write each block to the destination file.
- Handle missing files and other I/O errors.
- Close all streams automatically.
- Display a success message only after the copy operation finishes.
Sample Implementation
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileBackupUtility {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println(
"Usage: java FileBackupUtility ");
return;
}
String source = args[0];
String destination = args[1];
byte[] buffer = new byte[8192];
try (FileInputStream input =
new FileInputStream(source);
FileOutputStream output =
new FileOutputStream(destination)) {
int count;
while ((count = input.read(buffer)) != -1) {
output.write(buffer, 0, count);
}
System.out.println("Backup completed successfully.");
} catch (IOException e) {
System.out.println("Backup failed: " + e.getMessage());
}
}
}
How to run: Compile the class and pass two paths as command-line arguments: the source file first and the destination file second. Test with non-critical files first, because the destination is overwritten if it already exists. A production backup utility should also validate that the source and destination are different files and avoid overwriting important data unintentionally.
19. Interview Questions and Answers
Q1. What is FileInputStream in Java?
Answer: FileInputStream is a byte-stream class in the java.io package that reads raw bytes from a file.
Q2. What is FileOutputStream?
Answer: FileOutputStream writes raw bytes to a file. It can create a destination file if it does not exist and can append data when configured with append mode.
Q3. What does read() return at the end of a file?
Answer: It returns -1 when no more bytes are available because the end of the stream has been reached.
Q4. Why does read() return an int instead of a byte?
Answer: The int type can represent all possible byte values from 0 through 255 and the special end-of-stream value -1.
Q5. What happens when FileOutputStream opens an existing file without append mode?
Answer: The existing file is truncated when the stream is opened, so its previous contents are removed.
Q6. How do you append data to a file?
Answer: Create the stream using a constructor such as new FileOutputStream("output.txt", true).
Q7. Why should streams be closed?
Answer: Closing releases operating-system resources and ensures that output operations are completed appropriately. Try-with-resources is a recommended way to manage this lifecycle.
Q8. What is try-with-resources?
Answer: It is a Java language feature that automatically closes declared resources when control leaves the try block, including when an exception occurs.
Q9. Can FileInputStream read text files?
Answer: Yes. It reads their bytes, but text should be decoded using the correct character encoding. Reader classes are often more suitable for character-based processing.
Q10. Can FileOutputStream write images?
Answer: Yes. It can write raw image bytes and is useful when copying or saving binary files without changing their content.
Q11. What is the difference between byte streams and character streams?
Answer: Byte streams process bytes, while character streams process characters and support character encoding and decoding.
Q12. Which exception is commonly associated with file stream operations?
Answer: FileNotFoundException may occur when a file cannot be opened, and IOException may occur during reading, writing, or closing.
Q13. Why is a buffer used when copying a file?
Answer: A buffer processes multiple bytes per operation, usually reducing the overhead associated with reading and writing one byte at a time.
Q14. What is the purpose of the append parameter?
Answer: When it is true, new output bytes are added to the end of an existing file rather than replacing its contents.
Q15. What is a modern alternative for copying files?
Answer: The java.nio.file.Files.copy() method is a modern alternative for many file-copy operations.
20. Quick Revision Checklist
- FileInputStream reads bytes from files.
- FileOutputStream writes bytes to files.
read() returns -1 at the end of the stream.
read(byte[] buffer) reads a block of bytes.
write(byte[] data) writes bytes from an array.
- Opening FileOutputStream without append mode truncates an existing file.
- Use append mode when existing contents must be retained.
- Use try-with-resources to close streams safely.
- Handle IOException and other relevant exceptions.
- Use a suitable character encoding when converting text to bytes.
- Use buffers when processing larger files.
- Validate source and destination paths before copying files.
21. Conclusion
FileInputStream and FileOutputStream are fundamental Java classes for byte-based file handling. FileInputStream reads data from a file, while FileOutputStream writes data to a file. Together, they can be used to read binary data, save bytes, append content, and copy files efficiently.
To write reliable file-handling programs, understand the behavior of each constructor, process data using the correct number of bytes, handle exceptions, and close resources with try-with-resources. For text data, use a defined character encoding or an appropriate character-oriented API.
Continue learning Core Java concepts through the JustAcademy Core Java Training Course. You can also register for a course demo or visit the JustAcademy website for more information.