This collection of 32 Java file handling exercises covers everything from basic file and directory operations to binary streams, compression, and recursive file-system traversal.
What You’ll Practice
- Fundamentals: Checking, creating, listing, filtering, and deleting files and directories with the
Fileclass. - Reading & Writing:
FileWriter,BufferedReader, byte streams for binary data, and the modern NIOFilesutility class. - Text Processing: Word/line/character counting, keyword search, search-and-replace, and parsing/writing CSV files.
- Advanced: Zipping and unzipping archives, merging and splitting files, and recursively finding the largest file in a directory tree.
Each exercise includes a Problem Statement, Purpose, Hint, and a Solution with Explanation, along with a modern NIO alternative wherever one exists.
- Also, See: Java Exercises with over 20+ topic-wise sets and 575+ coding questions to practice.
- Practice questions using our Online Java Compiler
+ Table of Contents (32 Exercises)
Table of contents
- Exercise 1: File Checker
- Exercise 2: File Creator
- Exercise 3: Directory Maker
- Exercise 4: Directory Lister
- Exercise 5: Extension Filter
- Exercise 6: File Destroyer
- Exercise 7: Metadata Inspector
- Exercise 8: Simple Writer
- Exercise 9: Simple Reader
- Exercise 10: Text Appender
- Exercise 11: File Cloner
- Exercise 12: The Mover
- Exercise 13: Word Counter
- Exercise 14: Word Finder
- Exercise 15: Search & Replace
- Exercise 16: Media Copier
- Exercise 17: CSV Reader
- Exercise 18: CSV Writer
- Exercise 19: The Zipper
- Exercise 20: The Unzipper
- Exercise 21: The “Head” Command (First 10 Lines)
- Exercise 22: Target Line Extractor (Specific Line)
- Exercise 23: Selective Line Skipper
- Exercise 24: The Specific Line Updater
- Exercise 25: Read and Store (List Conversion)
- Exercise 26: The Reverser (Bottom-to-Top Reader)
- Exercise 27: The Interleaver (File Merger)
- Exercise 28: Tokenizer (Word-by-Word Reader)
- Exercise 29: Character Frequency Counter
- Exercise 30: The Log Filter (Conditional Extraction)
- Exercise 31: File Splitter
- Exercise 32: Giant Finder
Exercise 1: File Checker
Problem Statement: Write a program to check if a specified file or directory exists on your system.
Purpose: This exercise introduces the File class as a representation of a path on disk, and shows how to safely check for existence before performing any read or write operation on it.
Given Input: File file = new File("sample.txt"); (the file has not been created yet)
Expected Output: sample.txt does not exist.
▼ Hint
- Create a
Fileobject with the path you want to check. Creating the object does not create anything on disk. - Use the
exists()method to check whether that path actually points to something. exists()works the same way for both files and directories.
▼ Solution & Explanation
Explanation:
new File("sample.txt"): Creates a lightweight object representing a path in the file system. This does not touch the disk in any way.file.exists(): Performs the actual check against the file system, returningtrueonly if something is really present at that path.file.getName(): Returns just the file name portion of the path, without any parent directories, for use in the printed message.- Alternative: The same check also works for directories, since
Fileandexists()do not distinguish between files and folders on their own.
Exercise 2: File Creator
Problem Statement: Create a new empty text file named sample.txt. Handle the exception if the file already exists.
Purpose: This exercise practices creating a file on disk and handling the checked IOException that file operations can throw, such as when there are permission problems or invalid paths.
Given Input: File file = new File("sample.txt"); (the file does not exist yet)
Expected Output: File created: sample.txt
▼ Hint
- Use
createNewFile(), which returns abooleaninstead of throwing when the file already exists. - Wrap the call in a
try-catchblock forIOException, since it is still a checked exception that can occur for other reasons like invalid paths. - Check the returned
booleanto distinguish between “created successfully” and “already existed”.
▼ Solution & Explanation
Explanation:
file.createNewFile(): Attempts to create a new, empty file at the given path, returningtrueif it succeeded andfalseif a file already existed there.throws IOException:createNewFile()can still throw this checked exception for reasons unrelated to the file already existing, such as the parent directory not existing.catch (IOException e): Handles any of those underlying I/O failures gracefully instead of letting the program crash.- Alternative: Running the same program a second time, once
sample.txtalready exists, would print"File already exists: sample.txt"instead, sincecreateNewFile()returnsfalserather than throwing in that case.
Exercise 3: Directory Maker
Problem Statement: Create a nested directory structure (e.g., projects/java/exercises) using a single command.
Purpose: This exercise contrasts creating a single directory with creating an entire chain of nested directories at once, a common setup step for organizing project files.
Given Input: File directories = new File("projects/java/exercises"); (none of these folders exist yet)
Expected Output: Directory structure created: projects/java/exercises
▼ Hint
- Use
mkdirs()rather thanmkdir(). Onlymkdirs()creates any missing parent directories along the way. mkdirs()returns abooleanindicating whether at least the final directory was actually created.- Use
getPath()to print the relative path that was created.
▼ Solution & Explanation
Explanation:
directories.mkdirs(): Creates every missing directory in the path in one call, includingprojects,projects/java, andprojects/java/exercises.- Difference from
mkdir():mkdir()only creates the final directory and fails if any parent directory in the path does not already exist, whilemkdirs()builds the whole chain. directories.getPath(): Returns the path exactly as it was passed into theFileconstructor, used here to confirm what was created.- Alternative: Running this program again after the structure already exists would print the “already exists” message, since
mkdirs()returnsfalsewhen there is nothing left to create.
Exercise 4: Directory Lister
Problem Statement: List all files and folders inside a given directory.
Purpose: This exercise practices reading the contents of a directory as an array of File objects, a foundation for building any kind of file browser or batch-processing tool.
Given Input: A folder named projects containing a subfolder java and a file readme.txt
Expected Output:
java readme.txt
▼ Hint
- Use
listFiles()on aFilethat points to a directory. It returns aFile[]array. listFiles()returnsnullinstead of an empty array if the path is not a valid, accessible directory, so check fornullfirst.- Loop through the array and print each entry’s name with
getName().
▼ Solution & Explanation
Explanation:
folder.listFiles(): Returns an array ofFileobjects representing every entry directly insideprojects, including both files and subdirectories.contents != null: Guards against the case wherefolderdoes not exist, is not a directory, or an I/O error occurs, sincelistFiles()returnsnullrather than throwing in those situations.item.getName(): Prints just the entry’s own name, whether it happens to be a file or a folder, sinceFiledoes not differentiate them by type in this call.- Alternative: Calling
item.isDirectory()inside the loop would let you label each entry as a file or folder in the printed output.
Exercise 5: Extension Filter
Problem Statement: List only files with a .txt or .java extension from a specific folder.
Purpose: This exercise builds on directory listing by adding a filtering condition, showing how listFiles() can accept a filter so only matching entries are returned.
Given Input: A folder named projects containing Main.java, notes.txt, and image.png
Expected Output:
Main.java notes.txt
▼ Hint
- Use the overload of
listFiles()that accepts aFilenameFilter. - A lambda expression can implement
FilenameFilterdirectly:(dir, name) -> .... - Use
String.endsWith()to check for either extension inside the lambda.
▼ Solution & Explanation
Explanation:
listFiles((dir, name) -> ...): Passes a lambda implementingFilenameFilter, which the method calls once for every entry in the directory to decide whether to include it.name.endsWith(".txt") || name.endsWith(".java"): Returnstrueonly for entries whose file name ends with one of the two target extensions.image.png: Excluded from the results because its extension matches neither condition in the filter.- Alternative: The same filtering could be written without a lambda, using an anonymous
FilenameFilterclass, or applied after the fact with a stream andFiles.list()from the newer NIO API.
Exercise 6: File Destroyer
Problem Statement: Write a program to delete a specific file. Then, modify it to safely delete a directory only if it’s empty.
Purpose: This exercise practices file and directory deletion, and highlights that delete() silently refuses to remove a non-empty directory rather than throwing, so an explicit emptiness check is worthwhile.
Given Input: sample.txt exists, and projects/java/exercises is an empty directory
Expected Output:
Deleted file: sample.txt Deleted empty directory: projects/java/exercises
▼ Hint
- Use
delete()for the file, and check itsbooleanreturn value to confirm success. - For the directory, first confirm it is actually a directory with
isDirectory(). - Use
list()to get the directory’s contents as aString[], and check that its length is0before callingdelete()on it.
▼ Solution & Explanation
Explanation:
file.delete(): Removes the file from disk and returnstrueon success, orfalseif it could not be deleted, such as when it does not exist.directory.isDirectory(): Confirms the path actually refers to a directory before attempting any directory-specific logic on it.directory.list().length == 0: Checks that the directory contains no entries. Callingdelete()on a non-empty directory would simply fail silently and returnfalse, so this check makes the intent explicit.- Alternative: Deleting a directory that still contains files would require recursively deleting its contents first, since plain
delete()never removes a directory’s contents for you.
Exercise 7: Metadata Inspector
Problem Statement: Read and display a file’s metadata: size in bytes, absolute path, read/write permissions, and last modified date.
Purpose: This exercise practices reading metadata about a file rather than its contents, useful for building tools like file browsers, backup utilities, or sync checkers.
Given Input: File file = new File("sample.txt"); (an existing file on disk)
Expected Output (actual values depend on your system and file contents):
Size: 45 bytes Absolute path: /home/user/sample.txt Readable: true Writable: true Last modified: Fri Jul 03 10:15:22 IST 2026
▼ Hint
- Use
length()for size,getAbsolutePath()for the full path, andcanRead()/canWrite()for permissions. lastModified()returns alongtimestamp in milliseconds, not a readable date directly.- Wrap that timestamp in a
new Date(...)object to get a human-readable representation.
▼ Solution & Explanation
Explanation:
file.length(): Returns the file’s size in bytes as along, not the number of characters or lines it contains.file.getAbsolutePath(): Resolves the path relative to the current working directory into a full, unambiguous path on disk.file.canRead()andfile.canWrite(): Check the current process’s actual read and write permissions for the file, which can differ from the file simply existing.new Date(file.lastModified()): Converts the raw millisecond timestamp returned bylastModified()into a readable date and time.
Exercise 8: Simple Writer
Problem Statement: Write a single sentence into a new file using FileWriter.
Purpose: This exercise introduces FileWriter as the simplest way to write character data to a file, along with try-with-resources to ensure the file is closed automatically.
Given Input: writer.write("Java file handling exercises are fun to practice.");
Expected Output: Content written successfully.
▼ Hint
- Open the
FileWriterinside atry-with-resourcesstatement so it always closes, even if writing fails partway through. - By default,
FileWriteroverwrites the file’s existing content if the file already exists. - Call
write()with the sentence you want to save, and catchIOExceptionaround the whole block.
▼ Solution & Explanation
Explanation:
new FileWriter("sample.txt"): Opens the file for writing, creating it if it does not already exist, and truncating any existing content by default.try (FileWriter writer = ...): SinceFileWriterimplementsAutoCloseable, using try-with-resources guarantees the underlying file handle is released once writing finishes.writer.write(...): Writes the given string’s characters directly to the file, without adding a trailing newline automatically.- Alternative:
Files.writeString(Path.of("sample.txt"), "...")from the newer NIO API achieves the same result in a single line, without needing an explicittry-with-resourcesblock.
Exercise 9: Simple Reader
Problem Statement: Read the content of a text file and print it to the console line by line using BufferedReader.
Purpose: This exercise introduces BufferedReader wrapped around a FileReader, the standard combination for reading a text file efficiently one line at a time.
Given Input: sample.txt containing two lines: "Line one of the file." and "Line two of the file."
Expected Output:
Line one of the file. Line two of the file.
▼ Hint
- Wrap a
FileReaderinside aBufferedReaderto get access to the convenientreadLine()method. readLine()returnsnullonce the end of the file is reached, which makes a good loop condition.- Use
try-with-resourcesso the reader closes automatically once the loop finishes.
▼ Solution & Explanation
Explanation:
new BufferedReader(new FileReader("sample.txt")): Wraps the basicFileReaderin aBufferedReader, which adds efficient line-by-line reading throughreadLine().while ((line = reader.readLine()) != null): Reads one line at a time, assigning it tolineand looping untilreadLine()signals the end of the file by returningnull.System.out.println(line): Prints each line exactly as read, without the line terminator, sincereadLine()strips it automatically.- Alternative:
Files.readAllLines(Path.of("sample.txt"))from the NIO API reads the whole file into aList<String>in one call, which is simpler for small files but loads everything into memory at once.
Exercise 10: Text Appender
Problem Statement: Open an existing text file and append a new line of text to the bottom without overwriting the existing content.
Purpose: This exercise shows how a small constructor flag changes FileWriter‘s default overwrite behavior into append mode, a distinction that is easy to miss and easy to get wrong.
Given Input: sample.txt already contains "Java file handling exercises are fun to practice."
Expected Output: Content appended successfully.
▼ Hint
- Use the
FileWriterconstructor overload that accepts a secondbooleanparameter for append mode:new FileWriter("sample.txt", true). - Start the new content with a newline character so it lands on its own line below the existing text.
- Without the
trueflag,FileWriterwould erase the file’s existing content before writing.
▼ Solution & Explanation
Explanation:
new FileWriter("sample.txt", true): The second argument,true, switches the writer into append mode, so new writes are added after the file’s existing content instead of replacing it."\nThis line was appended...": The leading\nmoves the new text onto its own line, sincewrite()does not insert line breaks automatically.- Default behavior without
true: Omitting the second argument would causeFileWriterto truncate the file first, silently discarding"Java file handling exercises are fun to practice."before writing the new line. - Alternative:
Files.writeString(path, text, StandardOpenOption.APPEND)from the NIO API offers the same append behavior with an explicit, self-documenting option instead of a plainbooleanflag.
Exercise 11: File Cloner
Problem Statement: Copy the contents of one text file into another file using standard byte streams (FileInputStream and FileOutputStream).
Purpose: This exercise introduces byte streams as the lowest-level way to move data between files, reading and writing raw bytes rather than characters, which works for any file type.
Given Input: source.txt containing some text content
Expected Output: File copied successfully.
▼ Hint
- Open both a
FileInputStreamfor reading and aFileOutputStreamfor writing inside the sametry-with-resourcesstatement. - Read into a
byte[]buffer in a loop, since large files cannot be read in one call. read()returns-1once the end of the input stream is reached, which makes a good loop condition.- Write only the actual number of bytes read on each pass, not the full buffer size.
▼ Solution & Explanation
Explanation:
byte[] buffer = new byte[1024]: Reads data in fixed-size chunks instead of one byte at a time, which is far more efficient for anything larger than a trivial file.input.read(buffer): Fills the buffer with the next chunk of bytes and returns how many were actually read, or-1once there is nothing left.output.write(buffer, 0, bytesRead): Writes only the portion of the buffer that was actually filled on the last read, avoiding leftover data from a previous, larger chunk.- Alternative:
Files.copy(Path.of("source.txt"), Path.of("destination.txt"))from the NIO API performs the same copy in a single call, without managing a buffer manually.
Exercise 12: The Mover
Problem Statement: Move a file from one directory to another and rename it in the process.
Purpose: This exercise shows that moving and renaming a file are actually the same operation in Java’s File API, since both simply change the path a file is associated with.
Given Input: reports/draft.txt exists, and the archive folder already exists
Expected Output: File moved and renamed to: archive/final_report.txt
▼ Hint
- Create two
Fileobjects, one for the current path and one for the new path, including the new file name. - Call
renameTo()on the source file, passing the destination file as the argument. - The destination’s parent directory must already exist, or
renameTo()will simply fail and returnfalse.
▼ Solution & Explanation
Explanation:
source.renameTo(destination): Moves the file to the new location and gives it the new name in a single call, since a path change is all a “move” really is at the file system level.- Different folder, different name: Because
destinationpoints toarchive/final_report.txtwhilesourcepointed toreports/draft.txt, this single call changes both the file’s directory and its file name at once. - Return value:
renameTo()returns abooleanrather than throwing an exception, so its success should always be checked explicitly. - Alternative:
Files.move(source.toPath(), destination.toPath())from the NIO API offers more reliable, platform-consistent behavior and throws a descriptive exception on failure instead of just returningfalse.
Exercise 13: Word Counter
Problem Statement: Read a text file and calculate the total number of characters, words, and lines it contains.
Purpose: This exercise combines line-by-line reading with basic string processing, showing how simple counters and split() can turn raw text into useful statistics.
Given Input: sample.txt containing two lines: "Java file handling exercises are fun to practice." and "This line was appended without erasing the rest."
Expected Output:
Lines: 2 Words: 16 Characters: 97
▼ Hint
- Keep three separate counters: one for lines, one for words, and one for characters.
- Each call to
readLine()represents one line, so increment the line counter once per iteration. - Use
String.split("\\s+")on each trimmed line to break it into words, and add the resulting array’s length to the word count. - Add each line’s
length()to the character count.
▼ Solution & Explanation
Explanation:
lineCount++: Increments once perreadLine()call, so it always matches the number of lines actually read from the file.line.trim().split("\\s+"): Splits the line on one or more whitespace characters after trimming leading and trailing spaces, so extra spaces between words do not produce empty entries.!line.trim().isEmpty(): Skips the word count for blank lines, since splitting an empty string would otherwise incorrectly count it as one word.charCount += line.length(): Adds each line’s character count, though this excludes the line terminator itself sincereadLine()strips it before returning the line.
Exercise 14: Word Finder
Problem Statement: Search for a specific keyword in a text file. Print the line numbers where the keyword appears.
Purpose: This exercise practices tracking a manual line counter alongside readLine(), since BufferedReader does not expose line numbers directly, and reinforces simple substring searching with contains().
Given Input: sample.txt containing "Java file handling exercises are fun to practice." and "This line was appended without erasing the rest.", searching for "file"
Expected Output: Found on line 1: Java file handling exercises are fun to practice.
▼ Hint
- Maintain your own counter variable, incrementing it once per line read, since
BufferedReaderhas no built-in line number tracking. - Use
String.contains()to check whether the current line includes the keyword. - Track whether any match was found at all, so you can print a fallback message if the keyword never appears.
▼ Solution & Explanation
Explanation:
lineNumber++: Increments before the check, so line numbers reported to the user start at 1 rather than 0.line.contains(keyword): Performs a simple, case-sensitive substring search on the current line.found = true: Records that at least one match occurred, so the “not found” message only prints when the keyword genuinely never appeared.- Alternative: Using
line.toLowerCase().contains(keyword.toLowerCase())instead would make the search case-insensitive.
Exercise 15: Search & Replace
Problem Statement: Read a file, replace all occurrences of a specific word (e.g., “Java” to “Python”), and save the changes back to the file.
Purpose: This exercise introduces the modern NIO Files utility class, which can read and write an entire file’s text content in a single call, ideal for a simple whole-file find-and-replace.
Given Input: sample.txt containing the word "Java" at least once
Expected Output: Replacement complete.
▼ Hint
- Use
Files.readString(path)to load the entire file’s content as oneString. - Call
String.replace()to substitute every occurrence of the target word. - Use
Files.writeString(path, updated)to overwrite the file with the modified content.
▼ Solution & Explanation
Explanation:
Files.readString(path): Reads the file’s entire contents into a singleStringin one call, without needing to manage aBufferedReaderloop.content.replace("Java", "Python"): Replaces every occurrence of the literal text"Java"with"Python"throughout the whole string.Files.writeString(path, updated): Overwrites the original file with the updated content, replacing what was there before.- Caution: Because this approach loads the whole file into memory at once, it works well for small to medium text files, but a very large file would need a line-by-line or streaming approach instead.
Exercise 16: Media Copier
Problem Statement: Copy a binary file (like a .jpg image or .mp3 audio file) to a new location ensuring the data isn’t corrupted.
Purpose: This exercise reinforces that byte streams, unlike character streams such as FileReader, work correctly with any file format because they never attempt to interpret the bytes as text.
Given Input: photo.jpg exists in the current directory
Expected Output: Media file copied successfully.
▼ Hint
- Never use
FileReaderorFileWriterfor binary files. They interpret bytes as characters using a text encoding, which can corrupt non-text data. - Wrap
FileInputStreamandFileOutputStreaminBufferedInputStreamandBufferedOutputStreamfor better performance on larger files. - The copy loop itself looks identical to the plain byte-stream copy from Exercise 11.
▼ Solution & Explanation
Explanation:
- Byte streams, not character streams:
FileInputStreamandFileOutputStreammove raw bytes exactly as they are, with no text encoding or decoding step that could alter binary data like image or audio bytes. BufferedInputStreamandBufferedOutputStream: Add an internal buffer around the raw streams, reducing the number of costly underlying I/O calls, especially noticeable on larger media files.byte[] buffer = new byte[4096]: A larger buffer size than the plain text copy example, which is a common choice for binary data to reduce the number of read and write cycles.- Alternative:
Files.copy(Path.of("photo.jpg"), Path.of("photo_copy.jpg"))handles binary files just as safely, since NIO’s file copy also operates purely on bytes.
Exercise 17: CSV Reader
Problem Statement: Read a .csv file containing user data (ID, Name, Email) and parse it into a list of custom User objects.
Purpose: This exercise combines file reading with basic text parsing and object construction, a pattern used constantly when importing structured data from external sources.
Given Input: users.csv containing:
ID,Name,Email 1,Alice,alice@example.com 2,Bob,bob@example.com
Expected Output:
User{id=1, name=Alice, email=alice@example.com}
User{id=2, name=Bob, email=bob@example.com}
▼ Hint
- Read and discard the first line separately, since it is the header row, not a data row.
- Use
String.split(",")to break each remaining line into its three fields. - Parse the ID field with
Integer.parseInt(), and construct a newUserobject from the three parsed values.
▼ Solution & Explanation
Explanation:
reader.readLine(); // skip header row: Consumes and discards the first line, since it contains column names rather than actual data.line.split(","): Splits each data row into an array of three fields, one for each comma-separated value.Integer.parseInt(fields[0].trim()): Converts the first field from text to anint, trimming any accidental whitespace around the value first.users.forEach(System.out::println): Prints every parsedUserobject using its overriddentoString(), confirming the file was parsed correctly.
Exercise 18: CSV Writer
Problem Statement: Take a list of Java objects and export their attributes into a structured .csv file.
Purpose: This exercise is the reverse of the previous one, showing how to serialize a list of objects back into a plain-text, comma-separated format that other tools like spreadsheets can open directly.
Given Input: A list containing new User(1, "Alice", "alice@example.com") and new User(2, "Bob", "bob@example.com")
Expected Output: CSV file written successfully.
▼ Hint
- Write the header row first, with column names separated by commas.
- Loop through the list of objects, writing one comma-separated line per object.
- Remember to add a newline character after each row, including the header.
▼ Solution & Explanation
Explanation:
writer.write("ID,Name,Email\n"): Writes the header row first, giving the resulting file column names that spreadsheet tools can recognize.user.id + "," + user.name + "," + user.email + "\n": Builds one comma-separated line perUser, matching the same field order as the header.- Field order consistency: The header and every data row must list fields in the same order, or the resulting CSV file would be misleading when opened elsewhere.
- Caution: If any
nameoremailvalue itself contained a comma, this simple approach would produce a malformed row. A proper CSV library handles quoting and escaping automatically for cases like that.
Exercise 19: The Zipper
Problem Statement: Compress a single text file into a .zip file format using ZipOutputStream.
Purpose: This exercise introduces ZipOutputStream from java.util.zip, showing how a compressed archive is built by writing regular file bytes into a specially wrapped output stream.
Given Input: sample.txt exists in the current directory
Expected Output: File compressed successfully into sample.zip
▼ Hint
- Wrap a
FileOutputStreampointing to the.zipfile in aZipOutputStream. - Create a
ZipEntryfor the file you want to add, and callputNextEntry()before writing its bytes. - Copy the source file’s bytes into the
ZipOutputStreamthe same way you would copy them into a plainFileOutputStream. - Call
closeEntry()once the file’s data has been fully written.
▼ Solution & Explanation
Explanation:
new ZipOutputStream(new FileOutputStream("sample.zip")): Wraps a regular file output stream so that anything written through it gets compressed and packaged into the ZIP format automatically.new ZipEntry("sample.txt"): Represents one file’s entry inside the archive, including the name it will have once extracted.zipOut.putNextEntry(entry): Opens that entry for writing, so all subsequentwrite()calls go into this specific file within the archive.zipOut.closeEntry(): Finalizes the current entry’s data, allowing another entry to be added afterward if the archive needed to hold multiple files.
Exercise 20: The Unzipper
Problem Statement: Extract the contents of a .zip archive into a target folder.
Purpose: This closing exercise mirrors the previous one in reverse, using ZipInputStream to read entries back out of an archive and reconstruct each one as a standalone file on disk.
Given Input: sample.zip containing a single entry, sample.txt
Expected Output: Extracted: extracted/sample.txt
▼ Hint
- Create the target folder first with
mkdirs(), in case it does not already exist. - Wrap a
FileInputStreampointing to the.zipfile in aZipInputStream. - Call
getNextEntry()in a loop. It returnsnullonce every entry in the archive has been processed. - For each entry, open a new
FileOutputStreamin the target folder and copy the entry’s bytes into it, just like a normal stream copy.
▼ Solution & Explanation
Explanation:
targetDir.mkdirs(): Ensures the destination folder exists before any file is written into it, sinceFileOutputStreamdoes not create missing parent directories on its own.zipIn.getNextEntry(): Advances to the next file stored in the archive, returningnullonce every entry has been processed, which ends the loop.new File(targetDir, entry.getName()): Builds the output path by combining the target folder with the entry’s stored name, recreating the original file inside the new location.zipIn.read(buffer): Reads the current entry’s decompressed bytes directly from theZipInputStream, the same way bytes would be read from any other input stream.
Exercise 21: The “Head” Command (First 10 Lines)
Problem Statement: Write a program that opens a large text file, reads it line-by-line, but stops and closes the stream immediately after printing the first 10 lines.
Purpose: This exercise practices exiting a read loop early based on a counter rather than waiting for the end of the file, which avoids reading more of a large file than necessary.
Given Input: large_log.txt containing 15 lines, "Line 1" through "Line 15"
Expected Output:
Line 1 Line 2 Line 3 Line 4 Line 5 Line 6 Line 7 Line 8 Line 9 Line 10
▼ Hint
- Keep a counter that increments once per line printed.
- Combine the counter check and the
readLine()call in the samewhilecondition, so the loop stops as soon as either 10 lines are printed or the file ends. - Use
try-with-resourcesso the stream closes automatically the moment the loop exits, without needing an explicitclose()call.
▼ Solution & Explanation
Explanation:
count < 10 && (line = reader.readLine()) != null: Combines both stopping conditions in one expression. Java’s short-circuit evaluation meansreadLine()is never even called oncecountreaches 10.count++: Tracks how many lines have been printed so far, giving the loop its exit condition.try (BufferedReader reader = ...): Closes the underlying file handle automatically once thetryblock ends, whether that happens after 10 lines or because the file itself had fewer lines.- Alternative: If the file had fewer than 10 lines, the loop would simply stop early when
readLine()returnsnull, without any special handling needed.
Exercise 22: Target Line Extractor (Specific Line)
Problem Statement: Create a program with a method like readSpecificLine(String filePath, int lineNumber). If the user inputs 5, the program should scan the file and print only the 5th line. Handle the case where the file has fewer lines than requested.
Purpose: This exercise reinforces manual line counting, and adds a boundary check for when the requested line number exceeds the number of lines actually available.
Given Input: sample.txt containing only 3 lines, requesting readSpecificLine("sample.txt", 5)
Expected Output: File has only 3 lines. Line 5 does not exist.
▼ Hint
- Keep a running line counter and compare it against
lineNumberafter everyreadLine()call. - As soon as the counter matches the target, print the line and
returnimmediately to avoid reading the rest of the file unnecessarily. - If the loop finishes without ever matching, the counter’s final value tells you exactly how many lines the file actually had.
▼ Solution & Explanation
Explanation:
currentLine == lineNumber: Checked after incrementing, so the comparison correctly targets 1-based line numbers as a user would expect.return;inside the loop: Exits the method immediately once the target line is found, skipping any remaining lines in the file.- Fallback message: Only reached if the loop completes without ever matching, at which point
currentLinenaturally holds the total number of lines the file contained. readSpecificLine("sample.txt", 5): Sincesample.txtonly has 3 lines, the loop finishes without a match, and the fallback message reports the actual line count.
Exercise 23: Selective Line Skipper
Problem Statement: Write a program that reads a file and prints every line except for blank lines or lines that start with a comment symbol (like # or //).
Purpose: This exercise practices filtering lines during a read loop using continue, a common pattern when parsing configuration files that mix real settings with comments and blank spacing.
Given Input: config.txt containing:
# Configuration file host=localhost // port setting port=8080
Expected Output:
host=localhost port=8080
▼ Hint
- Trim each line first, since a blank line might still contain stray whitespace.
- Check
isEmpty()on the trimmed line, andstartsWith("#")orstartsWith("//")for comment lines. - Use
continueto skip printing whenever any of those conditions match, without stopping the loop entirely.
▼ Solution & Explanation
Explanation:
line.trim(): Removes leading and trailing whitespace before checking the line’s content, so a line containing only spaces still counts as blank.trimmed.isEmpty() || trimmed.startsWith("#") || trimmed.startsWith("//"): Combines all three skip conditions into a single check.continue;: Immediately moves to the next iteration of the loop, skipping theprintln()call for that line without breaking out of the loop entirely.System.out.println(line): Prints the original, untrimmed line for anything that passed all three filters, preserving its original formatting.
Exercise 24: The Specific Line Updater
Problem Statement: Read a text file, locate exactly the 5th line, modify its text (e.g., change “Pending” to “Approved”), and save the file back without messing up any of the other lines.
Purpose: This exercise shows how loading a whole file into a mutable List<String> makes editing a single, specific line straightforward, since each line becomes independently addressable by index.
Given Input: status.txt whose 5th line contains the word "Pending"
Expected Output: Line 5 updated successfully.
▼ Hint
- Use
Files.readAllLines()to load every line into aList<String>at once. - Remember that list indices are zero-based, so the 5th line is at index
4. - Use
List.set()to replace just that one element, then write the whole list back withFiles.write().
▼ Solution & Explanation
Explanation:
Files.readAllLines(path): Loads the entire file into memory as a mutableList<String>, with one element per line.int targetLineIndex = 4: The 5th line sits at index 4, since list indices start counting from 0.lines.get(targetLineIndex).replace("Pending", "Approved"): Reads the original 5th line and produces a modified version with the target word replaced.lines.set(targetLineIndex, updated): Replaces only that single element in the list, leaving every other line completely untouched before the whole list is written back to disk.
Exercise 25: Read and Store (List Conversion)
Problem Statement: Read an entire text file line-by-line and store each line as an element in a List<String>, allowing you to easily access any line by its index later in the program.
Purpose: This exercise practices the common pattern of converting a stream-based read into an in-memory collection, which then supports random access instead of only sequential access.
Given Input: sample.txt containing "Java file handling exercises are fun to practice." and "This line was appended without erasing the rest."
Expected Output:
Total lines stored: 2 Line 2: This line was appended without erasing the rest.
▼ Hint
- Create an empty
ArrayList<String>before the read loop begins. - Call
add()on the list once per line read. - Once reading is complete, use
size()andget()to inspect the stored lines.
▼ Solution & Explanation
Explanation:
List<String> lines = new ArrayList<>(): Creates an empty, resizable list to hold every line as it is read.lines.add(line): Appends each line to the end of the list in the order it was read from the file.lines.size(): Reports the total number of lines stored, available instantly since the whole file has already been read into memory.lines.get(1): Retrieves the second line directly by its zero-based index, something a plainBufferedReaderloop alone could not do without re-reading the file.
Exercise 26: The Reverser (Bottom-to-Top Reader)
Problem Statement: Write a program that reads a text file and prints its lines in reverse order (the last line of the file should be printed first, and the first line printed last). Hint: Think about data structures that follow Last-In, First-Out (LIFO).
Purpose: This exercise connects file reading with a classic data structure choice: a Stack naturally reverses order because the last item pushed is always the first one popped.
Given Input: sample.txt containing "Line one.", "Line two.", and "Line three."
Expected Output:
Line three. Line two. Line one.
▼ Hint
- Read the file normally, from top to bottom, but instead of printing immediately,
push()each line onto aStack<String>. - Once the whole file has been read,
pop()lines off the stack in a loop until it is empty. - Since a stack is LIFO, the last line pushed, which was the last line in the file, becomes the first one popped.
▼ Solution & Explanation
Explanation:
lines.push(line): Adds each line to the top of the stack as it is read, so the last line read ends up on top.while (!lines.isEmpty()): Continues popping until every line has been removed from the stack.lines.pop(): Removes and returns the top element, which is always the most recently pushed line, naturally producing reverse order.- Alternative: Reading all lines into a
List<String>and then looping backward with a decrementing index would achieve the same result without using aStackexplicitly.
Exercise 27: The Interleaver (File Merger)
Problem Statement: Create a program that takes two different text files (e.g., fileA.txt and fileB.txt) and merges them into a third file (merged.txt) by alternating lines: Line 1 from File A, Line 1 from File B, Line 2 from File A, Line 2 from File B, and so on.
Purpose: This exercise practices reading from two input streams at the same time, and handles the edge case where one file runs out of lines before the other.
Given Input: fileA.txt containing "A1", "A2" and fileB.txt containing "B1", "B2"
Expected Output: Files merged successfully into merged.txt, with merged.txt containing:
A1 B1 A2 B2
▼ Hint
- Open both input files and the output file together, and read the first line from each source before the loop starts.
- Loop as long as at least one of the two lines is not
null, since the files might have different lengths. - Inside the loop, write and advance each source independently, only if that particular source still has a line available.
▼ Solution & Explanation
Explanation:
String lineA = readerA.readLine();before the loop: Primes both readers with their first line so the loop’s condition has something meaningful to check right away.while (lineA != null || lineB != null): Keeps merging as long as either file still has lines remaining, correctly handling files of different lengths.if (lineA != null) { ...; lineA = readerA.readLine(); }: Writes and advances each source independently, so a shorter file simply stops contributing once it runs out, without breaking the loop for the other file.- Three resources in one
try-with-resources: All three streams, both readers and the writer, are declared together and each gets closed automatically once the block finishes.
Exercise 28: Tokenizer (Word-by-Word Reader)
Problem Statement: Instead of reading a file line-by-line, write a program using java.util.Scanner or String.split() to read a file word-by-word. Print each individual word on its own new line in the console, stripping out punctuation marks.
Purpose: This exercise introduces Scanner as an alternative to BufferedReader, configured with a custom delimiter so it naturally splits on both whitespace and punctuation instead of full lines.
Given Input: sample.txt containing "Java file handling exercises are fun to practice."
Expected Output:
Java file handling exercises are fun to practice
▼ Hint
- Construct a
Scannerdirectly from aFileobject, which throws a checkedFileNotFoundExceptionthat must be caught. - Call
useDelimiter()with a regular expression that matches both whitespace and punctuation, such as"[\\s\\p{Punct}]+". - Loop with
hasNext()andnext(), skipping any empty tokens the delimiter might produce.
▼ Solution & Explanation
Explanation:
new Scanner(new File("sample.txt")): Creates a token-based reader over the file’s contents, distinct from the line-basedBufferedReaderused in earlier exercises.scanner.useDelimiter("[\\s\\p{Punct}]+"): Redefines what separates one token from the next.\\smatches whitespace and\\p{Punct}matches punctuation characters, so both are treated as boundaries between words.scanner.hasNext()andscanner.next(): Retrieve one token at a time using the custom delimiter, instead of the default whitespace-only splitting.!word.isEmpty(): Guards against occasional empty tokens that can appear at the very start or end of the stream when the delimiter pattern matches consecutive separators.
Exercise 29: Character Frequency Counter
Problem Statement: Write a program that prompts the user for a specific character (like ‘e’ or ‘7’) and then scans an entire text file to count and display exactly how many times that specific character appears.
Purpose: This exercise combines reading a full file’s text with simple character-by-character iteration, a technique useful for validation, statistics, or basic text analysis tasks.
Given Input: sample.txt containing "Java file handling exercises are fun to practice.", target character 'e'
Expected Output: The character 'e' appears 6 times.
▼ Hint
- Read the entire file into one
StringusingFiles.readString(), rather than processing it line by line. - Convert the string into a
char[]withtoCharArray()so you can compare each character individually. - Increment a counter each time a character equals the target character.
▼ Solution & Explanation
Explanation:
Files.readString(Path.of("sample.txt")): Loads the entire file as a singleString, which is simple and sufficient for character-level scanning of reasonably sized files.content.toCharArray(): Converts the string into an array of individualcharvalues, so each character can be compared one at a time.if (c == target): A simple, case-sensitive equality check against the target character, incrementingcounton every match.- Alternative: The same result can be produced more concisely using a stream:
content.chars().filter(c -> c == target).count().
Exercise 30: The Log Filter (Conditional Extraction)
Problem Statement: Imagine a standard server log file where each line starts with a log level: INFO, WARN, or ERROR. Write a program that reads this file and exports only the ERROR lines into a brand new standalone file called critical_errors.log.
Purpose: This exercise combines reading and writing in the same pass, filtering lines by a prefix condition and streaming only the matches into a separate output file.
Given Input: server.log containing:
INFO Server started WARN Disk space low ERROR Database connection failed INFO Request processed ERROR Timeout occurred
Expected Output: 2 error line(s) exported to critical_errors.log
▼ Hint
- Open the source log for reading and the destination file for writing at the same time.
- Use
startsWith("ERROR")to check each line as it is read. - Only call
write()on the matching lines, and keep a counter to report how many were exported.
▼ Solution & Explanation
Explanation:
- Two resources opened together: The source reader and destination writer are both declared in the same
try-with-resourcesstatement, so both close automatically once processing finishes. line.startsWith("ERROR"): Filters for only the lines that begin with the target log level, ignoringINFOandWARNlines entirely.errorCount++: Tracks how many lines matched, giving a useful summary once the whole file has been processed.- Result: Only the two lines beginning with
ERRORget written tocritical_errors.log, whileserver.logitself remains completely unchanged.
Exercise 31: File Splitter
Problem Statement: Take a text file and split it into multiple smaller chunks (e.g., exactly 5 lines per file).
Purpose: This exercise practices dividing one large file into several smaller, numbered output files, a common preprocessing step for batch jobs or size-limited uploads.
Given Input: large_log.txt containing 12 lines
Expected Output: File split into 3 part(s).
▼ Hint
- Load all the lines into a
List<String>first, so they can be sliced into fixed-size chunks easily. - Loop over the list in steps of 5, using
subList()to grab each chunk. - Open a new numbered output file for each chunk, and use
Math.min()to avoid going past the end of the list on the final, possibly shorter chunk.
▼ Solution & Explanation
Explanation:
for (int i = 0; i < lines.size(); i += linesPerFile): Steps through the list 5 elements at a time, with each iteration representing the start of one output chunk.Math.min(i + linesPerFile, lines.size()): Prevents the final chunk from reading past the end of the list when the total line count is not an exact multiple of 5.lines.subList(i, end): Extracts just the current chunk’s lines as a view into the original list, without copying the whole list each time."part_" + fileCount + ".txt": Names each output file sequentially, so 12 lines at 5 lines per file producepart_1.txtandpart_2.txtwith 5 lines each, andpart_3.txtwith the remaining 2.
Exercise 32: Giant Finder
Problem Statement: Recursively scan a directory tree and find the largest file within its subfolders.
Purpose: This closing exercise combines directory listing with recursion, since a directory can contain other directories, requiring the search logic to call itself to reach every file at every depth.
Given Input: projects/ containing nested subfolders with files of varying sizes
Expected Output (actual values depend on your files):
Largest file: projects/java/exercises/data_dump.csv (204800 bytes)
▼ Hint
- Write a recursive method that takes a directory
Fileand callslistFiles()on it. - For each entry, if it is itself a directory, call the method again on that entry. If it is a file, compare its size against the current largest found so far.
- Keep the current largest file in a field or variable that persists across every recursive call, rather than resetting it each time.
▼ Solution & Explanation
Explanation:
private static File largestFile = null: A field shared across every recursive call, so progress tracking the largest file found so far persists no matter how deep the recursion goes.if (entry.isDirectory()) { findLargestFile(entry); }: The recursive step. Whenever a subdirectory is found, the method calls itself on that subdirectory to continue the search deeper into the tree.entry.length() > largestFile.length(): Compares each file’s size against the current record holder, updatinglargestFilewhenever a bigger one is found.entries == nullcheck: Guards against inaccessible or invalid directories partway through the recursion, preventing aNullPointerExceptionfrom a failedlistFiles()call.

Leave a Reply