PYnative

Python Programming

  • Learn Python ▼
    • Python Tutorials
    • Python Basics
    • Python Interview Q&As
  • Exercises ▼
    • Python Exercises
    • C++ Exercises
    • C Programming Exercises
    • Java Exercises
  • Quizzes
  • Code Editor ▼
    • Online Python Code Editor
    • Online C Compiler
    • Online C++ Compiler
    • Online Java Compiler
Home » Java Exercises » Java File Handling Exercises: 30+ Coding Problems with Solutions

Java File Handling Exercises: 30+ Coding Problems with Solutions

Updated on: July 6, 2026 | Leave a Comment

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 File class.
  • Reading & Writing: FileWriter, BufferedReader, byte streams for binary data, and the modern NIO Files utility 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 File object 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
import java.io.File;

public class Main {
    public static void main(String[] args) {
        File file = new File("sample.txt");

        if (file.exists()) {
            System.out.println(file.getName() + " exists.");
        } else {
            System.out.println(file.getName() + " does not exist.");
        }
    }
}Code language: Java (java)

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, returning true only 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 File and exists() 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 a boolean instead of throwing when the file already exists.
  • Wrap the call in a try-catch block for IOException, since it is still a checked exception that can occur for other reasons like invalid paths.
  • Check the returned boolean to distinguish between “created successfully” and “already existed”.
▼ Solution & Explanation
import java.io.File;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        File file = new File("sample.txt");

        try {
            boolean created = file.createNewFile();
            if (created) {
                System.out.println("File created: " + file.getName());
            } else {
                System.out.println("File already exists: " + file.getName());
            }
        } catch (IOException e) {
            System.out.println("Error creating file: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • file.createNewFile(): Attempts to create a new, empty file at the given path, returning true if it succeeded and false if 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.txt already exists, would print "File already exists: sample.txt" instead, since createNewFile() returns false rather 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 than mkdir(). Only mkdirs() creates any missing parent directories along the way.
  • mkdirs() returns a boolean indicating whether at least the final directory was actually created.
  • Use getPath() to print the relative path that was created.
▼ Solution & Explanation
import java.io.File;

public class Main {
    public static void main(String[] args) {
        File directories = new File("projects/java/exercises");
        boolean created = directories.mkdirs();

        if (created) {
            System.out.println("Directory structure created: " + directories.getPath());
        } else {
            System.out.println("Directory structure already exists or could not be created.");
        }
    }
}Code language: Java (java)

Explanation:

  • directories.mkdirs(): Creates every missing directory in the path in one call, including projects, projects/java, and projects/java/exercises.
  • Difference from mkdir(): mkdir() only creates the final directory and fails if any parent directory in the path does not already exist, while mkdirs() builds the whole chain.
  • directories.getPath(): Returns the path exactly as it was passed into the File constructor, 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() returns false when 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 a File that points to a directory. It returns a File[] array.
  • listFiles() returns null instead of an empty array if the path is not a valid, accessible directory, so check for null first.
  • Loop through the array and print each entry’s name with getName().
▼ Solution & Explanation
import java.io.File;

public class Main {
    public static void main(String[] args) {
        File folder = new File("projects");
        File[] contents = folder.listFiles();

        if (contents != null) {
            for (File item : contents) {
                System.out.println(item.getName());
            }
        } else {
            System.out.println("Directory does not exist or is not accessible.");
        }
    }
}Code language: Java (java)

Explanation:

  • folder.listFiles(): Returns an array of File objects representing every entry directly inside projects, including both files and subdirectories.
  • contents != null: Guards against the case where folder does not exist, is not a directory, or an I/O error occurs, since listFiles() returns null rather than throwing in those situations.
  • item.getName(): Prints just the entry’s own name, whether it happens to be a file or a folder, since File does 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 a FilenameFilter.
  • A lambda expression can implement FilenameFilter directly: (dir, name) -> ....
  • Use String.endsWith() to check for either extension inside the lambda.
▼ Solution & Explanation
import java.io.File;

public class Main {
    public static void main(String[] args) {
        File folder = new File("projects");
        File[] files = folder.listFiles((dir, name) -> name.endsWith(".txt") || name.endsWith(".java"));

        if (files != null) {
            for (File file : files) {
                System.out.println(file.getName());
            }
        }
    }
}Code language: Java (java)

Explanation:

  • listFiles((dir, name) -> ...): Passes a lambda implementing FilenameFilter, which the method calls once for every entry in the directory to decide whether to include it.
  • name.endsWith(".txt") || name.endsWith(".java"): Returns true only 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 FilenameFilter class, or applied after the fact with a stream and Files.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 its boolean return 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 a String[], and check that its length is 0 before calling delete() on it.
▼ Solution & Explanation
import java.io.File;

public class Main {
    public static void main(String[] args) {
        File file = new File("sample.txt");
        if (file.delete()) {
            System.out.println("Deleted file: " + file.getName());
        } else {
            System.out.println("Failed to delete file: " + file.getName());
        }

        File directory = new File("projects/java/exercises");
        if (directory.isDirectory() && directory.list().length == 0) {
            if (directory.delete()) {
                System.out.println("Deleted empty directory: " + directory.getPath());
            }
        } else {
            System.out.println("Directory is not empty or does not exist: " + directory.getPath());
        }
    }
}Code language: Java (java)

Explanation:

  • file.delete(): Removes the file from disk and returns true on success, or false if 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. Calling delete() on a non-empty directory would simply fail silently and return false, 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, and canRead() / canWrite() for permissions.
  • lastModified() returns a long timestamp in milliseconds, not a readable date directly.
  • Wrap that timestamp in a new Date(...) object to get a human-readable representation.
▼ Solution & Explanation
import java.io.File;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        File file = new File("sample.txt");

        if (file.exists()) {
            System.out.println("Size: " + file.length() + " bytes");
            System.out.println("Absolute path: " + file.getAbsolutePath());
            System.out.println("Readable: " + file.canRead());
            System.out.println("Writable: " + file.canWrite());
            System.out.println("Last modified: " + new Date(file.lastModified()));
        } else {
            System.out.println("File does not exist.");
        }
    }
}Code language: Java (java)

Explanation:

  • file.length(): Returns the file’s size in bytes as a long, 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() and file.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 by lastModified() 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 FileWriter inside a try-with-resources statement so it always closes, even if writing fails partway through.
  • By default, FileWriter overwrites the file’s existing content if the file already exists.
  • Call write() with the sentence you want to save, and catch IOException around the whole block.
▼ Solution & Explanation
import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (FileWriter writer = new FileWriter("sample.txt")) {
            writer.write("Java file handling exercises are fun to practice.");
            System.out.println("Content written successfully.");
        } catch (IOException e) {
            System.out.println("Error writing to file: " + e.getMessage());
        }
    }
}Code language: Java (java)

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 = ...): Since FileWriter implements AutoCloseable, 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 explicit try-with-resources block.

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 FileReader inside a BufferedReader to get access to the convenient readLine() method.
  • readLine() returns null once the end of the file is reached, which makes a good loop condition.
  • Use try-with-resources so the reader closes automatically once the loop finishes.
▼ Solution & Explanation
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • new BufferedReader(new FileReader("sample.txt")): Wraps the basic FileReader in a BufferedReader, which adds efficient line-by-line reading through readLine().
  • while ((line = reader.readLine()) != null): Reads one line at a time, assigning it to line and looping until readLine() signals the end of the file by returning null.
  • System.out.println(line): Prints each line exactly as read, without the line terminator, since readLine() strips it automatically.
  • Alternative: Files.readAllLines(Path.of("sample.txt")) from the NIO API reads the whole file into a List<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 FileWriter constructor overload that accepts a second boolean parameter 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 true flag, FileWriter would erase the file’s existing content before writing.
▼ Solution & Explanation
import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (FileWriter writer = new FileWriter("sample.txt", true)) {
            writer.write("\nThis line was appended without erasing the rest.");
            System.out.println("Content appended successfully.");
        } catch (IOException e) {
            System.out.println("Error appending to file: " + e.getMessage());
        }
    }
}Code language: Java (java)

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 \n moves the new text onto its own line, since write() does not insert line breaks automatically.
  • Default behavior without true: Omitting the second argument would cause FileWriter to 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 plain boolean flag.

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 FileInputStream for reading and a FileOutputStream for writing inside the same try-with-resources statement.
  • Read into a byte[] buffer in a loop, since large files cannot be read in one call.
  • read() returns -1 once 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
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (FileInputStream input = new FileInputStream("source.txt");
             FileOutputStream output = new FileOutputStream("destination.txt")) {

            byte[] buffer = new byte[1024];
            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("Error copying file: " + e.getMessage());
        }
    }
}Code language: Java (java)

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 -1 once 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 File objects, 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 return false.
▼ Solution & Explanation
import java.io.File;

public class Main {
    public static void main(String[] args) {
        File source = new File("reports/draft.txt");
        File destination = new File("archive/final_report.txt");

        boolean moved = source.renameTo(destination);
        if (moved) {
            System.out.println("File moved and renamed to: " + destination.getPath());
        } else {
            System.out.println("Failed to move the file.");
        }
    }
}Code language: Java (java)

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 destination points to archive/final_report.txt while source pointed to reports/draft.txt, this single call changes both the file’s directory and its file name at once.
  • Return value: renameTo() returns a boolean rather 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 returning false.

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
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        int lineCount = 0;
        int wordCount = 0;
        int charCount = 0;

        try (BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                lineCount++;
                charCount += line.length();
                if (!line.trim().isEmpty()) {
                    wordCount += line.trim().split("\\s+").length;
                }
            }
            System.out.println("Lines: " + lineCount);
            System.out.println("Words: " + wordCount);
            System.out.println("Characters: " + charCount);
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • lineCount++: Increments once per readLine() 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 since readLine() 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 BufferedReader has 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
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        String keyword = "file";
        int lineNumber = 0;
        boolean found = false;

        try (BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                lineNumber++;
                if (line.contains(keyword)) {
                    System.out.println("Found on line " + lineNumber + ": " + line);
                    found = true;
                }
            }
            if (!found) {
                System.out.println("Keyword not found.");
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }
}Code language: Java (java)

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 one String.
  • 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
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        Path path = Path.of("sample.txt");

        try {
            String content = Files.readString(path);
            String updated = content.replace("Java", "Python");
            Files.writeString(path, updated);
            System.out.println("Replacement complete.");
        } catch (IOException e) {
            System.out.println("Error processing file: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • Files.readString(path): Reads the file’s entire contents into a single String in one call, without needing to manage a BufferedReader loop.
  • 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 FileReader or FileWriter for binary files. They interpret bytes as characters using a text encoding, which can corrupt non-text data.
  • Wrap FileInputStream and FileOutputStream in BufferedInputStream and BufferedOutputStream for better performance on larger files.
  • The copy loop itself looks identical to the plain byte-stream copy from Exercise 11.
▼ Solution & Explanation
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (BufferedInputStream input = new BufferedInputStream(new FileInputStream("photo.jpg"));
             BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream("photo_copy.jpg"))) {

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = input.read(buffer)) != -1) {
                output.write(buffer, 0, bytesRead);
            }
            System.out.println("Media file copied successfully.");
        } catch (IOException e) {
            System.out.println("Error copying media file: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • Byte streams, not character streams: FileInputStream and FileOutputStream move raw bytes exactly as they are, with no text encoding or decoding step that could alter binary data like image or audio bytes.
  • BufferedInputStream and BufferedOutputStream: 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 new User object from the three parsed values.
▼ Solution & Explanation
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

class User {
    int id;
    String name;
    String email;

    public User(int id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{id=" + id + ", name=" + name + ", email=" + email + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        List<User> users = new ArrayList<>();

        try (BufferedReader reader = new BufferedReader(new FileReader("users.csv"))) {
            String line;
            reader.readLine(); // skip header row
            while ((line = reader.readLine()) != null) {
                String[] fields = line.split(",");
                int id = Integer.parseInt(fields[0].trim());
                String name = fields[1].trim();
                String email = fields[2].trim();
                users.add(new User(id, name, email));
            }
            users.forEach(System.out::println);
        } catch (IOException e) {
            System.out.println("Error reading CSV file: " + e.getMessage());
        }
    }
}Code language: Java (java)

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 an int, trimming any accidental whitespace around the value first.
  • users.forEach(System.out::println): Prints every parsed User object using its overridden toString(), 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
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

class User {
    int id;
    String name;
    String email;

    public User(int id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
}

public class Main {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User(1, "Alice", "alice@example.com"),
            new User(2, "Bob", "bob@example.com")
        );

        try (FileWriter writer = new FileWriter("users_export.csv")) {
            writer.write("ID,Name,Email\n");
            for (User user : users) {
                writer.write(user.id + "," + user.name + "," + user.email + "\n");
            }
            System.out.println("CSV file written successfully.");
        } catch (IOException e) {
            System.out.println("Error writing CSV file: " + e.getMessage());
        }
    }
}Code language: Java (java)

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 per User, 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 name or email value 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 FileOutputStream pointing to the .zip file in a ZipOutputStream.
  • Create a ZipEntry for the file you want to add, and call putNextEntry() before writing its bytes.
  • Copy the source file’s bytes into the ZipOutputStream the same way you would copy them into a plain FileOutputStream.
  • Call closeEntry() once the file’s data has been fully written.
▼ Solution & Explanation
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class Main {
    public static void main(String[] args) {
        try (FileInputStream input = new FileInputStream("sample.txt");
             ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream("sample.zip"))) {

            ZipEntry entry = new ZipEntry("sample.txt");
            zipOut.putNextEntry(entry);

            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = input.read(buffer)) != -1) {
                zipOut.write(buffer, 0, bytesRead);
            }
            zipOut.closeEntry();
            System.out.println("File compressed successfully into sample.zip");
        } catch (IOException e) {
            System.out.println("Error compressing file: " + e.getMessage());
        }
    }
}Code language: Java (java)

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 subsequent write() 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 FileInputStream pointing to the .zip file in a ZipInputStream.
  • Call getNextEntry() in a loop. It returns null once every entry in the archive has been processed.
  • For each entry, open a new FileOutputStream in the target folder and copy the entry’s bytes into it, just like a normal stream copy.
▼ Solution & Explanation
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class Main {
    public static void main(String[] args) {
        File targetDir = new File("extracted");
        targetDir.mkdirs();

        try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream("sample.zip"))) {
            ZipEntry entry;
            while ((entry = zipIn.getNextEntry()) != null) {
                File outFile = new File(targetDir, entry.getName());

                try (FileOutputStream output = new FileOutputStream(outFile)) {
                    byte[] buffer = new byte[1024];
                    int bytesRead;
                    while ((bytesRead = zipIn.read(buffer)) != -1) {
                        output.write(buffer, 0, bytesRead);
                    }
                }
                System.out.println("Extracted: " + outFile.getPath());
                zipIn.closeEntry();
            }
        } catch (IOException e) {
            System.out.println("Error extracting zip file: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • targetDir.mkdirs(): Ensures the destination folder exists before any file is written into it, since FileOutputStream does not create missing parent directories on its own.
  • zipIn.getNextEntry(): Advances to the next file stored in the archive, returning null once 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 the ZipInputStream, 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 same while condition, so the loop stops as soon as either 10 lines are printed or the file ends.
  • Use try-with-resources so the stream closes automatically the moment the loop exits, without needing an explicit close() call.
▼ Solution & Explanation
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("large_log.txt"))) {
            String line;
            int count = 0;
            while (count < 10 && (line = reader.readLine()) != null) {
                System.out.println(line);
                count++;
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • count < 10 && (line = reader.readLine()) != null: Combines both stopping conditions in one expression. Java’s short-circuit evaluation means readLine() is never even called once count reaches 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 the try block 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() returns null, 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 lineNumber after every readLine() call.
  • As soon as the counter matches the target, print the line and return immediately 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
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void readSpecificLine(String filePath, int lineNumber) {
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            int currentLine = 0;
            while ((line = reader.readLine()) != null) {
                currentLine++;
                if (currentLine == lineNumber) {
                    System.out.println("Line " + lineNumber + ": " + line);
                    return;
                }
            }
            System.out.println("File has only " + currentLine + " lines. Line " + lineNumber + " does not exist.");
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        readSpecificLine("sample.txt", 5);
    }
}Code language: Java (java)

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 currentLine naturally holds the total number of lines the file contained.
  • readSpecificLine("sample.txt", 5): Since sample.txt only 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, and startsWith("#") or startsWith("//") for comment lines.
  • Use continue to skip printing whenever any of those conditions match, without stopping the loop entirely.
▼ Solution & Explanation
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("config.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                String trimmed = line.trim();
                if (trimmed.isEmpty() || trimmed.startsWith("#") || trimmed.startsWith("//")) {
                    continue;
                }
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }
}Code language: Java (java)

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 the println() 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 a List<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 with Files.write().
▼ Solution & Explanation
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("status.txt");
        List<String> lines = Files.readAllLines(path);

        int targetLineIndex = 4; // 5th line, zero-based index
        if (targetLineIndex < lines.size()) {
            String updated = lines.get(targetLineIndex).replace("Pending", "Approved");
            lines.set(targetLineIndex, updated);
            Files.write(path, lines);
            System.out.println("Line 5 updated successfully.");
        } else {
            System.out.println("File does not have a 5th line.");
        }
    }
}Code language: Java (java)

Explanation:

  • Files.readAllLines(path): Loads the entire file into memory as a mutable List<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() and get() to inspect the stored lines.
▼ Solution & Explanation
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> lines = new ArrayList<>();

        try (BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                lines.add(line);
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }

        System.out.println("Total lines stored: " + lines.size());
        System.out.println("Line 2: " + lines.get(1));
    }
}Code language: Java (java)

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 plain BufferedReader loop 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 a Stack<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
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Stack;

public class Main {
    public static void main(String[] args) {
        Stack<String> lines = new Stack<>();

        try (BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                lines.push(line);
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }

        while (!lines.isEmpty()) {
            System.out.println(lines.pop());
        }
    }
}Code language: Java (java)

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 a Stack explicitly.

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
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader readerA = new BufferedReader(new FileReader("fileA.txt"));
             BufferedReader readerB = new BufferedReader(new FileReader("fileB.txt"));
             FileWriter writer = new FileWriter("merged.txt")) {

            String lineA = readerA.readLine();
            String lineB = readerB.readLine();

            while (lineA != null || lineB != null) {
                if (lineA != null) {
                    writer.write(lineA + "\n");
                    lineA = readerA.readLine();
                }
                if (lineB != null) {
                    writer.write(lineB + "\n");
                    lineB = readerB.readLine();
                }
            }

            System.out.println("Files merged successfully into merged.txt");
        } catch (IOException e) {
            System.out.println("Error merging files: " + e.getMessage());
        }
    }
}Code language: Java (java)

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 Scanner directly from a File object, which throws a checked FileNotFoundException that must be caught.
  • Call useDelimiter() with a regular expression that matches both whitespace and punctuation, such as "[\\s\\p{Punct}]+".
  • Loop with hasNext() and next(), skipping any empty tokens the delimiter might produce.
▼ Solution & Explanation
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner scanner = new Scanner(new File("sample.txt"))) {
            scanner.useDelimiter("[\\s\\p{Punct}]+");
            while (scanner.hasNext()) {
                String word = scanner.next();
                if (!word.isEmpty()) {
                    System.out.println(word);
                }
            }
        } catch (FileNotFoundException e) {
            System.out.println("Error: File not found.");
        }
    }
}Code language: Java (java)

Explanation:

  • new Scanner(new File("sample.txt")): Creates a token-based reader over the file’s contents, distinct from the line-based BufferedReader used in earlier exercises.
  • scanner.useDelimiter("[\\s\\p{Punct}]+"): Redefines what separates one token from the next. \\s matches whitespace and \\p{Punct} matches punctuation characters, so both are treated as boundaries between words.
  • scanner.hasNext() and scanner.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 String using Files.readString(), rather than processing it line by line.
  • Convert the string into a char[] with toCharArray() so you can compare each character individually.
  • Increment a counter each time a character equals the target character.
▼ Solution & Explanation
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class Main {
    public static void main(String[] args) throws IOException {
        char target = 'e';
        String content = Files.readString(Path.of("sample.txt"));

        int count = 0;
        for (char c : content.toCharArray()) {
            if (c == target) {
                count++;
            }
        }
        System.out.println("The character '" + target + "' appears " + count + " times.");
    }
}Code language: Java (java)

Explanation:

  • Files.readString(Path.of("sample.txt")): Loads the entire file as a single String, which is simple and sufficient for character-level scanning of reasonably sized files.
  • content.toCharArray(): Converts the string into an array of individual char values, so each character can be compared one at a time.
  • if (c == target): A simple, case-sensitive equality check against the target character, incrementing count on 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
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("server.log"));
             FileWriter writer = new FileWriter("critical_errors.log")) {

            String line;
            int errorCount = 0;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("ERROR")) {
                    writer.write(line + "\n");
                    errorCount++;
                }
            }
            System.out.println(errorCount + " error line(s) exported to critical_errors.log");
        } catch (IOException e) {
            System.out.println("Error processing log file: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • Two resources opened together: The source reader and destination writer are both declared in the same try-with-resources statement, so both close automatically once processing finishes.
  • line.startsWith("ERROR"): Filters for only the lines that begin with the target log level, ignoring INFO and WARN lines 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 ERROR get written to critical_errors.log, while server.log itself 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
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        List<String> lines = Files.readAllLines(Path.of("large_log.txt"));
        int linesPerFile = 5;
        int fileCount = 0;

        for (int i = 0; i < lines.size(); i += linesPerFile) {
            fileCount++;
            int end = Math.min(i + linesPerFile, lines.size());
            List<String> chunk = lines.subList(i, end);

            try (FileWriter writer = new FileWriter("part_" + fileCount + ".txt")) {
                for (String line : chunk) {
                    writer.write(line + "\n");
                }
            }
        }
        System.out.println("File split into " + fileCount + " part(s).");
    }
}Code language: Java (java)

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 produce part_1.txt and part_2.txt with 5 lines each, and part_3.txt with 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 File and calls listFiles() 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
import java.io.File;

public class Main {
    private static File largestFile = null;

    public static void findLargestFile(File directory) {
        File[] entries = directory.listFiles();
        if (entries == null) {
            return;
        }

        for (File entry : entries) {
            if (entry.isDirectory()) {
                findLargestFile(entry);
            } else {
                if (largestFile == null || entry.length() > largestFile.length()) {
                    largestFile = entry;
                }
            }
        }
    }

    public static void main(String[] args) {
        findLargestFile(new File("projects"));

        if (largestFile != null) {
            System.out.println("Largest file: " + largestFile.getPath() + " (" + largestFile.length() + " bytes)");
        } else {
            System.out.println("No files found.");
        }
    }
}Code language: Java (java)

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, updating largestFile whenever a bigger one is found.
  • entries == null check: Guards against inaccessible or invalid directories partway through the recursion, preventing a NullPointerException from a failed listFiles() call.

Filed Under: Java Exercises

Did you find this page helpful? Let others know about it. Sharing helps me continue to create free Python resources.

TweetF  sharein  shareP  Pin

About Vishal

I’m Vishal Hule, the Founder of PYnative.com. As a Python developer, I enjoy assisting students, developers, and learners. Follow me on Twitter.

Related Tutorial Topics:

Java Exercises

All Coding Exercises:

C Exercises
C++ Exercises
Python Exercises
Java Exercises

Python Exercises and Quizzes

Free coding exercises and quizzes cover Python basics, data structure, data analytics, and more.

  • 15+ Topic-specific Exercises and Quizzes
  • Each Exercise contains 25+ questions
  • Each Quiz contains 25 MCQ
Exercises
Quizzes

Leave a Reply Cancel reply

your email address will NOT be published. all comments are moderated according to our comment policy.

Use <pre> tag for posting code. E.g. <pre> Your entire code </pre>

In: Java Exercises
TweetF  sharein  shareP  Pin

  Java Exercises

  • All Java Exercises
  • Java Exercise for Beginners
  • Java Loops Exercise
  • Java String Exercise
  • Java ArrayList Exercise
  • Java LinkedList Exercise
  • Java HashMap and TreeMap Exercise
  • Java HashSet and TreeSet Exercise
  • Java OOP Exercise
  • Java Methods Exercise
  • Java Enums Exercise
  • Java Exception Handling Exercise
  • Java File Handling Exercise
  • Java Date and Time Exercise
  • Java Data Structures Exercise
  • Java Sorting and Searching Exercise
  • Java Lambda and Functional Interfaces Exercise
  • Java Regex Exercise
  • Java Random Data Generation Exercise
  • Java Generics Exercise
  • Java Reflection Exercise
  • Java JDBC Exercise

All Coding Exercises

Python Exercises C Exercises C++ Exercises Java Exercises

About PYnative

PYnative.com is for Python lovers. Here, You can get Tutorials, Exercises, and Quizzes to practice and improve your Python skills.

Follow Us

To get New Python Tutorials, Exercises, and Quizzes

  • Twitter
  • Facebook
  • Sitemap

Explore Python

  • Learn Python
  • Python Basics
  • Python Databases
  • Python Exercises
  • Python Quizzes
  • Online Python Code Editor
  • Python Tricks

Coding Exercises

  • C Exercises
  • C++ Exercises
  • Python Exercises
  • Java Exercises

Legal Stuff

  • About Us
  • Contact Us

We use cookies to improve your experience. While using PYnative, you agree to have read and accepted our:

  • Terms Of Use
  • Privacy Policy
  • Cookie Policy

Copyright © 2018–2026 pynative.com