Project Name: Payment Wallet System
Project Goal
=> User registration and login
=> Add money to wallet (top-up)
=> Transfer money to another user
=> View transaction history
=> Check current balance
Tech Stack
Microservices Architecture
- User Service : User registration, login, profile
- Wallet Service : Balance management, top-up
- Transaction Service : Record transfers, history
- Notification Service : Simple notification (Email/SMS simulation)
_________________________________________________________________________
GitHub URL- payment-wallet-system🔗
=> Download the zip file, extract, open in IDE (intelliJ, or any other)
=> Since this is a multi-module maven project, add 4 modules (right click, new -> module)
In parent pom.xml,
=> We can get required dependencies from maven repo (https://mvnrepository.com/)
=> Ensure the properties java.version, spring-boot.version
=> We cannot have direct dependencies here. We can use this parent pom file for version management only.
=> For spring-boot-starter dependencies, instead of mentioning everything in the dependencyManagement, we can simply have parent component and mention version.
=> For this specific project, we needed spring security, jjwt (json web token) api, impl, jackson dependencies and lambok dependencies
<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.example</groupId>
<artifactId>payment-wallet-system</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Payment Wallet System</name>
<description>Microservices based digital payment wallet</description>
<modules>
<module>user-service</module>
<module>wallet-service</module>
<module>transaction-service</module>
<module>notification-service</module>
</modules>
<properties>
<java.version>25</java.version>
<spring-boot.version>3.5.9</spring-boot.version>
</properties>
<!-- Use spring-boot-starter-parent as parent (remove dependencyManagement import) -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.9</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<!-- Dependency versions only (no actual dependencies here) -->
<dependencyManagement>
<dependencies>
<!--jjwt-->
<!-- Source: https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.13.0</version>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.13.0</version>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.13.0</version>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.modelmapper/modelmapper -->
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
=> For some dependencies, we need to mention scope
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>payment-wallet-system</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>user-service</artifactId>
<!--Actual dependencies import-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Jakarta Validation API + Hibernate Validator implementation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--spring-boot-starter-security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jjwt-->
<!-- Source: https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<!--lombok - optional – reduces boilerplate-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional> <!--Mention must-->
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.modelmapper/modelmapper -->
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
=> getter methods for all fields
=> setter methods for all non-final fields
=> toString() method (nice formatted string representation)
=> equals() and hashCode() methods (based on all fields)
=> canEqual() (for subclass compatibility)
private Long id;
private String username;
private String password;
private String email;
private String fullName;
private String role;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
// ... 10+ more getters/setters ...
@Override
public boolean equals(Object o) { /* complex */ }
@Override
public int hashCode() { /* complex */ }
@Override
public String toString() { /* complex */ }
}
public class User {
private Long id;
private String username;
private String password;
private String email;
private String fullName;
private String role;
}
=> On Entities (User): @Data is fine, but some people prefer @Getter @Setter @ToString(exclude = "password") to avoid exposing sensitive fields in toString().
=> Lombok dependency must be present in pom.xml
If you ever want to be more explicit (e.g., exclude password from toString), you can replace @Data with:
@Getter
@Setter
@ToString(exclude = "password")
@EqualsAndHashCode
public class User { ... }
user-service endpoints flow explanation briefly
1. User Registers (POST /api/users/register)
2. User Logs In (POST /api/users/login)
=> User gets a JWT token — this is their “login ticket” for future requests.
3. User Calls Protected Endpoint (e.g., GET /api/users/me)
User action:
=> Wants to see their profile
=> Sends request with header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
What happens:
- Checks header → extracts token (after "Bearer ")
- Uses JwtUtil.extractUsername(token) → gets username
- If token valid (not expired, signature correct) → loads user details (CustomUserDetailsService)
- Sets authentication in SecurityContext (marks user as logged in)
- Passes request to controller
- Gets current username from SecurityContext (who is logged in)
- Fetches user from DB
- Returns DTO (profile info)
Result:
=> With valid token → 200 OK + profile
=> Without token / invalid token → 401 Unauthorized (Spring Security blocks it)
In the SecurityFilterChain, why we are having .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
=> We use SessionCreationPolicy.STATELESS specifically because this is a JWT-based authentication system.
Normal Spring Security Default Behavior (with sessions)
By default, Spring Security uses session-based authentication:
When user logs in → server creates a session ID (stored in cookie or header)
Server remembers the user in HttpSession (server memory or Redis)
Every future request → browser sends session cookie → server looks up “who is this user?”
This is stateful — server keeps state (remembers logged-in users)
Problems with Stateful Sessions in Modern Microservices / API Systems
Scalability issue
If you have 10 servers behind a load balancer, each server has its own session memory
User logs in on server 1 → session created there
Next request goes to server 2 → server 2 doesn't know the user → logout or re-login required
Solution? Share sessions via Redis / sticky sessions — complicated & expensive
JWT is designed to be stateless
JWT token contains all info (username, role, expiration) + is signed
Server doesn't need to remember anything — just validate signature & expiration
Any server can handle any request — perfect for microservices & horizontal scaling
Performance & simplicity
No session storage (Redis, DB) → less cost, less latency
No session cleanup needed when user logs out
Works great with mobile apps, SPAs (React, Angular), APIs
What SessionCreationPolicy.STATELESS does
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
Summary in simple words
=> We are using JWT tokens → all login info is inside the token itself
=> Server doesn't need to "remember" users → no need for sessions
=> STATELESS mode turns off session creation → makes system scalable, simple, and fast
=> Without this line → Spring would try to create sessions → breaks JWT flow & scalability
=> This is standard for any JWT-based API/microservice project (Many companies use this).
--------------------------------------------------------------------------------------------------------