Inside `Online_Banking_System`: How a JavaFX Banking App Moves Money Without Framework Magic

A layered desktop app built with Java 21, JavaFX, JDBC, H2, and manual dependency injection, with one transfer flow that does the most important job in banking: keep every debit, credit, and log entry in sync.

8 min read • View on GitHub • More from bharathhonakatti26

A bank vault split into two chambers with a narrow control room between them. One side shows a debit leaving a source account, the other shows a matching credit entering a destination account, while a hand hovers over a commit switch beside a transaction log clipboard. It explains that the app’s real product is safe, all-or-nothing money movement.
The app’s most important feature is not the screen flow. It is the transfer boundary that keeps balances and logs consistent.
Key Takeaways

The real product is the transaction

Most banking apps want to impress you with dashboards, forms, and a polished login screen. This one earns attention somewhere less glamorous: in the code that decides whether a transfer succeeds without corrupting the ledger. That is the part that matters.

The repo’s core idea is simple. A transfer is not a UI event. It is a controlled sequence of debit, credit, and transaction log writes that either all happen together or do not happen at all.

The transfer flow is the project’s center of gravity. One connection, one transaction boundary, one outcome.

That is why the project is more interesting than a typical JavaFX demo. The UI is just the surface. The intellectual center is the discipline around money movement.

A layered desktop app on purpose

The codebase is organized the way a serious desktop app should be organized: JavaFX controllers at the top, services in the middle, JDBC repositories below, and infrastructure code handling database access and dependency wiring. It is not layering for ceremony. It is layering so each part can do one job well.

LayerResponsibilityWhy it matters
JavaFX controllersHandle user actions and update viewsKeeps UI code thin
ServicesOrchestrate business logicMakes transfers and workflows explicit
RepositoriesRun SQL and map rowsKeeps persistence separate from business rules
InfrastructureBuild the data source and object graphRemoves startup and wiring friction

The contrast with a framework-heavy default is the point. A Spring Boot app would hide some of this behind annotations and auto-configuration. This repository makes the plumbing visible, which is exactly why it is useful for learning.

Manual dependency injection, built to fit JavaFX

Layers: JavaFX UI, services, JDBC repositories (H2 embedded by default), and pooled connections via HikariCP.

Project README, Repository documentation · bharathhonakatti26/Online_Banking_System README

The app builds its own object graph. `ApplicationContext` wires repositories, services, and controllers together, then hands JavaFX an object factory through `FXMLLoader`. That avoids the usual no-args-constructor limitation without dragging in a full dependency injection container.

This matters because it keeps the app understandable. You can trace every dependency by reading the code. No magic, no hidden lifecycle.

A close-up assembly line where ApplicationContext hands a repository, then a service, then a controller into a JavaFX window frame. A single cable labeled FXMLLoader connects the controller factory to the UI. It shows how the app wires itself without a heavyweight container.
Manual wiring is the design choice that makes the rest of the repo easy to follow.

Why the transfer service matters most

`TransferService.java` is the file that makes the whole architecture worth studying. It manages a single JDBC connection, disables auto-commit, performs the balance updates, records the transaction entries, then commits only after every step succeeds.

That sequence is the banking logic. The app is not just moving numbers around. It is protecting consistency across account balances and the transaction log at the same time.

The rollback path is just as important. If any step fails, the connection backs out the whole operation, which is exactly what you want when the unit of work is money.

This is where the repository becomes more than a CRUD sample. It teaches the core habit of financial software: treat a transfer as an all-or-nothing unit, not a pile of independent updates.

The database starts itself

The app does not ask you to set up a database manually before it can run. `DataSourceFactory` bootstraps the embedded H2 schema on startup, which means the project is portable in a way many desktop apps are not.

That is a small detail with outsized value. A repository you can clone and launch immediately is much easier to evaluate, much easier to teach from, and much harder to break during setup.

The trade-off is that startup code now owns schema creation. For a learning project, that is a reasonable cost because it removes friction from the first run.

Repositories keep SQL and objects apart

public class JdbcAccountRepository implements AccountRepository {
    private final DataSource dataSource;

    public JdbcAccountRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Optional<Account> findById(long id) {
        String sql = "SELECT id, user_id, account_number, balance FROM accounts WHERE id = ?";

        try (Connection connection = dataSource.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setLong(1, id);
            try (ResultSet rs = statement.executeQuery()) {
                if (!rs.next()) return Optional.empty();
                return Optional.of(mapRow(rs));
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

The repository pattern is doing real work here. SQL stays in one place, the object mapping stays in one place, and the service layer can think in terms of accounts and transfers instead of result sets and column indexes.

ApproachWhat you gainWhat you give up
Prepared statements + row mappersExplicit control and readable SQLMore code than an ORM
ORM abstractionLess boilerplateLess visibility into the database work
SQL in controllersFast to prototypeHarder to test and maintain
Repository layerClear boundariesRequires a little discipline

That discipline is the story of the repository. The project refuses to blur business logic into persistence logic, and that keeps it surprisingly easy to reason about.

What this project gets right, and where it stops

Online Banking Desktop (JavaFX) JavaFX desktop client for an online banking system.

Project README, Repository documentation · bharathhonakatti26/Online_Banking_System README

The strongest thing about this repository is its clarity. It shows how a desktop banking app can be wired, booted, and kept consistent with plain Java, JDBC, and a small amount of infrastructure code.

The limits are real too. The repository notes plain-text password comparison, which is fine for a prototype but not acceptable for a production banking system. The code also reads like an educational foundation, not a hardened platform with deep test coverage and compliance controls.

StrengthLimitation
Explicit transfer atomicityNot production-grade security
Portable embedded setupPrototype-level maturity
Readable layered architectureLimited evidence of broad automated testing

That honesty is a feature. The repo is valuable because it demonstrates the mechanics cleanly and leaves the hardening work visible instead of pretending it is already solved.