Build a JavaFX Banking Dashboard
Build a JavaFX banking dashboard with validation and a transaction ledger.
Introduction
30 Second Summary
A banking screen earns trust when every money change is easy to follow. A rejected transaction should leave the balance exactly where it was.
In this project, you will build a desktop banking simulator with JavaFX inside IntelliJ IDEA. An object-oriented account model drives its live balance while protecting it from invalid transactions.
What You'll Build
You will launch NextWork Bank from IntelliJ IDEA to watch each deposit or withdrawal update its balance plus transaction history in a desktop window.
By the end of this project, you'll have:
- A native banking dashboard that launches as its own window from IntelliJ IDEA.
- A transaction workflow that updates the live balance plus the visible ledger after each successful deposit or withdrawal.
- Validation feedback that rejects zero, negative, over-precise, or overdrawn transactions without changing the balance.
- Secret Mission: Add controls that filter the transaction ledger by transaction type without changing the account data.
Are there any prerequisites?
Some programming experience is helpful. You need IntelliJ IDEA installed on a Mac.
The guide includes the JavaFX setup plus the complete project configuration.
Before We Start
This banking simulator demonstrates Java fundamentals in a learning environment. It does not process real money or represent a production financial system.
Set Up and Launch JavaFX
Your banking simulator cannot appear until its compiler can load the desktop interface modules. A compatible JDK gives JavaFX the runtime it expects.
IntelliJ IDEA will create the project structure. Maven will keep the JavaFX dependency reproducible.
In this step, get ready to:
- Enable the bundled JavaFX support in IntelliJ IDEA.
- Create a Maven project that uses JDK 26.
- Launch the setup window through the javafx:run Maven goal.
Enable JavaFX support and start the project
IntelliJ IDEA includes a bundled plugin that understands JavaFX project features. Confirming that plugin first removes an avoidable setup gap.
- Press Cmd+Space to open macOS search.
- Type IntelliJ IDEA and press Enter to open it.
You should see the IntelliJ IDEA welcome screen.
- Open IntelliJ IDEA's settings window from the macOS menu bar.
- Select Plugins in the left sidebar.
The settings window now shows the installed plugin list.
- Search for JavaFX in the plugin search field.
- Click Enable if the bundled JavaFX plugin is disabled.
The JavaFX plugin entry should now show that it is enabled.
- Restart IntelliJ IDEA if a restart prompt appears.
- Close the settings window to return to the welcome screen.
- Click New Project on the welcome screen.
- Select Java as the project language.
The project wizard now shows the Java project settings.
- Enter banking-dashboard as the project name.
- Select Maven as the build system.
The wizard is now ready for the project runtime and Maven coordinates.
Configure JDK 26 and the Maven files
JavaFX 26 works with JDK 26 for this project. The JDK selected here becomes the compiler and runtime for the application.
- Open the JDK menu in the project wizard.
✔️ I see JDK 26
The required JDK is already available to the project wizard.
- Select JDK 26 from the list.
The JDK field should now display version 26.
ⓧ I see an older JDK
The older JDK stays available for other projects. This project needs its own JDK 26 selection.
- Select Download JDK from the JDK menu.
- Choose version 26 in the download dialog.
- Start the JDK download from the dialog.
IntelliJ IDEA shows download progress while it installs the selected JDK.
- Select the downloaded JDK 26 in the project wizard.
The JDK field should now display version 26.
ⓧ JDK 26 is not listed
IntelliJ IDEA can download the missing JDK from inside the project wizard.
- Select Download JDK from the JDK menu.
- Choose version 26 in the download dialog.
- Start the JDK download from the dialog.
IntelliJ IDEA shows download progress while it installs the selected JDK.
- Select the downloaded JDK 26 in the project wizard.
The JDK field should now display version 26.
- Enter com.nextwork in the GroupId field.
- Enter banking-dashboard in the ArtifactId field.
The wizard should now show the Maven coordinates for the banking dashboard.
- Disable the sample code option.
- Click Create to generate the project.
Good start. IntelliJ IDEA now shows the banking-dashboard project with a generated pom.xml file.
The generated Maven file needs pinned compiler and JavaFX settings. These settings make the same dependencies available whenever Maven loads the project.
- Open pom.xml from the Project sidebar.
- Open the second tab below to use the complete pinned configuration.
✔️ Awesome, I've got everything!
Your pom.xml already matches the complete pinned configuration.
ⓧ I'd like to double check the full code
- Replace every line in pom.xml with this exact configuration:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nextwork</groupId>
<artifactId>banking-dashboard</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>26</maven.compiler.release>
<javafx.version>26</javafx.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<configuration>
<release>26</release>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.nextwork.bank/com.nextwork.bank.BankingApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
What does this configuration do?
- The properties pin JavaFX and the Java compiler to version 26.
- The javafx-controls dependency supplies the interface controls used by the application.
- The Maven compiler plugin version 3.14.1 compiles the project for JDK 26.
- The JavaFX Maven plugin version 0.0.8 provides the goal that launches BankingApplication.
- Save pom.xml.
- Reload the project with the reload control in the Maven tool window.
- Confirm that javafx-controls appears under Maven dependencies.
- Confirm that the JavaFX plugin appears under Maven plugins.
Dependencies not loading?
Check that every XML tag in pom.xml matches the reference above. Reload the Maven project after saving the file.
Ask for help with the Maven configuration.
A Java module descriptor tells the runtime which JavaFX module the application needs. It also exposes the application package to the JavaFX launcher.
- Locate src/main/java in the Project sidebar.
- Create module-info.java inside src/main/java with IntelliJ IDEA's new file action.
- Replace the file contents with this module descriptor:
module com.nextwork.bank {
requires javafx.controls;
exports com.nextwork.bank;
}
What does this module descriptor do?
- The com.nextwork.bank module gives the application a named module boundary.
- The requires javafx.controls directive makes JavaFX controls available on the module path.
- The exports com.nextwork.bank directive exposes the application package to the JavaFX launcher.
- Save module-info.java.
The editor should show the complete module descriptor without red error highlights.
Seeing module errors?
Confirm that module-info.java sits directly inside src/main/java. Check that Maven finished loading javafx-controls.
Ask for help with the module descriptor.
✔️ Awesome, I've got everything!
Your module descriptor is saved in the correct source folder.
ⓧ I'd like to double check the full code
module com.nextwork.bank {
requires javafx.controls;
exports com.nextwork.bank;
}
What should match?
This complete file declares the project module. It gives that module access to JavaFX controls.
Create and launch the JavaFX application
A JavaFX application starts with an Application subclass. Its start() method receives the window that the learner sees.
- Select src/main/java in the Project sidebar.
- Create the package com.nextwork.bank with IntelliJ IDEA's new package action.
The com.nextwork.bank package should now appear beneath src/main/java.
- Create the Java class BankingApplication inside com.nextwork.bank.
- Replace the generated class contents with this application:
package com.nextwork.bank;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
public class BankingApplication extends Application {
@Override
public void start(Stage stage) {
Label setupLabel = new Label("Setup complete");
stage.setTitle("NextWork Bank");
stage.setScene(new Scene(setupLabel, 300, 150));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
What does this application do?
- The Application class supplies the JavaFX application lifecycle.
- The start() method receives the top-level Stage window.
- The Label displays the setup confirmation inside the window.
- The Scene holds the label in a window measuring 300 by 150.
- The launch() call starts the JavaFX runtime from main().
- Save BankingApplication.java.
- Confirm that the file appears at src/main/java/com/nextwork/bank/BankingApplication.java in the Project sidebar.
Seeing red JavaFX imports?
Reload the Maven project after confirming that pom.xml contains the javafx-controls dependency. Confirm that the project SDK is JDK 26.
Ask for help with the unresolved imports.
✔️ Awesome, I've got everything!
Your application class is saved in the exported package.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
public class BankingApplication extends Application {
@Override
public void start(Stage stage) {
Label setupLabel = new Label("Setup complete");
stage.setTitle("NextWork Bank");
stage.setScene(new Scene(setupLabel, 300, 150));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
What should match?
This complete file creates the setup label. It places that label inside the first JavaFX window.
- Open the Maven tool window from IntelliJ IDEA's tool window bar.
- Expand Plugins in the Maven project tree.
- Expand the JavaFX plugin entry.
Before you run the goal, do you expect the compiler and JavaFX runtime to produce an editor message or a separate desktop window?
- Double-click the javafx:run goal under the JavaFX plugin.
You should see a separate window titled NextWork Bank. The window should show Setup complete inside it.
That's the setup hurdle cleared. Your Maven goal now launches a modular JavaFX desktop application.
Why does the Maven run stay active?
The Maven process remains active while the application window is open. Closing the JavaFX window ends that run.
Window not appearing?
Confirm that the JavaFX plugin is listed under Maven plugins. Check that module-info.java requires javafx.controls.
Compare the mainClass value in pom.xml with the package and class name in BankingApplication.java.
Ask for help with the launch problem.
Your JavaFX toolchain is working. Next, you will replace the setup message with an account backed by a Java object.
Model and Display an Account
Your JavaFX window launches successfully. Now the dashboard needs real account data behind what it displays.
A hard-coded label cannot represent changing bank data. The interface needs an account model that owns the account holder and balance.
You will create an Account object for Alex Morgan. Its opening balance uses BigDecimal so the value begins as the exact decimal amount 100.00.
In this step, get ready to:
- Create an Account class that protects its state through encapsulation.
- Initialize Alex Morgan with an exact decimal opening balance.
- Display the account holder plus formatted balance in a VBox dashboard.
Create the account model
The Account class keeps banking data together. Private fields prevent other classes from changing that data directly.
- In the Project tool window in IntelliJ IDEA, expand src/main/java/com/nextwork/bank.
- Right-click the com.nextwork.bank package.
- Select New.
- Select Java Class.
- Enter Account as the class name.
- Press Enter.
- Replace the generated contents of Account.java by pasting this code:
package com.nextwork.bank;
import java.math.BigDecimal;
public class Account {
private final String accountHolder;
private BigDecimal balance;
public Account(String accountHolder, BigDecimal openingBalance) {
this.accountHolder = accountHolder;
this.balance = openingBalance;
}
public String getAccountHolder() {
return accountHolder;
}
public BigDecimal getBalance() {
return balance;
}
}
What Does This Class Do?
- The private accountHolder and balance fields keep the account state inside Account.
- This protection is called encapsulation.
- The constructor receives the holder name plus opening balance when your application creates an account.
- The getAccountHolder() and getBalance() methods provide controlled access to those values.
- Save Account.java.
- Return to the Maven tool window from Step 1.
- Expand Plugins.
- Expand the JavaFX plugin.
- Double-click javafx:run.
You will see Setup complete inside the NextWork Bank window. That successful launch proves your new account model compiles without breaking the application.
Does the application fail to launch?
- Check that the first line of Account.java uses the com.nextwork.bank package.
- Check that the file name uses the exact capitalization Account.java.
- Ask for targeted help: Help me find why Account.java does not compile in my JavaFX Maven project.
✔️ Awesome, I've got everything!
Your account model is saved. Continue to connect it to the dashboard.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
import java.math.BigDecimal;
public class Account {
private final String accountHolder;
private BigDecimal balance;
public Account(String accountHolder, BigDecimal openingBalance) {
this.accountHolder = accountHolder;
this.balance = openingBalance;
}
public String getAccountHolder() {
return accountHolder;
}
public BigDecimal getBalance() {
return balance;
}
}
Initialize the account data
The application needs one Account object before it can display account data. A field keeps the same account available throughout BankingApplication.
- Switch back to BankingApplication.java.
- Select everything from the package com.nextwork.bank; line through the opening brace of BankingApplication.
- Replace that selection by pasting this code:
package com.nextwork.bank;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BankingApplication extends Application {
private final Account account = new Account("Alex Morgan", new BigDecimal("100.00"));
How Is the Account Initialized?
- The account field creates one Account object for Alex Morgan.
- The string-based BigDecimal constructor preserves the exact decimal value 100.00.
- The layout plus rounding imports prepare the application to display the account as a formatted dashboard.
- Save BankingApplication.java.
- Return to the Maven tool window from earlier.
- Run javafx:run from the JavaFX plugin.
You will see Setup complete again. This launch confirms that Alex Morgan's Account object initializes successfully.
Does the account fail to initialize?
- Check that Account.java sits in the same com.nextwork.bank package as BankingApplication.java.
- Check that new BigDecimal("100.00") keeps the amount inside double quotes.
- Ask for targeted help: Help me debug the Account field in BankingApplication.
Build the account dashboard
Money values need a consistent display format. The formatMoney() method rounds a value to two decimal places before adding the dollar sign.
- In BankingApplication.java, locate the public static void main line.
- Insert this method immediately above that line:
private String formatMoney(BigDecimal value) {
return "$" + value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
How Is the Balance Formatted?
- The value parameter holds the balance that needs formatting.
- The setScale() call produces two decimal places.
- The toPlainString() call converts the decimal into display text without scientific notation.
- Save BankingApplication.java.
- Return to the Maven tool window from earlier.
- Run javafx:run from the JavaFX plugin.
You will see the setup window open successfully. This confirms that formatMoney() compiles inside the application class.
Does the formatting method fail to compile?
- Check that formatMoney() appears inside the BankingApplication class.
- Check that the file imports BigDecimal plus RoundingMode.
- Ask for targeted help: Help me debug the formatMoney method.
The account data is ready to replace the fixed setup message. A VBox arranges the title, holder name, balance caption, and formatted balance vertically.
- Locate the existing start() method in BankingApplication.java.
- Replace the entire method by pasting this code:
@Override
public void start(Stage stage) {
Label titleLabel = new Label("NextWork Bank");
Label holderLabel = new Label("Account holder: " + account.getAccountHolder());
Label balanceCaption = new Label("Available balance");
Label balanceLabel = new Label(formatMoney(account.getBalance()));
VBox root = new VBox(12, titleLabel, holderLabel, balanceCaption, balanceLabel);
root.setPadding(new Insets(20));
stage.setTitle("NextWork Bank");
stage.setScene(new Scene(root, 480, 300));
stage.show();
}
How Does the Dashboard Use the Model?
- The holder label reads Alex Morgan through account.getAccountHolder().
- The balance label passes account.getBalance() into formatMoney().
- The VBox keeps the dashboard labels in vertical order with 12 pixels of spacing.
- The Insets value adds 20 pixels of padding around the dashboard.
- Save BankingApplication.java.
Before you run the updated application, which account holder and balance do you expect the dashboard to show?
- Return to the Maven tool window from earlier.
- Expand Plugins.
- Expand the JavaFX plugin.
- Double-click javafx:run.
You will see a NextWork Bank window showing Alex Morgan plus an available balance of $100.00.
You have connected your first domain object to a desktop interface. The dashboard now reads its visible values from the Account model.
Does the setup label still appear?
- Check that the old setupLabel line no longer appears in start().
- Check that stage.setScene() receives root as the scene content.
- Check that formatMoney() remains inside the BankingApplication class.
- Ask for targeted help: Help me debug why my account dashboard does not appear.
✔️ Awesome, I've got everything!
Your dashboard is reading the account holder and opening balance from the Account object.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BankingApplication extends Application {
private final Account account = new Account("Alex Morgan", new BigDecimal("100.00"));
@Override
public void start(Stage stage) {
Label titleLabel = new Label("NextWork Bank");
Label holderLabel = new Label("Account holder: " + account.getAccountHolder());
Label balanceCaption = new Label("Available balance");
Label balanceLabel = new Label(formatMoney(account.getBalance()));
VBox root = new VBox(12, titleLabel, holderLabel, balanceCaption, balanceLabel);
root.setPadding(new Insets(20));
stage.setTitle("NextWork Bank");
stage.setScene(new Scene(root, 480, 300));
stage.show();
}
private String formatMoney(BigDecimal value) {
return "$" + value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
public static void main(String[] args) {
launch(args);
}
}
Your dashboard now reads from a real Account object. Next, you will add controls that change the balance while the application is running.
Make the Balance Interactive
Your JavaFX account card now opens from IntelliJ IDEA. It shows Alex Morgan with a $100.00 balance.
A read-only dashboard cannot perform banking actions. This step connects account methods to controls so you can test what the model currently permits.
In this step, get ready to:
- Add direct deposit and withdrawal arithmetic to the account.
- Connect an amount field to Deposit and Withdraw buttons.
- Test a withdrawal that exceeds the available balance.
Add transaction methods
The Account object owns the balance. Its new methods use BigDecimal.add and BigDecimal.subtract to change that state.
- In the Project tool window, select Account.java.
- Find the closing brace below getBalance().
- Insert the transaction methods below getBalance() by copying this code:
public void deposit(BigDecimal amount) {
balance = balance.add(amount);
}
public void withdraw(BigDecimal amount) {
balance = balance.subtract(amount);
}
What Do These Methods Do?
- The deposit() method replaces the current balance with the sum returned by add().
- The withdraw() method replaces the current balance with the difference returned by subtract().
- Each method keeps balance changes inside the account model.
- Save Account.java.
- Switch back to the Maven tool window.
- Expand Plugins.
- Run javafx:run from the JavaFX plugin.
You should see the account card reopen with Alex Morgan and $100.00. This confirms that the new account methods compile successfully.
Seeing a Compilation Problem?
Check that both methods sit inside the Account class. Confirm that each method has its own pair of braces.
Use Help me fix the transaction methods in Account.java. if the project still does not compile.
✔️ Awesome, I've got everything!
Your account now has methods that can change its balance.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
import java.math.BigDecimal;
public class Account {
private final String accountHolder;
private BigDecimal balance;
public Account(String accountHolder, BigDecimal openingBalance) {
this.accountHolder = accountHolder;
this.balance = openingBalance;
}
public String getAccountHolder() {
return accountHolder;
}
public BigDecimal getBalance() {
return balance;
}
public void deposit(BigDecimal amount) {
balance = balance.add(amount);
}
public void withdraw(BigDecimal amount) {
balance = balance.subtract(amount);
}
}
What Should Match?
Your file should keep the existing fields and getters. The two new methods should appear before the final class brace.
Connect the controls to the account
The dashboard needs controls that collect an amount and trigger the matching account method. A shared event path keeps both buttons connected to the same balance refresh.
- Close the application window.
- Return to BankingApplication.java.
- Find this control and layout import block:
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
What Are You Looking For?
These imports support the read-only dashboard. The interactive version needs three additional control and layout classes.
- Replace that import block with this expanded block:
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
What Changes Here?
- The Button class provides the Deposit and Withdraw controls.
- The TextField class collects the transaction amount.
- The HBox class places both buttons on one horizontal row.
- Find the account field near the top of the class.
- Add these shared controls below the account field:
private Label balanceLabel;
private TextField amountField;
private Label statusLabel;
Why Are These Fields?
The event-handling methods need access to the balance label and amount field after start() finishes. The status label also stays available so each transaction can report its result.
- Save BankingApplication.java.
- Switch back to the Maven tool window.
- Run javafx:run from the JavaFX plugin.
You should see the existing account card reopen with a $100.00 balance. This proves the new imports and fields compile before the controls use them.
- Close the application window.
- Return to BankingApplication.java.
- Find the existing start() method:
@Override
public void start(Stage stage) {
Label titleLabel = new Label("NextWork Bank");
Label holderLabel = new Label("Account holder: " + account.getAccountHolder());
Label balanceCaption = new Label("Available balance");
Label balanceLabel = new Label(formatMoney(account.getBalance()));
VBox root = new VBox(12, titleLabel, holderLabel, balanceCaption, balanceLabel);
root.setPadding(new Insets(20));
stage.setTitle("NextWork Bank");
stage.setScene(new Scene(root, 480, 300));
stage.show();
}
What Does the Current Method Build?
The current method creates labels and adds them to a vertical layout. Its balance label is local to start().
- Replace the existing start() method with this interactive version:
@Override
public void start(Stage stage) {
Label titleLabel = new Label("NextWork Bank");
Label holderLabel = new Label("Account holder: " + account.getAccountHolder());
Label balanceCaption = new Label("Available balance");
balanceLabel = new Label();
amountField = new TextField();
amountField.setPromptText("Amount, for example 25.00");
Button depositButton = new Button("Deposit");
Button withdrawButton = new Button("Withdraw");
depositButton.setOnAction(event -> processTransaction(true));
withdrawButton.setOnAction(event -> processTransaction(false));
HBox actionButtons = new HBox(10, depositButton, withdrawButton);
statusLabel = new Label("Enter an amount to begin.");
VBox root = new VBox(12, titleLabel, holderLabel, balanceCaption, balanceLabel, amountField, actionButtons, statusLabel);
root.setPadding(new Insets(20));
refreshDashboard();
stage.setTitle("NextWork Bank");
stage.setScene(new Scene(root, 480, 400));
stage.show();
}
How Does the Interface Work?
- The amount field collects text such as 25.00.
- Each button passes a boolean value to processTransaction().
- The horizontal layout keeps both transaction buttons together.
- The taller scene creates room for the new controls.
- Place these methods below start() by copying this code:
private void processTransaction(boolean deposit) {
BigDecimal amount = new BigDecimal(amountField.getText().trim());
if (deposit) {
account.deposit(amount);
statusLabel.setText("Deposit complete.");
} else {
account.withdraw(amount);
statusLabel.setText("Withdrawal complete.");
}
amountField.clear();
refreshDashboard();
}
private void refreshDashboard() {
balanceLabel.setText(formatMoney(account.getBalance()));
}
What Does the Event Path Do?
- The BigDecimal constructor converts the trimmed field text into an amount.
- The boolean selects either deposit() or withdraw().
- The status label confirms which operation finished.
- The final refresh reads the latest balance from the account.
- Save BankingApplication.java.
- Switch back to the Maven tool window.
- Run javafx:run from the JavaFX plugin.
- Enter 25.00 in the amount field.
- Click Deposit.
You should see the balance change to $125.00. The status text should read Deposit complete..
Good progress. Your dashboard now turns a button click into a visible account update.
Does the Button Fail?
Confirm that processTransaction() and refreshDashboard() sit inside the BankingApplication class. Check that the amount field contains digits before you click a button.
Use Help me debug the JavaFX transaction controls. if the balance still does not update.
✔️ Awesome, I've got everything!
Your amount field and transaction buttons now update the visible balance.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BankingApplication extends Application {
private final Account account = new Account("Alex Morgan", new BigDecimal("100.00"));
private Label balanceLabel;
private TextField amountField;
private Label statusLabel;
@Override
public void start(Stage stage) {
Label titleLabel = new Label("NextWork Bank");
Label holderLabel = new Label("Account holder: " + account.getAccountHolder());
Label balanceCaption = new Label("Available balance");
balanceLabel = new Label();
amountField = new TextField();
amountField.setPromptText("Amount, for example 25.00");
Button depositButton = new Button("Deposit");
Button withdrawButton = new Button("Withdraw");
depositButton.setOnAction(event -> processTransaction(true));
withdrawButton.setOnAction(event -> processTransaction(false));
HBox actionButtons = new HBox(10, depositButton, withdrawButton);
statusLabel = new Label("Enter an amount to begin.");
VBox root = new VBox(12, titleLabel, holderLabel, balanceCaption, balanceLabel, amountField, actionButtons, statusLabel);
root.setPadding(new Insets(20));
refreshDashboard();
stage.setTitle("NextWork Bank");
stage.setScene(new Scene(root, 480, 400));
stage.show();
}
private void processTransaction(boolean deposit) {
BigDecimal amount = new BigDecimal(amountField.getText().trim());
if (deposit) {
account.deposit(amount);
statusLabel.setText("Deposit complete.");
} else {
account.withdraw(amount);
statusLabel.setText("Withdrawal complete.");
}
amountField.clear();
refreshDashboard();
}
private void refreshDashboard() {
balanceLabel.setText(formatMoney(account.getBalance()));
}
private String formatMoney(BigDecimal value) {
return "$" + value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
public static void main(String[] args) {
launch(args);
}
}
What Should Match?
Your file should contain the three shared controls and both transaction methods. The scene should use a width of 480 with a height of 400.
Test an oversized withdrawal
The interface can now send any entered amount to the account. A larger withdrawal tests whether the current model protects its balance.
Before you run the check, ask yourself whether the account will prevent a withdrawal larger than its available balance.
- Close the current application window.
- Switch back to the Maven tool window.
- Run javafx:run from the JavaFX plugin.
- Confirm that the restarted balance reads $100.00.
- Enter 150.00 in the amount field.
- Click Withdraw.
You should see the balance fall to $-50.00. The status text should read Withdrawal complete..
This Negative Balance Is Intentional
The result proves that the current withdraw() method subtracts every amount it receives. The account model currently has no rule that rejects an overdraft.
You have exposed the exact gap that the next step solves. Next, you will protect the account with validation inside its domain model.
Protect the Account with Validation
The previous step turned your JavaFX dashboard into an interactive app. It also exposed an overdrawn balance of -$50.00.
That negative balance shows why transaction rules belong inside the account model. This step uses a checked exception to reject invalid transactions before they can change the balance.
In this step, get ready to:
- Create a checked exception for invalid transactions.
- Protect the balance with validation inside Account.
- Catch transaction errors in the interface.
Create a checked exception
A checked exception makes invalid transactions part of each method's contract. Java requires calling code to handle that possible outcome.
- In the Project sidebar, expand src/main/java/com/nextwork/bank.
- Right-click the com.nextwork.bank package.
- Select New.
- Select Java Class.
- Enter InvalidTransactionException as the class name.
- Replace the generated contents of InvalidTransactionException.java by pasting this code:
package com.nextwork.bank;
public class InvalidTransactionException extends Exception {
public InvalidTransactionException(String message) {
super(message);
}
}
What does this class do?
- Extending Exception makes this a checked exception.
- The constructor accepts a message that explains why a transaction failed.
- Calling super(message) stores that explanation in the base exception.
- Save InvalidTransactionException.java with Cmd+S on macOS or Ctrl+S on Windows.
- Confirm the Project sidebar lists InvalidTransactionException.java inside com.nextwork.bank.
Good progress. Your project now has a dedicated way to report a rejected transaction.
Class name showing an error?
- Check that the file is named InvalidTransactionException.java.
- Check that the package line says package com.nextwork.bank;.
- Ask for help with the exception class.
✔️ Awesome, I've got everything!
Your checked exception class is ready for the account rules.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
public class InvalidTransactionException extends Exception {
public InvalidTransactionException(String message) {
super(message);
}
}
What should this file contain?
This complete file defines the checked exception that carries each validation message back to the interface.
Move transaction rules into Account
The account already uses BigDecimal for its money values. It now needs to guard every route that can change those values.
Validation inside Account protects the balance no matter which interface calls it. The balance changes only after an amount passes every rule.
- Switch back to Account.java from earlier.
- Locate the constructor that begins with public Account(String accountHolder, BigDecimal openingBalance).
- Replace that constructor by pasting this code:
public Account(String accountHolder, BigDecimal openingBalance) {
if (openingBalance == null || openingBalance.signum() < 0) {
throw new IllegalArgumentException("Opening balance cannot be negative.");
}
this.accountHolder = accountHolder;
this.balance = openingBalance;
}
How does the constructor protect the account?
- The condition rejects a missing opening balance.
- The signum() check rejects a negative opening balance.
- The field assignments run only after the opening balance passes the check.
- Save Account.java.
- Run javafx:run from the JavaFX plugin in the Maven tool window.
The window opens with $100.00 as the available balance. That confirms the valid opening amount still passes the constructor rule.
- Close the NextWork Bank window.
Window no longer opening?
- Check that both field assignments remain below the validation block.
- Check that the opening balance in BankingApplication.java remains 100.00.
- Ask why the constructor is rejecting the opening balance.
- In Account.java, locate the deposit method.
- Select from the start of deposit through the end of withdraw.
- Replace the selection with the guarded methods plus their shared validator by pasting this code:
public void deposit(BigDecimal amount) throws InvalidTransactionException {
validateAmount(amount);
balance = balance.add(amount);
}
public void withdraw(BigDecimal amount) throws InvalidTransactionException {
validateAmount(amount);
if (amount.compareTo(balance) > 0) {
throw new InvalidTransactionException("Insufficient funds for this withdrawal.");
}
balance = balance.subtract(amount);
}
private void validateAmount(BigDecimal amount) throws InvalidTransactionException {
if (amount == null || amount.signum() <= 0) {
throw new InvalidTransactionException("Enter an amount greater than zero.");
}
if (amount.scale() > 2) {
throw new InvalidTransactionException("Use no more than two decimal places.");
}
}
How do these rules protect the balance?
- Both transaction methods call validateAmount() before changing the balance.
- The first condition rejects missing amounts.
- The signum() condition rejects zero or negative amounts.
- The scale() condition rejects amounts with more than two decimal places.
- The withdrawal comparison rejects an amount greater than the current balance.
- Save Account.java.
- Switch back to BankingApplication.java from earlier.
The editor now marks the calls to account.deposit(amount) and account.withdraw(amount). This is the checked exception enforcing its contract.
Seeing errors inside Account?
- Check that validateAmount() sits inside the Account class.
- Check that both transaction method signatures declare throws InvalidTransactionException.
- Ask for help with the account validation.
✔️ Awesome, I've got everything!
Your account now checks every amount before changing its balance.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
import java.math.BigDecimal;
public class Account {
private final String accountHolder;
private BigDecimal balance;
public Account(String accountHolder, BigDecimal openingBalance) {
if (openingBalance == null || openingBalance.signum() < 0) {
throw new IllegalArgumentException("Opening balance cannot be negative.");
}
this.accountHolder = accountHolder;
this.balance = openingBalance;
}
public String getAccountHolder() {
return accountHolder;
}
public BigDecimal getBalance() {
return balance;
}
public void deposit(BigDecimal amount) throws InvalidTransactionException {
validateAmount(amount);
balance = balance.add(amount);
}
public void withdraw(BigDecimal amount) throws InvalidTransactionException {
validateAmount(amount);
if (amount.compareTo(balance) > 0) {
throw new InvalidTransactionException("Insufficient funds for this withdrawal.");
}
balance = balance.subtract(amount);
}
private void validateAmount(BigDecimal amount) throws InvalidTransactionException {
if (amount == null || amount.signum() <= 0) {
throw new InvalidTransactionException("Enter an amount greater than zero.");
}
if (amount.scale() > 2) {
throw new InvalidTransactionException("Use no more than two decimal places.");
}
}
}
What should this file contain?
This complete file protects the opening balance plus every deposit or withdrawal. Each transaction changes the balance only after validation succeeds.
Catch errors in the JavaFX interface
The account now reports rejected transactions through InvalidTransactionException. The event path must catch those errors so the window can show useful feedback without closing.
- In BankingApplication.java, locate processTransaction(boolean deposit).
- Select the complete processTransaction method.
- Replace the selection by pasting this guarded event path:
private void processTransaction(boolean deposit) {
try {
BigDecimal amount = new BigDecimal(amountField.getText().trim());
if (deposit) {
account.deposit(amount);
showSuccess("Deposit complete.");
} else {
account.withdraw(amount);
showSuccess("Withdrawal complete.");
}
amountField.clear();
refreshDashboard();
} catch (NumberFormatException exception) {
showError("Enter a valid number, such as 25.00.");
} catch (InvalidTransactionException exception) {
showError(exception.getMessage());
}
}
How does the event path handle errors?
- The try block contains the parsing plus the requested transaction.
- Successful transactions clear the field.
- Successful transactions refresh the displayed balance.
- The first catch block handles text that cannot become a number.
- The second catch block displays the message supplied by the account.
- Save BankingApplication.java.
The checked-exception warning clears from the account calls. The editor now marks showSuccess() and showError() because those helper methods come next.
- Locate refreshDashboard() in BankingApplication.java.
- Place your cursor below the closing brace of refreshDashboard().
- Add the status helpers by pasting this code:
private void showSuccess(String message) {
statusLabel.setText(message);
statusLabel.setStyle("-fx-text-fill: #087f5b;");
}
private void showError(String message) {
statusLabel.setText(message);
statusLabel.setStyle("-fx-text-fill: #c92a2a;");
}
What do the status helpers do?
- Both helpers place a message in statusLabel.
- Successful feedback uses green text.
- Rejected input uses red text.
- Save BankingApplication.java.
Before you run the dashboard, do you expect any of the four invalid amounts to change the balance? Hold that prediction.
- Return to the Maven tool window.
- Expand Plugins.
- Locate the JavaFX plugin.
- Run javafx:run.
The dashboard opens with an available balance of $100.00.
- Enter 0 in the amount field.
- Click Withdraw.
You see Enter an amount greater than zero. in red. The balance remains $100.00.
- Replace the amount with -5.
- Click Withdraw.
You see the same positive-amount message. The balance remains $100.00.
- Replace the amount with 1.999.
- Click Withdraw.
You see Use no more than two decimal places. in red. The balance remains $100.00.
- Replace the amount with 150.00.
- Click Withdraw.
You see Insufficient funds for this withdrawal. in red. The balance still reads $100.00.
That is the protection working. Four invalid transactions reached the account model without corrupting its balance.
Invalid input still changing the balance?
- Check that each balance update appears after its validation call in Account.java.
- Check that refreshDashboard() remains inside the successful path of the try block.
- Check that the insufficient-funds comparison runs before balance.subtract(amount).
- Ask for help tracing the invalid transaction.
✔️ Awesome, I've got everything!
Your interface now turns transaction failures into visible feedback while preserving the balance.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BankingApplication extends Application {
private final Account account = new Account("Alex Morgan", new BigDecimal("100.00"));
private Label balanceLabel;
private TextField amountField;
private Label statusLabel;
@Override
public void start(Stage stage) {
Label titleLabel = new Label("NextWork Bank");
Label holderLabel = new Label("Account holder: " + account.getAccountHolder());
Label balanceCaption = new Label("Available balance");
balanceLabel = new Label();
amountField = new TextField();
amountField.setPromptText("Amount, for example 25.00");
Button depositButton = new Button("Deposit");
Button withdrawButton = new Button("Withdraw");
depositButton.setOnAction(event -> processTransaction(true));
withdrawButton.setOnAction(event -> processTransaction(false));
HBox actionButtons = new HBox(10, depositButton, withdrawButton);
statusLabel = new Label("Enter an amount to begin.");
VBox root = new VBox(12, titleLabel, holderLabel, balanceCaption, balanceLabel, amountField, actionButtons, statusLabel);
root.setPadding(new Insets(20));
refreshDashboard();
stage.setTitle("NextWork Bank");
stage.setScene(new Scene(root, 480, 400));
stage.show();
}
private void processTransaction(boolean deposit) {
try {
BigDecimal amount = new BigDecimal(amountField.getText().trim());
if (deposit) {
account.deposit(amount);
showSuccess("Deposit complete.");
} else {
account.withdraw(amount);
showSuccess("Withdrawal complete.");
}
amountField.clear();
refreshDashboard();
} catch (NumberFormatException exception) {
showError("Enter a valid number, such as 25.00.");
} catch (InvalidTransactionException exception) {
showError(exception.getMessage());
}
}
private void refreshDashboard() {
balanceLabel.setText(formatMoney(account.getBalance()));
}
private void showSuccess(String message) {
statusLabel.setText(message);
statusLabel.setStyle("-fx-text-fill: #087f5b;");
}
private void showError(String message) {
statusLabel.setText(message);
statusLabel.setStyle("-fx-text-fill: #c92a2a;");
}
private String formatMoney(BigDecimal value) {
return "$" + value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
public static void main(String[] args) {
launch(args);
}
}
What should this file contain?
This complete file catches number-format errors plus rejected transactions. It sends successful and failed outcomes to separate status helpers.
Your account now rejects unsafe transactions without losing its valid state. Next up, you will record every successful balance change in a visible transaction ledger.
Add the Transaction Ledger
Your JavaFX dashboard now protects its balance from invalid input. The account can change safely.
A changing balance hides the path that produced it. In this step, an ordered List will capture every successful transaction for the dashboard to show.
In this step, get ready to:
- Define a transaction record for each successful operation.
- Store ordered transaction snapshots inside the account.
- Display the ledger below the account controls.
Create transaction records
A ledger entry needs enough data to explain one balance change. TransactionType identifies the operation that produced each entry.
- Expand src/main/java/com/nextwork/bank in the IntelliJ IDEA project tree.
- Create a Java enum named TransactionType inside com.nextwork.bank.
- Replace the generated content in TransactionType.java with this code:
package com.nextwork.bank;
public enum TransactionType {
DEPOSIT,
WITHDRAWAL
}
What does this enum do?
- TransactionType defines the complete set of operation categories used by the ledger.
- DEPOSIT represents money entering the account.
- WITHDRAWAL represents money leaving the account.
- Save TransactionType.java.
- Confirm that TransactionType.java is listed under com.nextwork.bank in the project tree.
Cannot find the enum?
- Check that TransactionType.java sits inside the com.nextwork.bank package.
- Check that the file contains public enum TransactionType.
Help me fix the location or declaration of TransactionType.java.
✔️ Awesome, I've got everything!
Your transaction categories are ready. Save TransactionType.java before continuing.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
public enum TransactionType {
DEPOSIT,
WITHDRAWAL
}
Compare this reference with your complete TransactionType.java file.
The enum identifies the operation. A Transaction object captures the amount plus the balance produced by that operation.
- Create a Java class named Transaction inside com.nextwork.bank.
- Replace the generated content in Transaction.java with this code:
package com.nextwork.bank;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Transaction {
private final TransactionType type;
private final BigDecimal amount;
private final BigDecimal balanceAfter;
public Transaction(TransactionType type, BigDecimal amount, BigDecimal balanceAfter) {
this.type = type;
this.amount = amount;
this.balanceAfter = balanceAfter;
}
public TransactionType getType() {
return type;
}
@Override
public String toString() {
String sign = type == TransactionType.DEPOSIT ? "+" : "-";
return type + " | " + sign + formatMoney(amount)
+ " | Balance: " + formatMoney(balanceAfter);
}
private String formatMoney(BigDecimal value) {
return "$" + value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
}
What does this class store?
- type records whether the transaction is a deposit or withdrawal.
- amount stores the value that moved.
- balanceAfter captures the resulting account balance.
- toString() formats the exact text that the ledger displays.
- Save Transaction.java.
- Expand Plugins in the Maven tool window.
- Run javafx:run from the JavaFX plugin.
The dashboard opens with its existing $100.00 balance. That successful launch proves both transaction classes compile inside the project.
Does the project fail to compile?
- Check that both new files begin with package com.nextwork.bank;.
- Check that every reference to TransactionType uses the same capital letters.
Help me debug why my new transaction classes do not compile.
✔️ Awesome, I've got everything!
Your transaction record now compiles. Save Transaction.java before updating the account.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Transaction {
private final TransactionType type;
private final BigDecimal amount;
private final BigDecimal balanceAfter;
public Transaction(TransactionType type, BigDecimal amount, BigDecimal balanceAfter) {
this.type = type;
this.amount = amount;
this.balanceAfter = balanceAfter;
}
public TransactionType getType() {
return type;
}
@Override
public String toString() {
String sign = type == TransactionType.DEPOSIT ? "+" : "-";
return type + " | " + sign + formatMoney(amount)
+ " | Balance: " + formatMoney(balanceAfter);
}
private String formatMoney(BigDecimal value) {
return "$" + value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
}
Compare this reference with your complete Transaction.java file.
Store transactions in Account
The account owns the balance change. It should also own the history created by that change.
An ArrayList keeps the entries in order. List.copyOf(transactions) gives the interface a read-only snapshot of that history.
- Return to Account.java.
- Replace everything from package com.nextwork.bank; through the closing brace of getBalance() with this block:
package com.nextwork.bank;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class Account {
private final String accountHolder;
private BigDecimal balance;
private final List<Transaction> transactions = new ArrayList<>();
public Account(String accountHolder, BigDecimal openingBalance) {
if (openingBalance == null || openingBalance.signum() < 0) {
throw new IllegalArgumentException("Opening balance cannot be negative.");
}
this.accountHolder = accountHolder;
this.balance = openingBalance;
}
public String getAccountHolder() {
return accountHolder;
}
public BigDecimal getBalance() {
return balance;
}
public List<Transaction> getTransactions() {
return List.copyOf(transactions);
}
How is the ledger protected?
- transactions is private because transaction history belongs to the account.
- new ArrayList<>() starts each account with an empty ordered history.
- getTransactions() returns a copied list that outside code cannot modify.
- Save Account.java.
- Close the running application window.
- Run javafx:run from the JavaFX plugin.
The dashboard opens with $100.00 available. The new collection now compiles without changing the account's existing behaviour.
Seeing collection errors?
- Check that java.util.ArrayList is imported at the top of Account.java.
- Check that java.util.List is imported at the top of Account.java.
Help me debug the transaction collection in Account.java.
The collection exists. Each successful account method now needs to add its own snapshot after changing the balance.
- Find this current deposit() method in Account.java:
public void deposit(BigDecimal amount) throws InvalidTransactionException {
validateAmount(amount);
balance = balance.add(amount);
}
What is missing here?
This version validates the amount before changing the balance. It does not preserve a record of the successful deposit.
- Replace the current deposit() method with this version:
public void deposit(BigDecimal amount) throws InvalidTransactionException {
validateAmount(amount);
balance = balance.add(amount);
transactions.add(0, new Transaction(TransactionType.DEPOSIT, amount, balance));
}
How is the deposit recorded?
The method changes the balance before creating the transaction. The recorded balanceAfter value therefore captures the new total.
Adding at index 0 places the newest deposit at the front of the ledger.
- Save Account.java.
- Close the running application window.
- Run javafx:run from the JavaFX plugin.
- Enter 25.00 in the amount field.
- Click Deposit.
The balance reads $125.00. This confirms the deposit still succeeds after creating its transaction snapshot.
Does the deposit fail?
- Check that transactions.add(...) appears after balance = balance.add(amount);.
- Check that the transaction uses TransactionType.DEPOSIT.
Help me debug why the updated deposit method does not run.
- Find this current withdraw() method in Account.java:
public void withdraw(BigDecimal amount) throws InvalidTransactionException {
validateAmount(amount);
if (amount.compareTo(balance) > 0) {
throw new InvalidTransactionException("Insufficient funds for this withdrawal.");
}
balance = balance.subtract(amount);
}
What is missing from withdrawals?
This method protects the balance before subtracting money. A successful withdrawal still disappears from view once the current balance changes again.
- Replace the current withdraw() method with this version:
public void withdraw(BigDecimal amount) throws InvalidTransactionException {
validateAmount(amount);
if (amount.compareTo(balance) > 0) {
throw new InvalidTransactionException("Insufficient funds for this withdrawal.");
}
balance = balance.subtract(amount);
transactions.add(0, new Transaction(TransactionType.WITHDRAWAL, amount, balance));
}
How is the withdrawal recorded?
The transaction is created only after every validation check passes. Rejected withdrawals never enter the ledger.
TransactionType.WITHDRAWAL gives the entry its withdrawal label plus its minus sign.
- Save Account.java.
- Close the running application window.
- Run javafx:run from the JavaFX plugin.
- Enter 10.00 in the amount field.
- Click Withdraw.
The balance reads $90.00. This proves the protected withdrawal still works after recording its snapshot.
Does the withdrawal fail?
- Check that the transaction line appears after balance = balance.subtract(amount);.
- Check that the transaction uses TransactionType.WITHDRAWAL.
Help me debug why the updated withdrawal method does not run.
✔️ Awesome, I've got everything!
Your account now records every successful balance change. Save Account.java before building the visible ledger.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class Account {
private final String accountHolder;
private BigDecimal balance;
private final List<Transaction> transactions = new ArrayList<>();
public Account(String accountHolder, BigDecimal openingBalance) {
if (openingBalance == null || openingBalance.signum() < 0) {
throw new IllegalArgumentException("Opening balance cannot be negative.");
}
this.accountHolder = accountHolder;
this.balance = openingBalance;
}
public String getAccountHolder() {
return accountHolder;
}
public BigDecimal getBalance() {
return balance;
}
public List<Transaction> getTransactions() {
return List.copyOf(transactions);
}
public void deposit(BigDecimal amount) throws InvalidTransactionException {
validateAmount(amount);
balance = balance.add(amount);
transactions.add(0, new Transaction(TransactionType.DEPOSIT, amount, balance));
}
public void withdraw(BigDecimal amount) throws InvalidTransactionException {
validateAmount(amount);
if (amount.compareTo(balance) > 0) {
throw new InvalidTransactionException("Insufficient funds for this withdrawal.");
}
balance = balance.subtract(amount);
transactions.add(0, new Transaction(TransactionType.WITHDRAWAL, amount, balance));
}
private void validateAmount(BigDecimal amount) throws InvalidTransactionException {
if (amount == null || amount.signum() <= 0) {
throw new InvalidTransactionException("Enter an amount greater than zero.");
}
if (amount.scale() > 2) {
throw new InvalidTransactionException("Use no more than two decimal places.");
}
}
}
Compare this reference with your complete Account.java file.
Display the ledger in JavaFX
The account now remembers every successful operation. A ListView turns those stored objects into visible rows beneath the controls.
- Return to BankingApplication.java.
- Replace everything from package com.nextwork.bank; through private Label statusLabel; with this block:
package com.nextwork.bank;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BankingApplication extends Application {
private final Account account = new Account("Alex Morgan", new BigDecimal("100.00"));
private Label balanceLabel;
private TextField amountField;
private Label statusLabel;
private ListView<Transaction> transactionList;
What does this application state do?
- ListView provides the visual control for the ledger rows.
- Priority lets the ledger use extra vertical space in the window.
- transactionList keeps the list available to both setup code and refresh code.
- Save BankingApplication.java.
- Close the running application window.
- Run javafx:run from the JavaFX plugin.
The dashboard opens with its existing controls. This confirms the new imports plus the ledger field compile successfully.
Seeing missing type errors?
- Check that javafx.scene.control.ListView is imported.
- Check that javafx.scene.layout.Priority is imported.
Help me fix the ListView imports or transactionList field.
The ledger also needs room in the window. The next layout update gives the account details a clearer hierarchy before adding the history list.
- Replace the code from Label titleLabel = new Label("NextWork Bank"); through balanceLabel = new Label(); with this block:
Label titleLabel = new Label("NextWork Bank");
titleLabel.setStyle("-fx-font-size: 24px; -fx-font-weight: bold;");
Label holderLabel = new Label("Account holder: " + account.getAccountHolder());
Label balanceCaption = new Label("Available balance");
balanceLabel = new Label();
balanceLabel.setStyle("-fx-font-size: 32px; -fx-font-weight: bold; -fx-text-fill: #1f6feb;");
What does this styling change?
The title becomes the clearest heading in the window. The balance gains stronger size plus colour so each transaction result is easy to spot.
- Save BankingApplication.java.
- Close the running application window.
- Run javafx:run from the JavaFX plugin.
The window now shows a larger title plus a blue $100.00 balance. The visual hierarchy leaves space for the ledger below.
Do the labels look unchanged?
- Check that each setStyle(...) line appears immediately after its matching label is created.
- Check that every quoted style value ends with a closing quotation mark.
Help me debug the title or balance styling in BankingApplication.java.
- Replace the code from statusLabel = new Label("Enter an amount to begin."); through stage.show(); with this block:
statusLabel = new Label("Enter an amount to begin.");
statusLabel.setStyle("-fx-text-fill: #475569;");
Label historyLabel = new Label("Transaction history");
historyLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
transactionList = new ListView<>();
transactionList.setPlaceholder(new Label("No transactions yet."));
VBox.setVgrow(transactionList, Priority.ALWAYS);
VBox root = new VBox(
12,
titleLabel,
holderLabel,
balanceCaption,
balanceLabel,
amountField,
actionButtons,
statusLabel,
historyLabel,
transactionList
);
root.setPadding(new Insets(20));
root.setStyle("-fx-background-color: #f4f7fb;");
refreshDashboard();
stage.setTitle("NextWork Bank");
stage.setScene(new Scene(root, 480, 540));
stage.show();
How does the ledger join the window?
- historyLabel identifies the new section.
- transactionList displays a row for each transaction object.
- setPlaceholder(...) explains why the list is empty before the first transaction.
- VBox.setVgrow(...) lets the ledger fill the available vertical space.
- Save BankingApplication.java.
- Close the running application window.
- Run javafx:run from the JavaFX plugin.
The taller window now includes Transaction history above an empty list. The list displays No transactions yet..
Do you see the empty ledger?
- Check that historyLabel appears before transactionList in the VBox.
- Check that the scene height is 540.
Help me debug why the transaction history section is missing.
The control is visible. refreshDashboard() now needs to copy the account's latest transaction snapshot into it.
- Find this current refreshDashboard() method:
private void refreshDashboard() {
balanceLabel.setText(formatMoney(account.getBalance()));
}
What does the current refresh cover?
The current method updates only the balance label. The visible ledger therefore has no transaction data yet.
- Replace the current refreshDashboard() method with this version:
private void refreshDashboard() {
balanceLabel.setText(formatMoney(account.getBalance()));
transactionList.getItems().setAll(account.getTransactions());
}
How does the ledger refresh?
getTransactions() supplies the account's copied transaction list. setAll(...) replaces the visible rows with that latest snapshot.
processTransaction() already calls refreshDashboard() after every successful operation. The balance plus the ledger therefore refresh together.
✔️ Awesome, I've got everything!
Your JavaFX interface is connected to the account's transaction history. Save BankingApplication.java before the final check.
ⓧ I'd like to double check the full code
package com.nextwork.bank;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BankingApplication extends Application {
private final Account account = new Account("Alex Morgan", new BigDecimal("100.00"));
private Label balanceLabel;
private TextField amountField;
private Label statusLabel;
private ListView<Transaction> transactionList;
@Override
public void start(Stage stage) {
Label titleLabel = new Label("NextWork Bank");
titleLabel.setStyle("-fx-font-size: 24px; -fx-font-weight: bold;");
Label holderLabel = new Label("Account holder: " + account.getAccountHolder());
Label balanceCaption = new Label("Available balance");
balanceLabel = new Label();
balanceLabel.setStyle("-fx-font-size: 32px; -fx-font-weight: bold; -fx-text-fill: #1f6feb;");
amountField = new TextField();
amountField.setPromptText("Amount, for example 25.00");
Button depositButton = new Button("Deposit");
Button withdrawButton = new Button("Withdraw");
depositButton.setOnAction(event -> processTransaction(true));
withdrawButton.setOnAction(event -> processTransaction(false));
HBox actionButtons = new HBox(10, depositButton, withdrawButton);
statusLabel = new Label("Enter an amount to begin.");
statusLabel.setStyle("-fx-text-fill: #475569;");
Label historyLabel = new Label("Transaction history");
historyLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
transactionList = new ListView<>();
transactionList.setPlaceholder(new Label("No transactions yet."));
VBox.setVgrow(transactionList, Priority.ALWAYS);
VBox root = new VBox(
12,
titleLabel,
holderLabel,
balanceCaption,
balanceLabel,
amountField,
actionButtons,
statusLabel,
historyLabel,
transactionList
);
root.setPadding(new Insets(20));
root.setStyle("-fx-background-color: #f4f7fb;");
refreshDashboard();
stage.setTitle("NextWork Bank");
stage.setScene(new Scene(root, 480, 540));
stage.show();
}
private void processTransaction(boolean deposit) {
try {
BigDecimal amount = new BigDecimal(amountField.getText().trim());
if (deposit) {
account.deposit(amount);
showSuccess("Deposit complete.");
} else {
account.withdraw(amount);
showSuccess("Withdrawal complete.");
}
amountField.clear();
refreshDashboard();
} catch (NumberFormatException exception) {
showError("Enter a valid number, such as 25.00.");
} catch (InvalidTransactionException exception) {
showError(exception.getMessage());
}
}
private void refreshDashboard() {
balanceLabel.setText(formatMoney(account.getBalance()));
transactionList.getItems().setAll(account.getTransactions());
}
private void showSuccess(String message) {
statusLabel.setText(message);
statusLabel.setStyle("-fx-text-fill: #087f5b;");
}
private void showError(String message) {
statusLabel.setText(message);
statusLabel.setStyle("-fx-text-fill: #c92a2a;");
}
private String formatMoney(BigDecimal value) {
return "$" + value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
public static void main(String[] args) {
launch(args);
}
}
Compare this reference with your complete BankingApplication.java file.
- Save BankingApplication.java.
- Close the running application window.
- Expand Plugins in the Maven tool window.
Before you run the dashboard, what balance should remain after depositing 25.00 before withdrawing 10.00?
- Run javafx:run from the JavaFX plugin.
- Enter 25.00 in the amount field.
- Click Deposit.
- Enter 10.00 in the amount field.
- Click Withdraw.
The balance reads $115.00. The newest WITHDRAWAL entry appears above the DEPOSIT entry.
That is the full feedback loop working. Every successful click now leaves a visible record.
Is the ledger empty?
- Check that refreshDashboard() calls transactionList.getItems().setAll(account.getTransactions());.
- Check that both account methods add a Transaction after changing the balance.
- Restart the application to reset the opening balance to $100.00 before repeating the final check.
Help me debug why my transaction ledger is empty or showing the wrong balance.
Secret mission
Filter the Transaction History
A growing transaction history becomes harder to scan. Add All, Deposits, and Withdrawals controls that filter the visible ledger without changing the account balance or stored transactions.
Clean Up Your Resources
Clean Up Your Resources
Your local JavaFX banking dashboard has no ongoing service costs. Decide whether to keep it available, close it for later, or delete its files.
Resources you used:
- The local banking-dashboard Maven project folder.
- The NextWork Bank desktop application process when it is running.
Keep everything running
No action needed. Choose this while you are still changing the dashboard.
- Keep IntelliJ IDEA open with banking-dashboard loaded.
- Leave NextWork Bank open while you are using the simulator.
- Continue running javafx:run from the Maven tool window when you want to test changes.
Pause - I'll come back to this later
Shut down the desktop app to free memory while keeping the project available for later. Your files remain on your Mac at no cost.
- Close the NextWork Bank window if it is open.
- Confirm the Run tool window no longer shows an active JavaFX process.
- Quit IntelliJ IDEA if you do not need the editor open.
- Open the existing banking-dashboard project in IntelliJ IDEA when you return.
Delete - I don't want to use this again
This option removes the local project when you no longer need its code. It permanently deletes the files inside banking-dashboard.
Save What You Need
Deleting the folder is permanent.
- Copy any code you want to keep to another location before continuing.
- Close the NextWork Bank window if it is open.
- Quit IntelliJ IDEA so the project folder is no longer in use.
- Press Cmd+Space to open macOS search.
- Type Terminal into the search field.
- Press Enter to open Terminal.
- Remove the banking-dashboard folder by running these commands:
cd ~/Desktop
rm -rf banking-dashboard
ls
What should you see?
The first line moves Terminal to your Desktop. The second line removes only the banking-dashboard folder.
The final line lists the remaining Desktop items. You should not see banking-dashboard in that list.
Still See the Project Folder?
A remaining folder means Terminal targeted a different location. Help me find and safely delete my local banking-dashboard folder on macOS.
Nice Work!
Nice Work!
You did it! Your JavaFX banking dashboard now runs from IntelliJ IDEA as an interactive desktop application.
You can change the balance through valid transactions. Invalid input leaves the account untouched. The visible ledger shows how each successful transaction changed the balance.
You've learned how to:
- Build a modular desktop application with Maven. Launch the dashboard from IntelliJ IDEA.
- Model account state through encapsulation. Use BigDecimal for predictable money values. Protect the balance with model-level validation plus a checked exception.
- Connect JavaFX controls to account methods through event handlers. Display successful transactions in a ListView. Protect the underlying ledger through an unmodifiable copy returned by getTransactions().
- Secret Mission: Add a transaction-type filter above the ledger. Each selection changes the visible entries while preserving the account balance plus the stored transaction data.
Ready to quiz yourself?