Project: A File-Based Notes Tool
Project: A File-Based Notes Tool
Every concept taught across this tutorial — Path, Files, buffered streams, try-with-resources, and character encoding — converges in this capstone project. You will build a small but complete command-line notes tool that persists notes to a plain-text file, supports listing, appending, deleting by index, and searching. The goal is not just to make it work, but to make deliberate design choices and understand the trade-offs behind each one.
What the tool does
- add — append a new note to the file.
- list — print all notes with their 1-based index.
- delete <index> — remove the note at the given index.
- search <term> — print every note containing the search term (case-insensitive).
Notes are stored one per line in ~/.notes/notes.txt. That single file is the entire "database".
Project skeleton
Keep things in one file for brevity; in a real project you would split NoteRepository, NoteService, and Main into separate classes.
user.home? Storing data in the user's home directory (~/.notes/) is a UNIX/Linux/macOS convention that keeps personal data separate from the application itself. It also means the tool works without elevated permissions and survives application reinstalls.
Bootstrapping the storage directory
Files.createDirectories is idempotent — it creates every missing segment in the path and silently succeeds if the path already exists. Always prefer it over Files.createDirectory when you cannot guarantee the parent exists.
Adding a note
Use Files.writeString with StandardOpenOption.APPEND to add a line without reading the whole file. This is O(1) in terms of memory regardless of how many notes already exist.
Files.writeString accepts a Charset overload. Defaulting to the platform charset is a portability trap — a file written on Windows (Cp1252) may be unreadable on a Linux server (UTF-8). Hardcode StandardCharsets.UTF_8 everywhere.
Listing notes
Files.readAllLines loads every line into a List<String>. For a notes file this is fine; if the file could be gigabytes, switch to Files.lines() (a lazy stream) instead.
Deleting a note by index
There is no efficient way to delete a line from the middle of a plain-text file in place — file systems do not support shrinking a region without rewriting everything after it. The standard pattern is read all, remove, write all. For a notes tool this is acceptable; for a multi-gigabyte log you would use a different storage format.
FileChannel.lock() or a proper database.
Searching notes
Use the streams API together with Files.lines() to avoid loading lines that do not match into memory. The result is printed with indices relative to the original file so the user can delete them by number.
Putting it all together — running the tool
Compile and run from the terminal:
Design decisions and trade-offs
- Plain text vs binary: Plain text is human-readable, trivially backed up with
cp, and grep-able. The downside is that newline characters inside a note would corrupt the format — hence the sanitisation inadd(). - Append vs rewrite on add: Appending is fast and safe under power loss (existing lines are untouched). Rewriting the whole file on every add would be slower and riskier for large files.
- Read-all on delete: Unavoidable with flat files. The trade-off is acknowledged explicitly — this pattern is fine for hundreds of notes; for thousands you would switch to a format like SQLite via JDBC.
- UTF-8 everywhere: Explicitly passing
StandardCharsets.UTF_8to every read and write call makes the tool portable and predictable regardless of the JVM's default charset. - No try-with-resources needed here:
Files.readAllLines,Files.writeString, andFiles.writeare convenience methods that open, use, and close the underlying channel internally. If you drop down toBufferedReaderorFileWriterdirectly, try-with-resources is mandatory.
Summary
This project combined every tool from the tutorial: Path and Files for idiomatic NIO.2 access, explicit UTF-8 charset handling, StandardOpenOption.APPEND for efficient writes, the read-modify-write pattern for in-place deletion, and streams-based search. The design choices — plain text, one note per line, home-directory storage — are deliberate and each has a documented trade-off. That kind of reasoning is what separates a maintainable codebase from one that merely works.