- Lab
- Core Tech

Guided: Java SE 21 Developer (Exam 1Z0-830) - File Handling and I/O Operations
This Code Lab will teach you how to handle files and work with I/O operations using BufferedReader, BufferedWriter, and NIO.2. You'll implement file-based storage, manage directories, read and write structured data, and perform operations like searching, deleting, and backing up files. By the end of this lab, you'll have hands-on experience handling persistent data in Java applications.

Path Info
Table of Contents
-
Challenge
Introduction
Welcome to the lab Guided: Java SE 21 Developer (Exam 1Z0-830) - File Handling and I/O Operations.
Java provides powerful mechanisms for reading from and writing to files, which are essential for applications that need to store and retrieve information between program executions.
Consider this scenario: You're developing an application that collects quotes. Without file operations, all information would be lost when the program terminates:
// Without file persistence public class SimpleQuoteApp { public static void main(String[] args) { List<String> quotes = new ArrayList<>(); quotes.add("The greatest glory in living lies not in never falling, but in rising every time we fall. - Nelson Mandela"); System.out.println("Stored quotes: " + quotes); // When program ends, all quotes are lost! } }
With proper file handling, you can save this data and retrieve it later:
// With file persistence import java.nio.file.*; import java.io.*; import java.util.*; public class PersistentQuoteApp { public static void main(String[] args) throws IOException { Path filePath = Paths.get("quotes.txt"); // Save a quote String quote = "The greatest glory in living lies not in never falling, but in rising every time we fall. - Nelson Mandela"; Files.writeString(filePath, quote); // Read it back later String savedQuote = Files.readString(filePath); System.out.println("Retrieved quote: " + savedQuote); } }
Java's file handling capabilities have evolved significantly:
- Traditional I/O (java.io): The original package providing input and output streams, readers, and writers
- New I/O (java.nio): Introduced in Java 1.4 with enhanced features like channels and buffers
- NIO.2 (java.nio.file): Added in Java 7, offering a more intuitive API for file operations
This lab will focus on modern approaches using both buffered I/O and NIO.2's Path API.
You'll build a Quote Collection Manager that will:
- Store quotes in text files organized by author
- Generate unique IDs for each quote
- Support operations like adding, reading, deleting, and searching quotes
- Implement backup functionality
Quotes will be stored in a structured format:
quotes/ # Base directory ├── quote_counter.txt # Counter file for generating IDs ├── Albert Einstein/ # Author directory │ ├── quote_001.txt # Quote file │ └── quote_003.txt └── Mark Twain/ # Another author directory └── quote_002.txt
Each quote file will contain the following structured data:
Text: The only source of knowledge is experience. Author: Albert Einstein Source: Life Experience LastModified: 2023-09-25T15:45:30.123Z ``` --- ### Familiarizing with the Program Structure The application includes the following classes in the `src/main/java` directory: - `com.pluralsight.quotemanager.model.Quote.java`: A model class representing a quote with text, author, source, and timestamp - `com.pluralsight.quotemanager.util.Constants.java`: A class with constants for the names of the base directory and counter file - `com.pluralsight.quotemanager.util.FileUtils.java`: A utility class with placeholders for file path management methods - `com.pluralsight.quotemanager.service.FileOperationService.java`: A service class with placeholders for file operations - `com.pluralsight.quotemanager.Main.java`: The class that runs the command-line interface for user interactions The `Quote`, `Constants`, and `Main` classes are fully implemented. You'll be working on the `FileUtils` and `FileOperationService` classes, where `TODO` comments indicate the areas to complete. You can compile and run the application using the **Run** button located at the bottom-right corner of the **Terminal**. Initially, the application will compile and run successfully, but some options will not function correctly. Begin by examining the code to understand the program's structure. Once you're ready, proceed with the coding tasks. If you need help, you can find solution files in the `solution` directory, organized by steps (e.g., `step2`, `step3`, etc.). Each solution file follows the naming convention `[filename]-[step]-[task].java` (e.g., `FileUtils-2-1.java` in `step2`).
-
Challenge
Setting up the File System
Java NIO.2 provides a comprehensive API for file operations that is more intuitive and feature-rich than the legacy
java.io.File
class. It is built around two key components: thePath
interface and theFiles
class.The
Path
InterfaceThe
Path
interface represents a file path in the system. The recommended way to obtain aPath
object is by using thePath.of
method:Path path = Path.of("documents", "reports", "quarterly.txt");
The benefits of using
Path
are:- Cross-platform compatibility: It automatically uses the correct file separator for the operating system.
- Built-in path manipulation methods: Functions like
resolve()
,relativize()
, andnormalize()
, simplify path handling. - Access to path components: Methods such as
getFileName()
,getParent()
, andgetRoot()
allow easy retrieval of specific parts of the path.
Now you're ready to start implementing the file system setup for the Quote Collection Manager using the
Path
interface. ---The
Files
ClassThe
Files
class provides static utility methods for working with files and directories. For example:// Check if a file exists boolean exists = Files.exists(path); // Create directories Files.createDirectories(directoryPath); // Read and write files String content = Files.readString(path); Files.writeString(path, "New content");
Other operations you can perform with the
Files
class are:- Creating files and directories:
createFile()
,createDirectory()
, andcreateDirectories()
- Checking file attributes:
exists()
,isDirectory()
, andisReadable()
- Reading and writing:
readString()
,writeString()
,newBufferedReader()
, andnewBufferedWriter()
- Listing directory contents:
list()
,walk()
, andfind()
- Manipulating files:
copy()
,move()
,delete()
, anddeleteIfExists()
For example, creating nested directory structures is straightforward with NIO.2:
// Creates all necessary parent directories Files.createDirectories(Path.of("data", "archive", "2023"));
This is particularly useful when you need to ensure a directory exists before writing files to it. Unlike the older
File.mkdirs()
method,Files.createDirectories()
provides better error handling and returns the created directory path.Now you're ready to continue implementing the file system setup for the Quote Collection Manager using the
Files
class. -
Challenge
Managing File-based Counters
Java NIO.2 provides straightforward methods for reading and writing simple text files. When you need to read or persist small amounts of data, such as counters or configuration settings, these methods offer a clean alternative to using streams or readers/writers.
Reading Text Files with
Files.readString()
The
Files.readString()
method reads the entire contents of a file into aString
:Path path = Path.of("config.txt"); try { String content = Files.readString(path); System.out.println("File content: " + content); } catch (NoSuchFileException e) { System.out.println("File doesn't exist"); } catch (IOException e) { System.out.println("Error reading file: " + e.getMessage()); }
Here are some key considerations for
Files.readString()
:- It reads the entire file at once, making it suitable for smaller files.
- If the file doesn't exist, it throws a
NoSuchFileException
(a subclass ofIOException
). - You can specify a character set as an optional second parameter (defaults to UTF-8).
However, in many cases, you'll need to handle missing files by using default values. The following example demonstrates how to read a score from a file while providing a default value:
Path counterFile = Path.of("game_score.txt"); int score = 0; // Default value try { String content = Files.readString(counterFile); if (!content.isEmpty()) { score = Integer.parseInt(content.trim()); } } catch (NoSuchFileException e) { // File doesn't exist yet, use the default value } catch (IOException e) { System.err.println("Error reading counter: " + e.getMessage()); }
Now you're ready to implement the method that reads the quote IDs from a file. ---
Writing Text Files with
Files.writeString()
The
Files.writeString()
method writes aString
to a file:Path configPath = Path.of("settings.txt"); String data = "sound=on\ndifficulty=hard"; try { Files.writeString(configPath, data); System.out.println("Settings saved"); } catch (IOException e) { System.err.println("Error saving settings: " + e.getMessage()); }
Here are some key considerations for
Files.writeString()
:- If the file does not exist, it is created automatically.
- If the file already exists, its contents are overwritten.
- You can use options like
StandardOpenOption.APPEND
to append data instead of overwriting. - Similar to
readString()
, you can specify a character set as an optional parameter.
Now you're ready to complete the implementation of the file-based counter for unique quote IDs.
-
Challenge
Writing and Reading Quotes
Writing Structured Data with
BufferedWriter
While
Files.writeString()
is convenient for simple text,BufferedWriter
provides greater control when writing structured data to files. It improves performance through buffering (reducing system calls) and offers methods for writing individual lines.You can create a
BufferedWriter
usingFiles.newBufferedWriter(path)
:Path path = Path.of("data.txt"); try (BufferedWriter writer = Files.newBufferedWriter(path)) { // Use the writer here } catch (IOException e) { System.err.println("Error writing to file: " + e.getMessage()); }
The buffering mechanism of
BufferedWriter
improves efficiency when writing many small pieces of data. If the file doesn't exist, it will be created. If it exists, it will be overwritten by default.Also, notice the use of a
try-with-resources
block to automatically close the writer when the block finishes, even if an exception occurs.The
BufferedWriter
class provides several methods for writing content. Here's an example:try (BufferedWriter writer = Files.newBufferedWriter(path)) { // Write a string writer.write("Customer: John Doe"); // Move to a new line writer.newLine(); // Write more data writer.write("Account: 12345"); writer.newLine(); writer.write("Balance: $1000.00"); } catch (IOException e) { System.err.println("Error writing to file: " + e.getMessage()); }
The
newLine()
method adds platform-specific line separators. When writing structured data, organize related information on separate lines to improve readability and facilitate parsing later.Now you're ready to implement the method that saves a quote to a file. ---
Reading Structured Data with
BufferedReader
Just as
BufferedWriter
facilitates writing structured data,BufferedReader
enables efficient, line-by-line reading of text files. UnlikeFiles.readString()
, which reads the entire file at once,BufferedReader
is particularly useful for processing files with structured formats where data is organized by lines.You can create a
BufferedReader
usingFiles.newBufferedReader(path)
:Path path = Path.of("customer.txt"); try (BufferedReader reader = Files.newBufferedReader(path)) { // Use the reader here } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); }
Files.newBufferedReader()
throwsNoSuchFileException
if the file doesn't exist, and likeBufferedWriter
, usingtry-with-resources
ensures the reader is properly closed.The most common way to use
BufferedReader
is to read a file line by line:try (BufferedReader reader = Files.newBufferedReader(path)) { String line; while ((line = reader.readLine()) != null) { System.out.println("Read line: " + line); // Process each line here } } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); }
The
readLine()
method returnsnull
when it reaches the end of the file.When reading structured data, you often need to extract specific information based on line positions or content. Consider a file containing account details in the following format:
Customer: John Doe Account: 123456789 Balance: $5000.00
The following code reads and extracts the customer name, account number, and balance from the file:
try (BufferedReader reader = Files.newBufferedReader(path)) { String customerLine = reader.readLine(); String accountLine = reader.readLine(); String balanceLine = reader.readLine(); // Parse the data String customerName = customerLine.substring(customerLine.indexOf(":") + 1).trim(); String accountNumber = accountLine.substring(accountLine.indexOf(":") + 1).trim(); String balanceText = balanceLine.substring(balanceLine.indexOf(":") + 1).trim(); System.out.println("Customer: " + customerName); } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); }
When working with structured data, you can extract specific details using string operations such as
substring()
,split()
, or by checking if lines start with certain prefixes.Now you're ready to implement the method that reads a quote from a file.
-
Challenge
Deleting and Listing Quotes
Deleting Files with
Files.deleteIfExists()
The
Files.deleteIfExists()
method provides a convenient way to delete a file without throwing an exception if the file doesn't exist:Path logFile = Path.of("logs", "old_log.txt"); try { boolean deleted = Files.deleteIfExists(logFile); if (deleted) { System.out.println("File deleted successfully"); } else { System.out.println("File did not exist"); } } catch (IOException e) { System.err.println("Error deleting file: " + e.getMessage()); }
Here are some key considerations for
Files.deleteIfExists()
:- Returns
true
if the file was deleted andfalse
if it didn't exist - Throws an
IOException
if deletion fails due to reasons such as permission issues or the file being in use - Often preferred over
Files.delete()
, which throws aNoSuchFileException
if the file does not exist
Now you're ready to implement the method that will be called when users want to remove a quote from the collection. ---
Listing Directory Contents with
Files.list()
The
Files.list()
method returns aStream<Path>
containing the entries in a directory:Path directory = Path.of("documents"); try (Stream<Path> entries = Files.list(directory)) { entries.forEach(path -> { System.out.println(path.getFileName()); }); } catch (IOException e) { System.err.println("Error listing directory: " + e.getMessage()); }
Since
Files.list()
returns a stream, you can leverage Stream API methods to filter and process files:Path documentsDir = Path.of("documents"); try (Stream<Path> entries = Files.list(documentsDir)) { // Get only text files List<Path> textFiles = entries .filter(Files::isRegularFile) // Excludes directories .filter(path -> path.toString().endsWith(".txt")) // Keeps only .txt files .collect(Collectors.toList()); System.out.println("Found " + textFiles.size() + " text files"); } catch (IOException e) { System.err.println("Error processing directory: " + e.getMessage()); }
In this example:
.filter(Files::isRegularFile)
: Keeps only regular files, excluding directories.filter(path -> path.toString().endsWith(".txt"))
: Further filters the stream to include only files with a.txt
extension.collect(Collectors.toList())
: Collects the filtered paths into aList<Path>
Since
Files.list()
returns a stream, you must use atry-with-resources
statement to ensure it is properly closed and to avoid resource leaks:// Proper resource management with try-with-resources try (Stream<Path> stream = Files.list(directory)) { // Use the stream } catch (IOException e) { // Handle exceptions }
Finally, note that
Files.list()
only lists the immediate contents of a directory, it does not perform a recursive listing.Now you're ready to implement the method that lists quotes by authors.
- Returns
-
Challenge
Searching and Modifying Quotes
Searching for Content
Finding content within a directory structure often requires navigating multiple levels. While the previous step covered listing files in a single directory, searching across all quotes involves:
- List all author directories.
- For each author directory, process all quote files.
- Check each file for the search criteria.
One approach to navigate a nested directory structure is using nested streams:
// Outer stream try (Stream<Path> directories = Files.list(rootPath)) { directories.filter(Files::isDirectory).forEach(directory -> { // Inner stream try (Stream<Path> files = Files.list(directory)) { files.filter(Files::isRegularFile).forEach(file -> { // Process each file try { // Read and analyze file content } catch (IOException e) { System.err.println("Error processing file: " + e.getMessage()); } }); } catch (IOException e) { System.err.println("Error listing directory: " + e.getMessage()); } }); }
This snippet recursively processes a nested directory structure using two levels of
Files.list()
streams:- The outer stream iterates over directories inside
rootPath
. - The inner stream lists and processes regular files within each directory.
Java provides multiple ways to search text within files. Here's a basic example:
String text = "My favorite colors are brown and blue"; String searchTerm = "BROWN"; // Case-sensitive search (returns -1 for no match) boolean containsExact = text.contains(searchTerm); // false // Case-insensitive search boolean containsIgnoreCase = text.toLowerCase().contains(searchTerm.toLowerCase()); // true
For a case-insensitive search, both the text and the search term are converted to lowercase. The
toLowerCase()
method is commonly used for this purpose, though it creates new string objects. For more advanced matching, regular expressions can be used.Finally, when gathering results from multiple sources, a common approach is:
- Creating a collection to store matching results
- Iterating through potential sources
- Adding matching items to the collection
For example:
List<String> results = new ArrayList<>(); // Process multiple sources for (String source : sources) { if (source.contains(searchTerm)) { results.add(source); } } return results;
This pattern of building a result collection is fundamental for search operations across multiple files.
Now you're ready to implement the search functionality for your Quote Collection Manager. ---
Moving Files with
Files.move()
One of the final features needed for the application is the ability to move files between directories. Java NIO.2 provides the
Files.move()
method for this purpose.The simplest form of
Files.move()
takes a source path and a target path:Path source = Path.of("documents", "draft.txt"); Path target = Path.of("documents", "final", "report.txt"); try { Files.move(source, target); System.out.println("File moved successfully"); } catch (IOException e) { System.err.println("Error moving file: " + e.getMessage()); }
This snippet moves a file from
documents/draft.txt
todocuments/final/report.txt
usingFiles.move()
. If the move is successful, a confirmation message is printed. Otherwise, anIOException
is caught and an error message is displayed.The
Files.move()
method allows you to:- Move a file to a different directory.
- Rename a file (when the target is in the same directory but has a different name).
- Do both simultaneously.
Additionally,
Files.move()
supports optional parameters to control its behavior:// Move and replace if the target already exists Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); // Atomic move (when supported by the file system) Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
The most commonly used option is
StandardCopyOption.REPLACE_EXISTING
, which overwrites the target file if it already exists. Without this option, an exception is thrown if the target file exists.Now you're ready to implement the functionality that lets users change a quote's author by moving its file.
-
Challenge
Backing up the Quotes
Creating a backup system involves copying an entire directory structure, including all subdirectories and files. Java NIO.2 provides powerful tools to accomplish this efficiently.
Walking Directory Trees with
Files.walk()
Unlike
Files.list()
, which only processes the immediate contents of a directory,Files.walk()
recursively traverses an entire directory tree:Path rootDir = Path.of("documents"); try (Stream<Path> allPaths = Files.walk(rootDir)) { allPaths.forEach(path -> { System.out.println(path); }); } catch (IOException e) { System.err.println("Error walking directory tree: " + e.getMessage()); }
This code will list all files and directories under
documents
, including those in subdirectories at any depth.When walking a directory tree, you need to distinguish between files and directories. You can use
Files.isDirectory()
to handle them separately:if (Files.isDirectory(path)) { // Process directories } else { // Process files }
This distinction allows for different operations, such as recursively traversing directories or reading file contents.
Working with Relative Paths
When copying a directory structure, maintaining relative paths between the source and destination is essential. Java NIO.2 provides two key methods for this:
relativize()
: Creates a relative path between two pathsresolve()
: Combines a path with a relative path
For example, given a root directory and a specific file within it:
Path sourceRoot = Path.of("/home/user/documents"); Path sourceFile = Path.of("/home/user/documents/project/report.txt");
You can obtain the relative path from the root to the file using
relativize()
:Path relativePath = sourceRoot.relativize(sourceFile); // "project/report.txt"
This relative path can then be applied to a new root directory using
resolve()
:Path backupRoot = Path.of("/backup/docs"); Path backupFile = backupRoot.resolve(relativePath); // "/backup/docs/project/report.txt"
This approach ensures that directory structures remain intact when copying or backing up files.
Copying Files with
Files.copy()
Java NIO.2 provides a straightforward way to copy files using
Files.copy()
:Path source = Path.of("original.txt"); Path target = Path.of("backup", "original.txt"); // Basic copy (fails if the target file already exists) Files.copy(source, target); // Copy with options (overwrites the target file if it exists) Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
By default,
Files.copy()
does not overwrite existing files. However, you can specifyStandardCopyOption.REPLACE_EXISTING
to allow overwriting.Now you're ready to implement the backup functionality for your Quote Collection Manager.
-
Challenge
Conclusion
Congratulations on successfully completing this Code Lab!
You can compile and run the application either by clicking the Run button in the bottom-right corner of the screen or by using the Terminal with the following commands:
-
Compile and package the application:
mvn clean package
-
Run the application:
java -cp target/quote-collection-manager-1.0-SNAPSHOT.jar com.pluralsight.quotemanager.Main
When you start the application, the main menu appears:
Quote Collection Manager 1. Add New Quote 2. Read Quote 3. Delete Quote 4. List Quotes by Author 5. Search Quotes 6. Change Quote's Author 7. Backup Collection 8. Exit Choose an option:
Try adding a few quotes, searching for specific content, and creating a backup to see all your implementation in action! ---
Extending the Program
Here are some ideas to further enhance your skills and extend the application's capabilities:
-
Object Serialization: Modify the program to store quotes as serialized Java objects instead of text files, making the
Quote
class implementSerializable
and useObjectOutputStream
andObjectInputStream
for storage and retrieval. -
File Change Monitoring: Automatically refresh the application's data when files are modified externally by using Java's
WatchService
to monitor the quotes directory for changes. -
Memory-Mapped File Access: For very large quote collections, implement memory-mapped file access using
FileChannel.map()
to improve performance when reading and writing large files.
By implementing these enhancements, you'll gain a deeper understanding of Java's file-handling capabilities and create a more robust Quote Collection Manager. ---
Related Courses in Pluralsight's Library
If you'd like to continue building your Java skills or explore related topics, check out these courses available on Pluralsight:
These courses cover a wide range of Java programming topics. Explore them to further your learning journey in Java!
-
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.