Desktop app · case study
Budget Guardian
Personal finance desktop app where every feature runs on custom data structures, no Java Collections, enforced by a build-failing test.

- My role
Sole developer · personal project, built to review DSA before TA work
- Type
Desktop application
- Stack
Java, JavaFX, SQLite, Maven
Why I built it
I built Budget Guardian to track my own spending, and to review data structures and algorithms before working as a teaching assistant for my university's DSA course.Every feature runs on data structures I wrote myself, with no Java collections in the business logic, and a test fails the build if one appears.I'm the sole developer.
The problem
Track spending, budgets, debts and repeat purchases in one desktop app.
Review DSA by using each structure where a real feature needs it.
Keep the no-collections rule true as the code grows.
Features
- Dashboard: spend vs. budget, balances, priority alerts
- Ledger, transfers, and debt tracking with auto-settling payments
- Detects refillable items and flags overdue purchases
- Weekly danger-spending guard on Alcohol + Gambling
- Reports with heap-sort ranking, plus a DFS/BFS transfer graph
- Free-text search (Ctrl+F) and full undo (Ctrl+Z)
Architecture
- JavaFX views to Services
- Services to In-memory store: read, write
- Services to Repositories: persist
- Repositories to In-memory store: hydrate at start
- Repositories to SQLite file: local
- Repositories to Express 5 API: X-API-Key
- Express 5 API to Neon Postgres: Prisma
In words
JavaFX views call services that work on an in-memory store of hand-written data structures.Repositories hydrate that store at startup.After that, every change is persisted through them first.Storage is a local SQLite file by default.API mode uses an Express 5 API with zod validation and Prisma on Neon Postgres.The desktop never holds database credentials.
Pipeline
push / PR → main
mvn test
incl. ArchitectureGuardTest
Fat jar
Boot smoke test
launches the real app on Windows
tag v*.*.*
Version from tag
mvn test
jpackage + WiX
MSI installer
GitHub Release
one per version, never overwritten
Decisions
- Money as whole satang, never floating point.
1 baht is 100 satang.Every calculation is exact, with no rounding drift.
- Save first, then update memory.
Every change is persisted before memory changes.A crash or failed save never leaves the app ahead of storage.
- Two storage modes behind one interface.
SQLite by default, or an Express API on Neon Postgres.The desktop never holds database credentials.
- Transfers are never expenses.
Transfers have their own model and table.They can't distort spending reports.
- Danger week is a calendar week.
Monday to Sunday is cheaper to compute than a rolling seven days, and easier to reason about.
Challenges
- Keeping the no-collections rule honest.
A test fails the build on any forbidden java.util import.The UI is the one exception: JavaFX needs its own list type.
- The cloud database sleeps when idle. The first request is slow.
The client timeout (25s) exceeds the database connect timeout (15s).Only idempotent requests retry.
- Shutting the local backend down cleanly on Windows.
It starts Node directly, not through npm.Stopping it can't orphan a process.
Testing
287 JUnit tests pass in Windows CI, from data structures to full-stack integration.
A boot-smoke test launches the packaged app and checks every view loads.
Version tags publish a permanent MSI.Pushes to main refresh a rolling pre-release.
Outcome
v1.0.0 released as a Windows installer.
Next
Test coverage reports in CI
macOS and Linux installers
Stack
- Java
Every feature runs on hand-written data structures.No Java Collections.
- JavaFX
Builds the desktop views in code, without FXML.
- SQLite
Persists data locally and loads it into memory at startup.
- Maven
Builds the fat jar, and the MSI installer with jpackage.
Security
- Authentication
None locally: it's a single-user desktop app.API mode authenticates with an X-API-Key header.
- Input validation
Services validate every input before saving.The API adds zod schemas.Money is integer satang, never floating point.
- Protections
The desktop never holds database credentials.A test fails the build on any java.util collection in business logic.