SETUP
β
What To Do
Go to Spring Initializr
π https://start.spring.io/
Fill in the form like this:
Project: Maven (default, like npm for Java)
Language: Java
Spring Boot Version: Use default
Project Metadata:
Group: com.bankapp
Artifact: bankapp
Name: bankapp
Package name: com.bankapp
Packaging: Jar
Java: 17 or 21 (depending on your local setup)
Add Dependencies:
β Spring Web (like Express.js)
β Spring Data JPA (like Mongoose ORM)
β H2 Database (in-memory DB like MongoDB memory server for testing)
β Spring Boot DevTools (for hot reloads)
Click Generate β Unzip the project.
Open it in IntelliJ IDEA or VS Code with Java extensions.
β
4. Global Exception Handler (Optional but clean)
package com.bankapp.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> handleResourceNotFound(ResourceNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationErrors(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
β
Fix: Add Jakarta Bean Validation Dependency
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
β
1. Add JWT Dependency
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or gson -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
β
1. Add Spring Security Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
β
Step 1: Create the Models (Entities)
USER MODEL
package com.bankapp.model;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.List;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@Column(unique = true)
private String email;
private String password;
private LocalDateTime createdAt;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Account> accounts;
public User() {}
public User(Long id, String name, String email, String password, LocalDateTime createdAt, List<Account> accounts) {
this.id = id;
this.name = name;
this.email = email;
this.password = password;
this.createdAt = createdAt;
this.accounts = accounts;
}
// Getters and setters for all fields
// You can use your IDE to auto-generate them
}
ACCOUNT MODEL
package com.bankapp.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
@Column(unique = true)
private String accountNumber;
private String accountType;
private BigDecimal balance;
private LocalDateTime createdAt;
private String status;
@OneToMany(mappedBy = "fromAccount", cascade = CascadeType.ALL)
private List<Transaction> sentTransactions;
@OneToMany(mappedBy = "toAccount", cascade = CascadeType.ALL)
private List<Transaction> receivedTransactions;
public Account() {}
public Account(Long id, User user, String accountNumber, String accountType, BigDecimal balance,
LocalDateTime createdAt, String status,
List<Transaction> sentTransactions, List<Transaction> receivedTransactions) {
this.id = id;
this.user = user;
this.accountNumber = accountNumber;
this.accountType = accountType;
this.balance = balance;
this.createdAt = createdAt;
this.status = status;
this.sentTransactions = sentTransactions;
this.receivedTransactions = receivedTransactions;
}
// Getters and setters for all fields
}
TRANSACTION MODEL
package com.bankapp.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne
@JoinColumn(name = "from_account_id", nullable = true)
private Account fromAccount;
@ManyToOne
@JoinColumn(name = "to_account_id", nullable = true)
private Account toAccount;
private String type;
private BigDecimal amount;
@Column(length = 500)
private String description;
private LocalDateTime timestamp;
private String status;
public Transaction() {}
public Transaction(Long id, Account fromAccount, Account toAccount, String type, BigDecimal amount,
String description, LocalDateTime timestamp, String status) {
this.id = id;
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.type = type;
this.amount = amount;
this.description = description;
this.timestamp = timestamp;
this.status = status;
}
// Getters and setters for all fields
}
REPOSITORIES
USER REPOSITORY
package com.bankapp.repository;
import com.bankapp.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
}
ACCOUNT REPOSITORY
package com.bankapp.repository;
import com.bankapp.model.Account;
import com.bankapp.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface AccountRepository extends JpaRepository<Account, Long> {
List<Account> findByUser(User user);
Optional<Account> findByAccountNumber(String accountNumber);
}
TRANSACTION REPOSITORY
package com.bankapp.repository;
import com.bankapp.model.Transaction;
import com.bankapp.model.Account;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface TransactionRepository extends JpaRepository<Transaction, Long> {
List<Transaction> findByFromAccount(Account fromAccount);
List<Transaction> findByToAccount(Account toAccount);
}
----- SERVICES -----
USER SERVICE
package com.bankapp.service;
import com.bankapp.model.User;
import java.util.List;
import java.util.Optional;
public interface UserService {
User createUser(User user);
Optional<User> getUserById(Long id);
Optional<User> getUserByEmail(String email);
List<User> getAllUsers();
}
USER SERVICE IMPL
package com.bankapp.service.impl;
import com.bankapp.model.User;
import com.bankapp.repository.UserRepository;
import com.bankapp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User createUser(User user) {
user.setCreatedAt(LocalDateTime.now());
return userRepository.save(user);
}
@Override
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
@Override
public Optional<User> getUserByEmail(String email) {
return userRepository.findByEmail(email);
}
@Override
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
ACCOUNT SERVICE
package com.bankapp.service;
import com.bankapp.model.Account;
import com.bankapp.model.User;
import java.util.List;
import java.util.Optional;
public interface AccountService {
Account createAccount(Account account);
Optional<Account> getAccountById(Long id);
Optional<Account> getByAccountNumber(String accountNumber);
List<Account> getAccountsByUser(User user);
}
ACCOUNT SERVICE IMPL
package com.bankapp.service.impl;
import com.bankapp.model.Account;
import com.bankapp.model.User;
import com.bankapp.repository.AccountRepository;
import com.bankapp.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Service
public class AccountServiceImpl implements AccountService {
private final AccountRepository accountRepository;
@Autowired
public AccountServiceImpl(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Override
public Account createAccount(Account account) {
account.setCreatedAt(LocalDateTime.now());
return accountRepository.save(account);
}
@Override
public Optional<Account> getAccountById(Long id) {
return accountRepository.findById(id);
}
@Override
public Optional<Account> getByAccountNumber(String accountNumber) {
return accountRepository.findByAccountNumber(accountNumber);
}
@Override
public List<Account> getAccountsByUser(User user) {
return accountRepository.findByUser(user);
}
}
TRANSACTION SERVICE
package com.bankapp.service;
import com.bankapp.model.Account;
import com.bankapp.model.Transaction;
import java.util.List;
public interface TransactionService {
Transaction deposit(Account toAccount, double amount, String description);
Transaction withdraw(Account fromAccount, double amount, String description);
Transaction transfer(Account fromAccount, Account toAccount, double amount, String description);
List<Transaction> getTransactionsByAccount(Account account);
}
TRANSACTION SERVICE IMPL
package com.bankapp.service.impl;
import com.bankapp.model.Account;
import com.bankapp.model.Transaction;
import com.bankapp.repository.AccountRepository;
import com.bankapp.repository.TransactionRepository;
import com.bankapp.service.TransactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class TransactionServiceImpl implements TransactionService {
private final TransactionRepository transactionRepository;
private final AccountRepository accountRepository;
@Autowired
public TransactionServiceImpl(TransactionRepository transactionRepository,
AccountRepository accountRepository) {
this.transactionRepository = transactionRepository;
this.accountRepository = accountRepository;
}
@Override
public Transaction deposit(Account toAccount, double amount, String description) {
BigDecimal amt = BigDecimal.valueOf(amount);
toAccount.setBalance(toAccount.getBalance().add(amt));
accountRepository.save(toAccount);
Transaction txn = new Transaction(null, null, toAccount, "DEPOSIT", amt, description,
LocalDateTime.now(), "SUCCESS");
return transactionRepository.save(txn);
}
@Override
public Transaction withdraw(Account fromAccount, double amount, String description) {
BigDecimal amt = BigDecimal.valueOf(amount);
if (fromAccount.getBalance().compareTo(amt) < 0) {
throw new IllegalArgumentException("Insufficient balance");
}
fromAccount.setBalance(fromAccount.getBalance().subtract(amt));
accountRepository.save(fromAccount);
Transaction txn = new Transaction(null, fromAccount, null, "WITHDRAWAL", amt, description,
LocalDateTime.now(), "SUCCESS");
return transactionRepository.save(txn);
}
@Override
public Transaction transfer(Account fromAccount, Account toAccount, double amount, String description) {
BigDecimal amt = BigDecimal.valueOf(amount);
if (fromAccount.getBalance().compareTo(amt) < 0) {
throw new IllegalArgumentException("Insufficient balance");
}
fromAccount.setBalance(fromAccount.getBalance().subtract(amt));
toAccount.setBalance(toAccount.getBalance().add(amt));
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
Transaction txn = new Transaction(null, fromAccount, toAccount, "TRANSFER", amt, description,
LocalDateTime.now(), "SUCCESS");
return transactionRepository.save(txn);
}
@Override
public List<Transaction> getTransactionsByAccount(Account account) {
List<Transaction> sent = transactionRepository.findByFromAccount(account);
List<Transaction> received = transactionRepository.findByToAccount(account);
sent.addAll(received);
return sent;
}
}
--------------- CONTROLLERS --------------------
USER CONTROLLER
package com.bankapp.controller;
import com.bankapp.model.User;
import com.bankapp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/create")
public ResponseEntity<User> createUser(@RequestBody User user) {
User created = userService.createUser(user);
return ResponseEntity.ok(created);
}
@GetMapping("/{id}")
public ResponseEntity<User> getById(@PathVariable Long id) {
return userService.getUserById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping
public ResponseEntity<List<User>> getAll() {
return ResponseEntity.ok(userService.getAllUsers());
}
}
ACCOUNT CONTROLLER
package com.bankapp.controller;
import com.bankapp.model.Account;
import com.bankapp.model.User;
import com.bankapp.service.AccountService;
import com.bankapp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/accounts")
public class AccountController {
private final AccountService accountService;
private final UserService userService;
@Autowired
public AccountController(AccountService accountService, UserService userService) {
this.accountService = accountService;
this.userService = userService;
}
@PostMapping("/create/{userId}")
public ResponseEntity<?> createAccount(@PathVariable Long userId, @RequestBody Account account) {
return userService.getUserById(userId).map(user -> {
account.setUser(user);
Account created = accountService.createAccount(account);
return ResponseEntity.ok(created);
}).orElse(ResponseEntity.badRequest().body("User not found"));
}
@GetMapping("/user/{userId}")
public ResponseEntity<List<Account>> getUserAccounts(@PathVariable Long userId) {
return userService.getUserById(userId).map(user -> {
List<Account> accounts = accountService.getAccountsByUser(user);
return ResponseEntity.ok(accounts);
}).orElse(ResponseEntity.notFound().build());
}
@GetMapping("/{accountNumber}")
public ResponseEntity<?> getAccountByNumber(@PathVariable String accountNumber) {
return accountService.getByAccountNumber(accountNumber)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
TRANSACTION CONTROLLER
package com.bankapp.controller;
import com.bankapp.model.Account;
import com.bankapp.model.Transaction;
import com.bankapp.service.AccountService;
import com.bankapp.service.TransactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/transactions")
public class TransactionController {
private final TransactionService transactionService;
private final AccountService accountService;
@Autowired
public TransactionController(TransactionService transactionService, AccountService accountService) {
this.transactionService = transactionService;
this.accountService = accountService;
}
@PostMapping("/deposit")
public ResponseEntity<?> deposit(@RequestParam String toAccountNumber,
@RequestParam double amount,
@RequestParam String description) {
return accountService.getByAccountNumber(toAccountNumber).map(account -> {
Transaction txn = transactionService.deposit(account, amount, description);
return ResponseEntity.ok(txn);
}).orElse(ResponseEntity.badRequest().body("To Account not found"));
}
@PostMapping("/withdraw")
public ResponseEntity<?> withdraw(@RequestParam String fromAccountNumber,
@RequestParam double amount,
@RequestParam String description) {
return accountService.getByAccountNumber(fromAccountNumber).map(account -> {
try {
Transaction txn = transactionService.withdraw(account, amount, description);
return ResponseEntity.ok(txn);
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}).orElse(ResponseEntity.badRequest().body("From Account not found"));
}
@PostMapping("/transfer")
public ResponseEntity<?> transfer(@RequestParam String fromAccountNumber,
@RequestParam String toAccountNumber,
@RequestParam double amount,
@RequestParam String description) {
var from = accountService.getByAccountNumber(fromAccountNumber);
var to = accountService.getByAccountNumber(toAccountNumber);
if (from.isEmpty() || to.isEmpty()) {
return ResponseEntity.badRequest().body("Invalid account(s)");
}
try {
Transaction txn = transactionService.transfer(from.get(), to.get(), amount, description);
return ResponseEntity.ok(txn);
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
@GetMapping("/{accountNumber}")
public ResponseEntity<?> getTransactions(@PathVariable String accountNumber) {
return accountService.getByAccountNumber(accountNumber).map(account -> {
List<Transaction> txns = transactionService.getTransactionsByAccount(account);
return ResponseEntity.ok(txns);
}).orElse(ResponseEntity.notFound().build());
}
}
Top comments (0)