google-site-verification: google61fe8ba583a51912.html
Expense Tracker Desktop App in Java

A comprehensive, user-friendly desktop expense tracking application built with Java Swing and SQLite database.

Project Structure

ExpenseTracker/ ├── src/ │ ├── com/expensetracker/ │ │ ├── models/ │ │ ├── dao/ │ │ ├── gui/ │ │ ├── services/ │ │ └── utils/ │ └── resources/ ├── lib/ └── database/

Maven Dependencies

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>com.expensetracker</groupId> <artifactId>expense-tracker</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- SQLite Database --> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.42.0.0</version> </dependency> <!-- JFreeChart for Charts --> <dependency> <groupId>org.jfree</groupId> <artifactId>jfreechart</artifactId> <version>1.5.3</version> </dependency> <!-- Apache Commons for Utilities --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency> <!-- JUnit for Testing --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <!-- Mockito for Testing --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.2.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.3.0</version> <configuration> <archive> <manifest> <mainClass>com.expensetracker.ExpenseTrackerApp</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project>

Domain Models

package com.expensetracker.models; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Objects; public class Expense { private Integer id; private BigDecimal amount; private String description; private Category category; private LocalDate date; private PaymentMethod paymentMethod; private String notes; private LocalDateTime createdAt; private LocalDateTime updatedAt; public enum PaymentMethod { CASH("Cash"), CREDIT_CARD("Credit Card"), DEBIT_CARD("Debit Card"), BANK_TRANSFER("Bank Transfer"), DIGITAL_WALLET("Digital Wallet"), OTHER("Other"); private final String displayName; PaymentMethod(String displayName) { this.displayName = displayName; } public String getDisplayName() { return displayName; } @Override public String toString() { return displayName; } } // Constructors public Expense() { this.createdAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now(); } public Expense(BigDecimal amount, String description, Category category, LocalDate date, PaymentMethod paymentMethod, String notes) { this(); this.amount = amount; this.description = description; this.category = category; this.date = date; this.paymentMethod = paymentMethod; this.notes = notes; } // Getters and Setters public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } public LocalDate getDate() { return date; } public void setDate(LocalDate date) { this.date = date; } public PaymentMethod getPaymentMethod() { return paymentMethod; } public void setPaymentMethod(PaymentMethod paymentMethod) { this.paymentMethod = paymentMethod; } public String getNotes() { return notes; } public void setNotes(String notes) { this.notes = notes; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Expense expense = (Expense) o; return Objects.equals(id, expense.id); } @Override public int hashCode() { return Objects.hash(id); } @Override public String toString() { return String.format("Expense{id=%d, amount=%s, description='%s', category=%s}", id, amount, description, category); } }
package com.expensetracker.models; import java.util.Objects; public class Category { private Integer id; private String name; private String description; private Double budgetLimit; private String colorCode; // Constructors public Category() {} public Category(String name, String description, Double budgetLimit, String colorCode) { this.name = name; this.description = description; this.budgetLimit = budgetLimit; this.colorCode = colorCode; } public Category(String name) { this(name, null, null, "#3498db"); } // Getters and Setters public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Double getBudgetLimit() { return budgetLimit; } public void setBudgetLimit(Double budgetLimit) { this.budgetLimit = budgetLimit; } public String getColorCode() { return colorCode; } public void setColorCode(String colorCode) { this.colorCode = colorCode; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Category category = (Category) o; return Objects.equals(id, category.id); } @Override public int hashCode() { return Objects.hash(id); } @Override public String toString() { return name; } }

Database Layer

package com.expensetracker.dao; import com.expensetracker.models.Expense; import com.expensetracker.models.Category; import com.expensetracker.utils.DatabaseConnection; import java.sql.*; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; public class ExpenseDAO { public void createTable() { String sql = """ CREATE TABLE IF NOT EXISTS expenses ( id INTEGER PRIMARY KEY AUTOINCREMENT, amount DECIMAL(10,2) NOT NULL, description TEXT NOT NULL, category_id INTEGER NOT NULL, date DATE NOT NULL, payment_method TEXT NOT NULL, notes TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (category_id) REFERENCES categories (id) ) """; try (Connection conn = DatabaseConnection.getConnection(); Statement stmt = conn.createStatement()) { stmt.execute(sql); } catch (SQLException e) { throw new RuntimeException("Error creating expenses table", e); } } public Integer insert(Expense expense) { String sql = """ INSERT INTO expenses (amount, description, category_id, date, payment_method, notes) VALUES (?, ?, ?, ?, ?, ?) """; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { pstmt.setBigDecimal(1, expense.getAmount()); pstmt.setString(2, expense.getDescription()); pstmt.setInt(3, expense.getCategory().getId()); pstmt.setDate(4, Date.valueOf(expense.getDate())); pstmt.setString(5, expense.getPaymentMethod().name()); pstmt.setString(6, expense.getNotes()); int affectedRows = pstmt.executeUpdate(); if (affectedRows == 0) { throw new SQLException("Creating expense failed, no rows affected."); } try (ResultSet generatedKeys = pstmt.getGeneratedKeys()) { if (generatedKeys.next()) { return generatedKeys.getInt(1); } else { throw new SQLException("Creating expense failed, no ID obtained."); } } } catch (SQLException e) { throw new RuntimeException("Error inserting expense", e); } } public List<Expense> findAll() { String sql = """ SELECT e.*, c.id as cat_id, c.name as cat_name, c.description as cat_description, c.budget_limit, c.color_code FROM expenses e JOIN categories c ON e.category_id = c.id ORDER BY e.date DESC, e.created_at DESC """; List<Expense> expenses = new ArrayList<>(); try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql); ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { expenses.add(mapRowToExpense(rs)); } } catch (SQLException e) { throw new RuntimeException("Error fetching expenses", e); } return expenses; } public List<Expense> findByDateRange(LocalDate startDate, LocalDate endDate) { String sql = """ SELECT e.*, c.id as cat_id, c.name as cat_name, c.description as cat_description, c.budget_limit, c.color_code FROM expenses e JOIN categories c ON e.category_id = c.id WHERE e.date BETWEEN ? AND ? ORDER BY e.date DESC, e.created_at DESC """; List<Expense> expenses = new ArrayList<>(); try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setDate(1, Date.valueOf(startDate)); pstmt.setDate(2, Date.valueOf(endDate)); try (ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { expenses.add(mapRowToExpense(rs)); } } } catch (SQLException e) { throw new RuntimeException("Error fetching expenses by date range", e); } return expenses; } public List<Expense> findByCategory(Integer categoryId) { String sql = """ SELECT e.*, c.id as cat_id, c.name as cat_name, c.description as cat_description, c.budget_limit, c.color_code FROM expenses e JOIN categories c ON e.category_id = c.id WHERE e.category_id = ? ORDER BY e.date DESC """; List<Expense> expenses = new ArrayList<>(); try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setInt(1, categoryId); try (ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { expenses.add(mapRowToExpense(rs)); } } } catch (SQLException e) { throw new RuntimeException("Error fetching expenses by category", e); } return expenses; } public boolean update(Expense expense) { String sql = """ UPDATE expenses SET amount = ?, description = ?, category_id = ?, date = ?, payment_method = ?, notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? """; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setBigDecimal(1, expense.getAmount()); pstmt.setString(2, expense.getDescription()); pstmt.setInt(3, expense.getCategory().getId()); pstmt.setDate(4, Date.valueOf(expense.getDate())); pstmt.setString(5, expense.getPaymentMethod().name()); pstmt.setString(6, expense.getNotes()); pstmt.setInt(7, expense.getId()); return pstmt.executeUpdate() > 0; } catch (SQLException e) { throw new RuntimeException("Error updating expense", e); } } public boolean delete(Integer id) { String sql = "DELETE FROM expenses WHERE id = ?"; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setInt(1, id); return pstmt.executeUpdate() > 0; } catch (SQLException e) { throw new RuntimeException("Error deleting expense", e); } } public BigDecimal getTotalExpenses(LocalDate startDate, LocalDate endDate) { String sql = "SELECT SUM(amount) as total FROM expenses WHERE date BETWEEN ? AND ?"; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setDate(1, Date.valueOf(startDate)); pstmt.setDate(2, Date.valueOf(endDate)); try (ResultSet rs = pstmt.executeQuery()) { if (rs.next()) { BigDecimal total = rs.getBigDecimal("total"); return total != null ? total : BigDecimal.ZERO; } } } catch (SQLException e) { throw new RuntimeException("Error calculating total expenses", e); } return BigDecimal.ZERO; } private Expense mapRowToExpense(ResultSet rs) throws SQLException { Expense expense = new Expense(); expense.setId(rs.getInt("id")); expense.setAmount(rs.getBigDecimal("amount")); expense.setDescription(rs.getString("description")); expense.setDate(rs.getDate("date").toLocalDate()); expense.setPaymentMethod(Expense.PaymentMethod.valueOf(rs.getString("payment_method"))); expense.setNotes(rs.getString("notes")); expense.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime()); expense.setUpdatedAt(rs.getTimestamp("updated_at").toLocalDateTime()); // Create category Category category = new Category(); category.setId(rs.getInt("cat_id")); category.setName(rs.getString("cat_name")); category.setDescription(rs.getString("cat_description")); category.setBudgetLimit(rs.getDouble("budget_limit")); category.setColorCode(rs.getString("color_code")); expense.setCategory(category); return expense; } }
package com.expensetracker.dao; import com.expensetracker.models.Category; import com.expensetracker.utils.DatabaseConnection; import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.Optional; public class CategoryDAO { public void createTable() { String sql = """ CREATE TABLE IF NOT EXISTS categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, description TEXT, budget_limit REAL, color_code TEXT DEFAULT '#3498db' ) """; try (Connection conn = DatabaseConnection.getConnection(); Statement stmt = conn.createStatement()) { stmt.execute(sql); } catch (SQLException e) { throw new RuntimeException("Error creating categories table", e); } } public Integer insert(Category category) { String sql = "INSERT INTO categories (name, description, budget_limit, color_code) VALUES (?, ?, ?, ?)"; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { pstmt.setString(1, category.getName()); pstmt.setString(2, category.getDescription()); pstmt.setObject(3, category.getBudgetLimit()); pstmt.setString(4, category.getColorCode()); int affectedRows = pstmt.executeUpdate(); if (affectedRows == 0) { throw new SQLException("Creating category failed, no rows affected."); } try (ResultSet generatedKeys = pstmt.getGeneratedKeys()) { if (generatedKeys.next()) { return generatedKeys.getInt(1); } else { throw new SQLException("Creating category failed, no ID obtained."); } } } catch (SQLException e) { throw new RuntimeException("Error inserting category", e); } } public List<Category> findAll() { String sql = "SELECT * FROM categories ORDER BY name"; List<Category> categories = new ArrayList<>(); try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql); ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { categories.add(mapRowToCategory(rs)); } } catch (SQLException e) { throw new RuntimeException("Error fetching categories", e); } return categories; } public Optional<Category> findById(Integer id) { String sql = "SELECT * FROM categories WHERE id = ?"; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setInt(1, id); try (ResultSet rs = pstmt.executeQuery()) { if (rs.next()) { return Optional.of(mapRowToCategory(rs)); } } } catch (SQLException e) { throw new RuntimeException("Error fetching category by ID", e); } return Optional.empty(); } public Optional<Category> findByName(String name) { String sql = "SELECT * FROM categories WHERE name = ?"; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, name); try (ResultSet rs = pstmt.executeQuery()) { if (rs.next()) { return Optional.of(mapRowToCategory(rs)); } } } catch (SQLException e) { throw new RuntimeException("Error fetching category by name", e); } return Optional.empty(); } public boolean update(Category category) { String sql = "UPDATE categories SET name = ?, description = ?, budget_limit = ?, color_code = ? WHERE id = ?"; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, category.getName()); pstmt.setString(2, category.getDescription()); pstmt.setObject(3, category.getBudgetLimit()); pstmt.setString(4, category.getColorCode()); pstmt.setInt(5, category.getId()); return pstmt.executeUpdate() > 0; } catch (SQLException e) { throw new RuntimeException("Error updating category", e); } } public boolean delete(Integer id) { String sql = "DELETE FROM categories WHERE id = ?"; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setInt(1, id); return pstmt.executeUpdate() > 0; } catch (SQLException e) { throw new RuntimeException("Error deleting category", e); } } private Category mapRowToCategory(ResultSet rs) throws SQLException { Category category = new Category(); category.setId(rs.getInt("id")); category.setName(rs.getString("name")); category.setDescription(rs.getString("description")); category.setBudgetLimit(rs.getDouble("budget_limit")); if (rs.wasNull()) { category.setBudgetLimit(null); } category.setColorCode(rs.getString("color_code")); return category; } }

Database Connection Utility

package com.expensetracker.utils; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DatabaseConnection { private static final String URL = "jdbc:sqlite:expense_tracker.db"; private static Connection connection = null; private DatabaseConnection() { // Private constructor to prevent instantiation } public static Connection getConnection() { if (connection == null) { try { connection = DriverManager.getConnection(URL); // Enable foreign keys try (var stmt = connection.createStatement()) { stmt.execute("PRAGMA foreign_keys = ON"); } } catch (SQLException e) { throw new RuntimeException("Error connecting to database", e); } } return connection; } public static void closeConnection() { if (connection != null) { try { connection.close(); connection = null; } catch (SQLException e) { System.err.println("Error closing database connection: " + e.getMessage()); } } } public static void initializeDatabase() { CategoryDAO categoryDAO = new CategoryDAO(); ExpenseDAO expenseDAO = new ExpenseDAO(); categoryDAO.createTable(); expenseDAO.createTable(); // Insert default categories if they don't exist insertDefaultCategories(categoryDAO); } private static void insertDefaultCategories(CategoryDAO categoryDAO) { String[] defaultCategories = { "Food & Dining", "Transportation", "Shopping", "Entertainment", "Bills & Utilities", "Healthcare", "Education", "Travel", "Other" }; String[] colors = { "#e74c3c", "#3498db", "#9b59b6", "#f1c40f", "#1abc9c", "#e67e22", "#34495e", "#d35400", "#95a5a6" }; for (int i = 0; i < defaultCategories.length; i++) { String categoryName = defaultCategories[i]; if (categoryDAO.findByName(categoryName).isEmpty()) { Category category = new Category(categoryName, null, null, colors[i]); categoryDAO.insert(category); } } } }

Service Layer

package com.expensetracker.services; import com.expensetracker.models.Expense; import com.expensetracker.models.Category; import com.expensetracker.dao.ExpenseDAO; import com.expensetracker.dao.CategoryDAO; import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class ExpenseService { private final ExpenseDAO expenseDAO; private final CategoryDAO categoryDAO; public ExpenseService() { this.expenseDAO = new ExpenseDAO(); this.categoryDAO = new CategoryDAO(); } public void addExpense(Expense expense) { if (expense.getAmount().compareTo(BigDecimal.ZERO) <= 0) { throw new IllegalArgumentException("Expense amount must be positive"); } if (expense.getDate().isAfter(LocalDate.now())) { throw new IllegalArgumentException("Expense date cannot be in the future"); } expenseDAO.insert(expense); } public List<Expense> getAllExpenses() { return expenseDAO.findAll(); } public List<Expense> getExpensesByDateRange(LocalDate startDate, LocalDate endDate) { if (startDate.isAfter(endDate)) { throw new IllegalArgumentException("Start date cannot be after end date"); } return expenseDAO.findByDateRange(startDate, endDate); } public List<Expense> getExpensesByCategory(Integer categoryId) { return expenseDAO.findByCategory(categoryId); } public boolean updateExpense(Expense expense) { return expenseDAO.update(expense); } public boolean deleteExpense(Integer expenseId) { return expenseDAO.delete(expenseId); } public BigDecimal getTotalExpenses(LocalDate startDate, LocalDate endDate) { return expenseDAO.getTotalExpenses(startDate, endDate); } public Map<Category, BigDecimal> getExpensesByCategory(LocalDate startDate, LocalDate endDate) { List<Expense> expenses = getExpensesByDateRange(startDate, endDate); return expenses.stream() .collect(Collectors.groupingBy( Expense::getCategory, Collectors.reducing( BigDecimal.ZERO, Expense::getAmount, BigDecimal::add ) )); } public List<Category> getAllCategories() { return categoryDAO.findAll(); } public void addCategory(Category category) { if (categoryDAO.findByName(category.getName()).isPresent()) { throw new IllegalArgumentException("Category with this name already exists"); } categoryDAO.insert(category); } public boolean updateCategory(Category category) { return categoryDAO.update(category); } public boolean deleteCategory(Integer categoryId) { // Check if category has expenses List<Expense> categoryExpenses = expenseDAO.findByCategory(categoryId); if (!categoryExpenses.isEmpty()) { throw new IllegalStateException("Cannot delete category with existing expenses"); } return categoryDAO.delete(categoryId); } }

User Interface - Main Application

package com.expensetracker.gui; import com.expensetracker.models.Expense; import com.expensetracker.models.Category; import com.expensetracker.services.ExpenseService; import com.expensetracker.utils.DatabaseConnection; import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; import java.awt.event.ActionEvent; import java.math.BigDecimal; import java.time.LocalDate; import java.time.YearMonth; import java.util.List; public class ExpenseTrackerApp extends JFrame { private final ExpenseService expenseService; private JTable expensesTable; private DefaultTableModel tableModel; private JLabel totalLabel; private JComboBox<String> monthComboBox; private JComboBox<Integer> yearComboBox; public ExpenseTrackerApp() { this.expenseService = new ExpenseService(); initializeUI(); loadExpenses(); loadFilterData(); } private void initializeUI() { setTitle("Personal Expense Tracker"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(1000, 700); setLocationRelativeTo(null); // Create main panel with border layout JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // Create header panel JPanel headerPanel = createHeaderPanel(); mainPanel.add(headerPanel, BorderLayout.NORTH); // Create expenses table JPanel tablePanel = createTablePanel(); mainPanel.add(tablePanel, BorderLayout.CENTER); // Create summary panel JPanel summaryPanel = createSummaryPanel(); mainPanel.add(summaryPanel, BorderLayout.SOUTH); add(mainPanel); } private JPanel createHeaderPanel() { JPanel headerPanel = new JPanel(new BorderLayout()); // Title JLabel titleLabel = new JLabel("Personal Expense Tracker", JLabel.CENTER); titleLabel.setFont(new Font("Arial", Font.BOLD, 24)); titleLabel.setForeground(new Color(44, 62, 80)); headerPanel.add(titleLabel, BorderLayout.NORTH); // Toolbar JPanel toolbarPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); JButton addButton = new JButton("Add Expense"); addButton.addActionListener(this::showAddExpenseDialog); JButton editButton = new JButton("Edit Expense"); editButton.addActionListener(this::editSelectedExpense); JButton deleteButton = new JButton("Delete Expense"); deleteButton.addActionListener(this::deleteSelectedExpense); JButton categoriesButton = new JButton("Manage Categories"); categoriesButton.addActionListener(this::showCategoriesDialog); JButton chartsButton = new JButton("View Charts"); chartsButton.addActionListener(this::showChartsDialog); toolbarPanel.add(addButton); toolbarPanel.add(editButton); toolbarPanel.add(deleteButton); toolbarPanel.add(categoriesButton); toolbarPanel.add(chartsButton); // Filter panel JPanel filterPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); filterPanel.add(new JLabel("Filter by:")); monthComboBox = new JComboBox<>(new String[]{ "All Months", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }); monthComboBox.addActionListener(e -> filterExpenses()); yearComboBox = new JComboBox<>(); yearComboBox.addActionListener(e -> filterExpenses()); JButton clearFilterButton = new JButton("Clear Filter"); clearFilterButton.addActionListener(e -> clearFilters()); filterPanel.add(monthComboBox); filterPanel.add(yearComboBox); filterPanel.add(clearFilterButton); JPanel combinedToolbar = new JPanel(new BorderLayout()); combinedToolbar.add(toolbarPanel, BorderLayout.NORTH); combinedToolbar.add(filterPanel, BorderLayout.SOUTH); headerPanel.add(combinedToolbar, BorderLayout.CENTER); return headerPanel; } private JPanel createTablePanel() { JPanel tablePanel = new JPanel(new BorderLayout()); // Create table model String[] columnNames = { "ID", "Date", "Description", "Amount", "Category", "Payment Method", "Notes" }; tableModel = new DefaultTableModel(columnNames, 0) { @Override public boolean isCellEditable(int row, int column) { return false; // Make table non-editable } }; expensesTable = new JTable(tableModel); expensesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); expensesTable.getColumnModel().getColumn(0).setMaxWidth(50); // ID column expensesTable.getColumnModel().getColumn(1).setMaxWidth(100); // Date column expensesTable.getColumnModel().getColumn(3).setMaxWidth(100); // Amount column expensesTable.getColumnModel().getColumn(4).setMaxWidth(120); // Category column expensesTable.getColumnModel().getColumn(5).setMaxWidth(120); // Payment Method column JScrollPane scrollPane = new JScrollPane(expensesTable); tablePanel.add(scrollPane, BorderLayout.CENTER); return tablePanel; } private JPanel createSummaryPanel() { JPanel summaryPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); summaryPanel.setBorder(BorderFactory.createTitledBorder("Summary")); totalLabel = new JLabel("Total Expenses: $0.00"); totalLabel.setFont(new Font("Arial", Font.BOLD, 16)); totalLabel.setForeground(new Color(231, 76, 60)); summaryPanel.add(totalLabel); return summaryPanel; } private void loadExpenses() { loadExpenses(LocalDate.of(1970, 1, 1), LocalDate.now()); } private void loadExpenses(LocalDate startDate, LocalDate endDate) { tableModel.setRowCount(0); List<Expense> expenses = expenseService.getExpensesByDateRange(startDate, endDate); for (Expense expense : expenses) { Object[] row = { expense.getId(), expense.getDate(), expense.getDescription(), String.format("$%.2f", expense.getAmount()), expense.getCategory().getName(), expense.getPaymentMethod().getDisplayName(), expense.getNotes() != null ? expense.getNotes() : "" }; tableModel.addRow(row); } updateTotalLabel(startDate, endDate); } private void updateTotalLabel(LocalDate startDate, LocalDate endDate) { BigDecimal total = expenseService.getTotalExpenses(startDate, endDate); totalLabel.setText(String.format("Total Expenses: $%.2f", total)); } private void loadFilterData() { // Populate years (current year and 5 years back) int currentYear = LocalDate.now().getYear(); yearComboBox.removeAllItems(); yearComboBox.addItem(0); // 0 represents all years for (int year = currentYear; year >= currentYear - 5; year--) { yearComboBox.addItem(year); } } private void filterExpenses() { int selectedMonth = monthComboBox.getSelectedIndex(); // 0 = All Months int selectedYear = (Integer) yearComboBox.getSelectedItem(); LocalDate startDate, endDate; if (selectedYear == 0) { // All years startDate = LocalDate.of(1970, 1, 1); endDate = LocalDate.now(); } else if (selectedMonth == 0) { // All months of selected year startDate = LocalDate.of(selectedYear, 1, 1); endDate = LocalDate.of(selectedYear, 12, 31); } else { // Specific month and year YearMonth yearMonth = YearMonth.of(selectedYear, selectedMonth); startDate = yearMonth.atDay(1); endDate = yearMonth.atEndOfMonth(); } loadExpenses(startDate, endDate); } private void clearFilters() { monthComboBox.setSelectedIndex(0); yearComboBox.setSelectedIndex(0); loadExpenses(); } private void showAddExpenseDialog(ActionEvent e) { AddExpenseDialog dialog = new AddExpenseDialog(this, expenseService); dialog.setVisible(true); if (dialog.isSuccess()) { loadExpenses(); } } private void editSelectedExpense(ActionEvent e) { int selectedRow = expensesTable.getSelectedRow(); if (selectedRow == -1) { JOptionPane.showMessageDialog(this, "Please select an expense to edit.", "No Selection", JOptionPane.WARNING_MESSAGE); return; } Integer expenseId = (Integer) tableModel.getValueAt(selectedRow, 0); List<Expense> expenses = expenseService.getAllExpenses(); Expense selectedExpense = expenses.stream() .filter(exp -> exp.getId().equals(expenseId)) .findFirst() .orElse(null); if (selectedExpense != null) { AddExpenseDialog dialog = new AddExpenseDialog(this, expenseService, selectedExpense); dialog.setVisible(true); if (dialog.isSuccess()) { loadExpenses(); } } } private void deleteSelectedExpense(ActionEvent e) { int selectedRow = expensesTable.getSelectedRow(); if (selectedRow == -1) { JOptionPane.showMessageDialog(this, "Please select an expense to delete.", "No Selection", JOptionPane.WARNING_MESSAGE); return; } Integer expenseId = (Integer) tableModel.getValueAt(selectedRow, 0); String description = (String) tableModel.getValueAt(selectedRow, 2); int confirm = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete expense: " + description + "?", "Confirm Delete", JOptionPane.YES_NO_OPTION); if (confirm == JOptionPane.YES_OPTION) { boolean success = expenseService.deleteExpense(expenseId); if (success) { JOptionPane.showMessageDialog(this, "Expense deleted successfully."); loadExpenses(); } else { JOptionPane.showMessageDialog(this, "Failed to delete expense.", "Error", JOptionPane.ERROR_MESSAGE); } } } private void showCategoriesDialog(ActionEvent e) { CategoriesDialog dialog = new CategoriesDialog(this, expenseService); dialog.setVisible(true); loadExpenses(); // Refresh in case categories were changed } private void showChartsDialog(ActionEvent e) { ChartsDialog dialog = new ChartsDialog(this, expenseService); dialog.setVisible(true); } public static void main(String[] args) { // Set system look and feel try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel()); } catch (Exception e) { e.printStackTrace(); } // Initialize database DatabaseConnection.initializeDatabase(); SwingUtilities.invokeLater(() -> { new ExpenseTrackerApp().setVisible(true); }); } }

Dialog Windows

package com.expensetracker.gui; import com.expensetracker.models.Expense; import com.expensetracker.models.Category; import com.expensetracker.services.ExpenseService; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; public class AddExpenseDialog extends JDialog { private final ExpenseService expenseService; private Expense expense; private boolean success = false; private JTextField amountField; private JTextField descriptionField; private JComboBox<Category> categoryComboBox; private JComboBox<Expense.PaymentMethod> paymentMethodComboBox; private JTextArea notesArea; private JSpinner dateSpinner; public AddExpenseDialog(Frame owner, ExpenseService expenseService) { this(owner, expenseService, null); } public AddExpenseDialog(Frame owner, ExpenseService expenseService, Expense expense) { super(owner, expense == null ? "Add New Expense" : "Edit Expense", true); this.expenseService = expenseService; this.expense = expense; initializeUI(); if (expense != null) { populateFields(); } } private void initializeUI() { setSize(400, 400); setLocationRelativeTo(getOwner()); setResizable(false); JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // Form panel JPanel formPanel = new JPanel(new GridLayout(6, 2, 5, 5)); // Amount formPanel.add(new JLabel("Amount:")); amountField = new JTextField(); formPanel.add(amountField); // Description formPanel.add(new JLabel("Description:")); descriptionField = new JTextField(); formPanel.add(descriptionField); // Category formPanel.add(new JLabel("Category:")); categoryComboBox = new JComboBox<>(); loadCategories(); formPanel.add(categoryComboBox); // Payment Method formPanel.add(new JLabel("Payment Method:")); paymentMethodComboBox = new JComboBox<>(Expense.PaymentMethod.values()); formPanel.add(paymentMethodComboBox); // Date formPanel.add(new JLabel("Date:")); SpinnerDateModel dateModel = new SpinnerDateModel(); dateSpinner = new JSpinner(dateModel); JSpinner.DateEditor dateEditor = new JSpinner.DateEditor(dateSpinner, "yyyy-MM-dd"); dateSpinner.setEditor(dateEditor); formPanel.add(dateSpinner); // Notes formPanel.add(new JLabel("Notes:")); notesArea = new JTextArea(3, 20); JScrollPane notesScrollPane = new JScrollPane(notesArea); formPanel.add(notesScrollPane); mainPanel.add(formPanel, BorderLayout.CENTER); // Buttons panel JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JButton saveButton = new JButton("Save"); JButton cancelButton = new JButton("Cancel"); saveButton.addActionListener(this::saveExpense); cancelButton.addActionListener(e -> dispose()); buttonPanel.add(saveButton); buttonPanel.add(cancelButton); mainPanel.add(buttonPanel, BorderLayout.SOUTH); add(mainPanel); } private void loadCategories() { List<Category> categories = expenseService.getAllCategories(); for (Category category : categories) { categoryComboBox.addItem(category); } } private void populateFields() { if (expense != null) { amountField.setText(expense.getAmount().toString()); descriptionField.setText(expense.getDescription()); // Select the category in combo box for (int i = 0; i < categoryComboBox.getItemCount(); i++) { if (categoryComboBox.getItemAt(i).equals(expense.getCategory())) { categoryComboBox.setSelectedIndex(i); break; } } paymentMethodComboBox.setSelectedItem(expense.getPaymentMethod()); dateSpinner.setValue(java.sql.Date.valueOf(expense.getDate())); notesArea.setText(expense.getNotes() != null ? expense.getNotes() : ""); } } private void saveExpense(ActionEvent e) { try { // Validate inputs BigDecimal amount = new BigDecimal(amountField.getText().trim()); if (amount.compareTo(BigDecimal.ZERO) <= 0) { throw new IllegalArgumentException("Amount must be positive"); } String description = descriptionField.getText().trim(); if (description.isEmpty()) { throw new IllegalArgumentException("Description is required"); } Category selectedCategory = (Category) categoryComboBox.getSelectedItem(); if (selectedCategory == null) { throw new IllegalArgumentException("Please select a category"); } Expense.PaymentMethod paymentMethod = (Expense.PaymentMethod) paymentMethodComboBox.getSelectedItem(); java.util.Date selectedDate = (java.util.Date) dateSpinner.getValue(); LocalDate date = selectedDate.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate(); String notes = notesArea.getText().trim(); if (expense == null) { // Create new expense Expense newExpense = new Expense(amount, description, selectedCategory, date, paymentMethod, notes); expenseService.addExpense(newExpense); } else { // Update existing expense expense.setAmount(amount); expense.setDescription(description); expense.setCategory(selectedCategory); expense.setDate(date); expense.setPaymentMethod(paymentMethod); expense.setNotes(notes); expenseService.updateExpense(expense); } success = true; dispose(); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "Please enter a valid amount.", "Invalid Amount", JOptionPane.ERROR_MESSAGE); } catch (IllegalArgumentException ex) { JOptionPane.showMessageDialog(this, ex.getMessage(), "Validation Error", JOptionPane.ERROR_MESSAGE); } catch (Exception ex) { JOptionPane.showMessageDialog(this, "Error saving expense: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } } public boolean isSuccess() { return success; } }

Charts and Reports

package com.expensetracker.gui; import com.expensetracker.models.Category; import com.expensetracker.services.ExpenseService; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.data.general.DefaultPieDataset; import javax.swing.*; import java.awt.*; import java.math.BigDecimal; import java.time.LocalDate; import java.time.YearMonth; import java.util.Map; public class ChartsDialog extends JDialog { private final ExpenseService expenseService; public ChartsDialog(Frame owner, ExpenseService expenseService) { super(owner, "Expense Charts", true); this.expenseService = expenseService; initializeUI(); } private void initializeUI() { setSize(800, 600); setLocationRelativeTo(getOwner()); JTabbedPane tabbedPane = new JTabbedPane(); // Current month chart tabbedPane.addTab("Current Month", createCurrentMonthChart()); // All time chart tabbedPane.addTab("All Time", createAllTimeChart()); add(tabbedPane); } private JPanel createCurrentMonthChart() { YearMonth currentMonth = YearMonth.now(); LocalDate startDate = currentMonth.atDay(1); LocalDate endDate = currentMonth.atEndOfMonth(); Map<Category, BigDecimal> expensesByCategory = expenseService.getExpensesByCategory(startDate, endDate); DefaultPieDataset dataset = new DefaultPieDataset(); for (Map.Entry<Category, BigDecimal> entry : expensesByCategory.entrySet()) { dataset.setValue(entry.getKey().getName(), entry.getValue()); } JFreeChart chart = ChartFactory.createPieChart( "Expenses for " + currentMonth.getMonth() + " " + currentMonth.getYear(), dataset, true, // include legend true, false ); return new ChartPanel(chart); } private JPanel createAllTimeChart() { Map<Category, BigDecimal> expensesByCategory = expenseService.getExpensesByCategory( LocalDate.of(1970, 1, 1), LocalDate.now() ); DefaultPieDataset dataset = new DefaultPieDataset(); for (Map.Entry<Category, BigDecimal> entry : expensesByCategory.entrySet()) { dataset.setValue(entry.getKey().getName(), entry.getValue()); } JFreeChart chart = ChartFactory.createPieChart( "All Time Expenses by Category", dataset, true, true, false ); return new ChartPanel(chart); } }

Key Features

Core Functionality

  1. Expense Management - Add, edit, delete, and view expenses
  2. Category Management - Organize expenses by categories
  3. Date Filtering - Filter expenses by month and year
  4. Payment Methods - Track different payment methods

Data Visualization

  1. Pie Charts - Visualize expenses by category
  2. Summary Statistics - Total expenses and category breakdowns
  3. Tabular Display - Clean, sortable expense table

Data Persistence

  1. SQLite Database - Lightweight, file-based database
  2. Automatic Backup - Database file can be easily backed up
  3. Data Integrity - Foreign key constraints and validation

User Experience

  1. Intuitive UI - Clean, modern interface
  2. Form Validation - Comprehensive input validation
  3. Error Handling - User-friendly error messages
  4. Responsive Design - Adapts to different screen sizes

Usage Instructions

  1. Adding Expenses: Click "Add Expense" and fill in the details
  2. Viewing Expenses: Use the table to browse all expenses
  3. Filtering: Use month/year dropdowns to filter expenses
  4. Charts: Click "View Charts" to see visual breakdowns
  5. Categories: Manage expense categories through "Manage Categories"

Building and Running

# Compile and run with Maven mvn clean compile mvn exec:java -Dexec.mainClass="com.expensetracker.ExpenseTrackerApp" # Or create a JAR file mvn clean package java -jar target/expense-tracker-1.0.0.jar

This expense tracker provides a complete desktop solution for personal finance management with a professional-grade user interface and robust data management capabilities.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper