
Distributed transactions across microservices require a different approach than the traditional two phase commit (2PC). In a Spring Boot microservices architecture, where each service owns its own database and communicates over the network, 2PC becomes complex, slow, and error prone due to tight coupling and potential locking across services.
To address this, the Saga pattern in microservices has become a widely adopted solution. Instead of one global transaction, a Saga breaks the workflow into a series of local transactions, each managed by an individual service. Once a service completes its part, it publishes an event (e.g., “DebitCompleted”), which other services consume to trigger their own local operations.
This event driven architecture in Spring Boot creates a loosely coupled, scalable system where each step in the process knows just enough to do its job. If all goes well, the saga flows through each service to completion.
But real systems aren’t perfect; failures happen. If any service fails during the process, the Saga pattern allows you to run compensating transactions, custom logic designed to undo the effects of previously completed steps. For example, if a credit operation fails, the debit service can be asked to roll back the money transfer.
This way, eventual consistency is preserved without distributed locks or tight coordination, making it ideal for resilient, fault tolerant microservices. This is particularly effective when paired with robust cloud development services to support scalability and on demand infrastructure.
Follow this Saga pattern microservices tutorial step by step with me, and implement it in your IDE to fully understand the Saga pattern and how to apply it in your projects. By the end, you’ll be confident in setting up and managing distributed transactions in Spring Boot using the Saga pattern.
In this tutorial, we will build a simple money transfer microservice system consisting of:
- Account-Debit Service (runs on port 8081)
- Account-Credit Service (runs on port 8082)
Both services will communicate solely via RabbitMQ microservice communication (using Spring AMQP), without any REST calls between them. We will use in-memory data and expose REST endpoints for testing with Postman:
- Debit Service:
- POST /transfer → publishes a TransferRequested event
- GET /accounts → view in-memory balances
- POST /transfer → publishes a TransferRequested event
- Credit Service:
- GET /accounts → view in-memory balances
The Saga Workflow:
- Debit Service consumes TransferRequested →
- If there is sufficient balance, debit the “fromAccount”, then publish DebitCompleted;
- Otherwise, publish DebitFailed.
- If there is sufficient balance, debit the “fromAccount”, then publish DebitCompleted;
- Credit Service consumes DebitCompleted →
- If credit logic succeeds, credit the “toAccount”, then publish CreditCompleted;
- Otherwise, publish CreditFailed.
- If credit logic succeeds, credit the “toAccount”, then publish CreditCompleted;
- Debit Service also listens for CreditFailed →
- Issue a compensation refund (i.e., add back the amount to “fromAccount”).
We’ll implement full AMQP (Advanced Message Queuing Protocol) RabbitMQ saga implementation, event DTOs, AccountStore (in memory balances with all methods), @RabbitListener handlers, and REST controllers. Finally, we’ll run RabbitMQ locally via Docker and test the entire flow with Postman, including a simulated credit failure. This implementation can complement more advanced devops services for streamlined automation, testing, and deployment.

Technologies
- Java 17
- Spring Boot 4.0.0
- Spring AMQP (spring-boot-starter-amqp)
- RabbitMQ (Docker image: rabbitmq:3-management)
- Postman (or any REST client)
Project Setup
Open Spring Initializer, generate two services, Debit Service and Credit Service, then download the generated ZIP files. Extract and import each service as a Maven project into your preferred IDE.
Add the following dependencies to your project:
- Spring Web – for building RESTful APIs
- Spring for RabbitMQ – for asynchronous messaging support using AMQP
- Spring Boot DevTools – for automatic restarts and live reload during development
To see how this kind of setup scales and aligns with modern software practices, check out our in-depth CI/CD with Docker & AWS Blog for integrating continuous deployment in similar microservice architectures.

1. Running RabbitMQ Locally via Docker
Use Docker to run RabbitMQ:
docker run -d --hostname rabbit --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
Code language: CSS (css)

- AMQP port: 5672
- Visit Management UI: http://localhost:15672 (default user/pass: guest / guest)
- Management Home Page:
2. Common Event DTOs
Each microservice defines its own copy of the following DTO classes. This design supports microservices coordination and ensures modularity within a distributed system. For each DTO, ensure that it includes: a constructor with all fields, appropriate getters and setters, and an overridden toString() method.
Debit Service → com.debitservice.events
public class TransferRequested implements Serializable {
private String transferId;
private String fromAccount;
private String toAccount;
private Double amount;
private TransferRequested() {}
}
public class DebitCompleted implements Serializable {
private String transferId;
private String fromAccount;
private String toAccount;
private Double amount;
private DebitCompleted() {}
}
public class DebitFailed implements Serializable {
private String transferId;
private String reason;
private DebitFailed() {}
}
public class CreditFailed implements Serializable {
private String transferId;
private String reason;
private Double refundAmount;
private String refundAccount;
private CreditFailed() {}
}
Code language: PHP (php)
Credit Service → com.creditservice.events
public class DebitCompleted implements Serializable {
private String transferId;
private String fromAccount;
private String toAccount;
private Double amount;
private DebitCompleted() {}
}
public class CreditCompleted implements Serializable {
private String transferId;
private String toAccount;
private Double amount;
private CreditCompleted() {}
}
public class CreditFailed implements Serializable {
private String transferId;
private String reason;
private Double refundAmount;
private String refundAccount;
private CreditFailed() {}
}
Code language: PHP (php)
3. AccountStore Component (Both Services)
We simulate accounts in-memory. Place this class in debit service under com.debitservice.store
package com.debitservice.store;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Component;
@Component
public class AccountStore {
private final Map<String, Double> accounts = new ConcurrentHashMap<>(Map.of("A", 100.0, "B", 50.0));
public Map<String, Double> getAccounts() {
return accounts;
}
public Double getBalance(String acct) {
return accounts.getOrDefault(acct, 0.0);
}
public void updateBalance(String acct, Double newBal) {
accounts.put(acct, newBal);
}
public boolean debit(String acct, Double amount) {
synchronized (accounts) {
Double balance = getBalance(acct);
if (balance >= amount) {
Double updated = balance - amount;
updateBalance(acct, updated);
return true;
} else {
return false;
}
}
}
public void credit(String acct, Double amount) {
synchronized (accounts) {
Double balance = getBalance(acct);
Double updated = balance + amount;
updateBalance(acct, updated);
}
}
}
Code language: JavaScript (javascript)
Explanation
- We use a ConcurrentHashMap to store balances for accounts “A” and “B” by default.
- Because we’re not using a real database, this is purely in-memory; all state resets when the services restart.
- Methods:
- getAccounts(): returns the map for GET /accounts endpoint.
- getBalance(String): returns current balance or 0.
- updateBalance(String, Double): overwrites balance.
- debit(String, Double): synchronized check and subtract; logs each step.
- credit(String, Double): synchronized add; logs each step.
- getAccounts(): returns the map for GET /accounts endpoint.
This idempotency in microservices is crucial to avoid duplicate transactions in event driven architecture systems.
4. Spring Boot + RabbitMQ Configuration (AMQP)
RabbitMQ is a message broker that uses the AMQP protocol (Advanced Message Queuing Protocol) to send and receive messages between different systems in a reliable and asynchronous manner.
Spring Boot makes it easy to integrate RabbitMQ using the Spring AMQP module. It abstracts the low-level AMQP details, enabling seamless RabbitMQ microservice communication with simple annotations and configurations.
- You use @RabbitListener to receive messages from a queue.
- You use RabbitTemplate.convertAndSend() to send messages to an exchange with a routing key.
- Spring handles message serialization, deserialization, connection management, retries, and error handling under the hood.
Together, Spring Boot and RabbitMQ provide a clean and efficient way to enable event-driven communication between microservices or components in a distributed system.
This idempotency in microservices is crucial to avoid duplicate transactions in event-driven architecture systems. For additional reading on building resilient systems with messaging queues and distributed services, refer to our guide on Spring Boot with Apache Kafka.
Exchange, Routing Keys and Queues Configurations:
We configure a single Direct Exchange named money.transfer.exchange, plus five queues:
- transfer.requested.queue
- debit.completed.queue
- debit.failed.queue
- credit.completed.queue
- credit.failed.queue
Bindings route messages by routing key, e.g. “transfer.requested” → transfer.requested.queue, “debit.completed” → debit.completed.queue, etc.

5. Debit Service Implementation (Port 8081)
Debit-service package structure

5.1 application.properties (Debit Service)
spring.application.name=debit-service
server.port=8081
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
5.2 DebitServiceApplication.java
package com.debitservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DebitServiceApplication {
public static void main(String[] args) {
SpringApplication.run(DebitServiceApplication.class, args);
}
}
Code language: JavaScript (javascript)
5.3 AccountStore.java
Add the AccountStore class from the above given example for debit service.
5.4 RabbitMQConfig.java
package com.debitservice.config;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE = "money.transfer.exchange";
public static final String QUEUE_TRANSFER_REQUESTED = "transfer.requested.queue";
public static final String QUEUE_DEBIT_COMPLETED = "debit.completed.queue";
public static final String QUEUE_DEBIT_FAILED = "debit.failed.queue";
public static final String QUEUE_CREDIT_COMPLETED = "credit.completed.queue";
public static final String QUEUE_CREDIT_FAILED = "credit.failed.queue";
public static final String RK_TRANSFER_REQUESTED = "transfer.requested";
public static final String RK_DEBIT_COMPLETED = "debit.completed";
public static final String RK_DEBIT_FAILED = "debit.failed";
public static final String RK_CREDIT_COMPLETED = "credit.completed";
public static final String RK_CREDIT_FAILED = "credit.failed";
@Bean
DirectExchange sagaExchange() {
return new DirectExchange(EXCHANGE);
}
@Bean
Queue transferRequestedQueue() {
return QueueBuilder.durable(QUEUE_TRANSFER_REQUESTED).build();
}
@Bean
Queue debitCompletedQueue() {
return QueueBuilder.durable(QUEUE_DEBIT_COMPLETED).build();
}
@Bean
Queue debitFailedQueue() {
return QueueBuilder.durable(QUEUE_DEBIT_FAILED).build();
}
@Bean
Queue creditCompletedQueue() {
return QueueBuilder.durable(QUEUE_CREDIT_COMPLETED).build();
}
@Bean
Queue creditFailedQueue() {
return QueueBuilder.durable(QUEUE_CREDIT_FAILED).build();
}
@Bean
Binding bindTransferRequested(Queue transferRequestedQueue, DirectExchange exchange) {
return BindingBuilder.bind(transferRequestedQueue).to(exchange).with(RK_TRANSFER_REQUESTED);
}
@Bean
Binding bindDebitCompleted(Queue debitCompletedQueue, DirectExchange exchange) {
return BindingBuilder.bind(debitCompletedQueue).to(exchange).with(RK_DEBIT_COMPLETED);
}
@Bean
Binding bindDebitFailed(Queue debitFailedQueue, DirectExchange exchange) {
return BindingBuilder.bind(debitFailedQueue).to(exchange).with(RK_DEBIT_FAILED);
}
@Bean
Binding bindCreditCompleted(Queue creditCompletedQueue, DirectExchange exchange) {
return BindingBuilder.bind(creditCompletedQueue).to(exchange).with(RK_CREDIT_COMPLETED);
}
@Bean
Binding bindCreditFailed(Queue creditFailedQueue, DirectExchange exchange) {
return BindingBuilder.bind(creditFailedQueue).to(exchange).with(RK_CREDIT_FAILED);
}
@Bean
MessageConverter converter() {
return new Jackson2JsonMessageConverter();
}
@Bean
AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(converter());
return rabbitTemplate;
}
}
Code language: JavaScript (javascript)
5.5 DebitEventHandler.java
package com.debitservice.handler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import com.debitservice.config.RabbitMQConfig;
import com.debitservice.events.CreditFailed;
import com.debitservice.events.DebitCompleted;
import com.debitservice.events.DebitFailed;
import com.debitservice.events.TransferRequested;
import com.debitservice.store.AccountStore;
@Component
public class DebitEventHandler {
private final RabbitTemplate rabbitTemplate;
private final AccountStore accountStore;
public DebitEventHandler(RabbitTemplate rabbitTemplate, AccountStore accountStore) {
this.rabbitTemplate = rabbitTemplate;
this.accountStore = accountStore;
}
@RabbitListener(queues = RabbitMQConfig.QUEUE_TRANSFER_REQUESTED)
public void handleTransferRequest(TransferRequested request) {
boolean success = accountStore.debit(request.getFromAccount(), request.getAmount());
if (success) {
DebitCompleted event = new DebitCompleted(request.getTransferId(), request.getFromAccount(),
request.getToAccount(), request.getAmount());
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE, RabbitMQConfig.RK_DEBIT_COMPLETED, event);
} else {
DebitFailed event = new DebitFailed(request.getTransferId(),
"Insufficient balance in account: " + request.getFromAccount());
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE, RabbitMQConfig.RK_DEBIT_FAILED, event);
}
}
@RabbitListener(queues = RabbitMQConfig.QUEUE_CREDIT_FAILED)
public void handleCreditFailed(CreditFailed event) {
accountStore.credit(event.getRefundAccount(), event.getRefundAmount());
}
}
Code language: JavaScript (javascript)
5.6 TransferController.java
package com.debitservice.controller;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.debitservice.config.RabbitMQConfig;
import com.debitservice.events.TransferRequested;
@RestController
@RequestMapping("/transfer")
public class TransferController {
private final RabbitTemplate rabbitTemplate;
public TransferController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@PostMapping
public String requestTransfer(@RequestBody TransferRequested request) {
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE, RabbitMQConfig.RK_TRANSFER_REQUESTED, request);
return "Transfer requested: " + request.getTransferId();
}
}
Code language: JavaScript (javascript)
5.7 BalanceController.java
package com.debitservice.controller;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.debitservice.store.AccountStore;
@RestController
@RequestMapping("/accounts")
public class BalanceController {
private final AccountStore accountStore;
public BalanceController(AccountStore accountStore) {
this.accountStore = accountStore;
}
@GetMapping
public Map<String, Double> getAccounts() {
return accountStore.getAccounts();
}
}
Code language: JavaScript (javascript)
6. Credit Service Implementation (Port 8082)
Credit-service package structure

6.1 application.properties (Credit Service)
spring.application.name=credit-service
server.port=8082
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
6.2 CreditServiceApplication.java
package com.creditservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CreditServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CreditServiceApplication.class, args);
}
}
Code language: JavaScript (javascript)
6.3 AccountStore.java (Credit Service)
package com.creditservice.store;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Component;
@Component
public class AccountStore {
private final Map<String, Double> accounts = new ConcurrentHashMap<>(Map.of("C", 20.0, "D", 80.0));
public Map<String, Double> getAccounts() {
return accounts;
}
public Double getBalance(String acct) {
return accounts.getOrDefault(acct, 0.0);
}
public void updateBalance(String acct, Double newBal) {
accounts.put(acct, newBal);
}
public boolean debit(String acct, Double amount) {
synchronized (accounts) {
Double balance = getBalance(acct);
if (balance >= amount) {
Double updated = balance - amount;
updateBalance(acct, updated);
return true;
} else {
return false;
}
}
}
public void credit(String acct, Double amount) {
synchronized (accounts) {
Double balance = getBalance(acct);
Double updated = balance + amount;
updateBalance(acct, updated);
}
}
}
Code language: JavaScript (javascript)
6.4 RabbitMQConfig.java (Credit Service)
Use the same exchange and queue names as the Debit Service, so that they share the same RabbitMQ infrastructure.
6.5 CreditEventHandler.java
package com.creditservice.handler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import com.creditservice.config.RabbitMQConfig;
import com.creditservice.events.CreditCompleted;
import com.creditservice.events.CreditFailed;
import com.creditservice.events.DebitCompleted;
import com.creditservice.store.AccountStore;
@Component
public class CreditEventHandler {
private final RabbitTemplate rabbitTemplate;
private final AccountStore accountStore;
public CreditEventHandler(RabbitTemplate rabbitTemplate, AccountStore accountStore){
this.rabbitTemplate = rabbitTemplate;
this.accountStore = accountStore;
}
@RabbitListener(queues = RabbitMQConfig.QUEUE_DEBIT_COMPLETED)
public void handleDebitCompleted(DebitCompleted event) {
// Simulate a failure for a specific transferId "FAIL-CREDIT"
if ("FAIL-CREDIT".equals(event.getTransferId())) {
CreditFailed cf = new CreditFailed(event.getTransferId(), "Simulated credit failure", event.getAmount(),
event.getFromAccount());
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE, RabbitMQConfig.RK_CREDIT_FAILED, cf);
return;
}
accountStore.credit(event.getToAccount(), event.getAmount());
CreditCompleted cc = new CreditCompleted(event.getTransferId(), event.getFromAccount(), event.getAmount());
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE, RabbitMQConfig.RK_CREDIT_COMPLETED, cc);
}
}
Code language: JavaScript (javascript)
6.6 BalanceController.java
package com.creditservice.controller;
import com.creditservice.store.AccountStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/accounts")
public class BalanceController {
private final AccountStore accountStore;
public BalanceController(AccountStore accountStore) {
this.accountStore = accountStore;
}
@GetMapping
public Map<String, Double> getAccounts() {
return accountStore.getAccounts();
}
}
Code language: JavaScript (javascript)
7. Testing the Saga Flow via Postman
To validate the spring boot money transfer microservice, start both the Debit and Credit services. Ensure each service runs on its assigned port and is connected to the RabbitMQ server (typically on port 5672)..
7.1 Debit Service Accounts:
Check the /accounts endpoints of each service to view the current account balances. The Debit Service contains two accounts, A and B with balances { “A”: 100.0, “B”: 50.0 }, while the Credit Service includes accounts C and D with balances { “C”: 20.0, “D”: 80.0 }.
GET http://localhost:8081/accounts

7.2 Credit Service Accounts:
GET http://localhost:8082/accounts

7.3 Perform Successful Transfer
To simulate a successful money transfer, initiate:
POST http://localhost:8081/transfer

Working:
- Debit Service receives a TransferRequested with transferId=TXN1001.
- accountStore.debit(“A”, 30.0) → succeeds (100.0 ≥ 30.0). A’s new balance = 70.0.
- Publishes DebitCompleted(“TXN1001″,”A”,”C”,30.0).
- Verify Debit Service Account Balances:
GET http://localhost:8081/accounts → {“A”:70.0,”B”:50.0}

- Credit Service consumes DebitCompleted:
- Does not match “FAIL-CREDIT”, so accountStore.credit(“C”, 30.0) → C’s new balance = 50.0.
- Publishes CreditCompleted(“TXN1001″,”A”,30.0).
- Verify Credit Service Account Balances:
GET http://localhost:8082/accounts → {“C”:50.0,”D”:80.0}

7.4 Insufficient Funds (Debit Failure)
To simulate failure in debit:
POST http://localhost:8081/transfer

What Happens:
- Debit Service tries accountStore.debit(“A”,1000.0) → fails (70.0 < 1000.0).
- Publishes DebitFailed(“TXN1002″,”Insufficient balance…”).
- Credit Service does not react to DebitFailed (no listener).
- Balances remain unchanged.
Verify:
Debit Service:
GET http://localhost:8081/accounts → {“A”:70.0,”B”:50.0}
Credit Service remains the same.
7.5 Simulated Credit Failure (Compensation)
To simulate a credit failure and trigger the saga pattern compensation:
POST http://localhost:8081/transfer

Working:
- Debit Service:
- accountStore.debit(“A”,20.0) → succeeds (A’s balance = 50.0).
- Publishes DebitCompleted(“FAIL-CREDIT”,”A”,20.0).
- Credit Service receives DebitCompleted(“FAIL-CREDIT”,…):
- Matches “FAIL-CREDIT” → publishes CreditFailed(“FAIL-CREDIT”, “Simulated credit failure”).
- Matches “FAIL-CREDIT” → publishes CreditFailed(“FAIL-CREDIT”, “Simulated credit failure”).
- Debit Service listens for CreditFailed:
- accountStore.credit(“A”,50.0) (compensation refund). A’s balance goes back to 100.0 (50.0 + 50.0).
- accountStore.credit(“A”,50.0) (compensation refund). A’s balance goes back to 100.0 (50.0 + 50.0).
Verify:
Debit Service:
GET http://localhost:8081/accounts → {“A”:100.0,”B”:50.0}

Credit Service remains unchanged.
State of RabbitMQ Exchanges and Queues on Management Portal


8. Conclusion & Next Steps
We’ve successfully built a Spring Boot money transfer microservice using the Saga pattern that demonstrates:
- RabbitMQ (AMQP) for asynchronous messaging
- @RabbitListener for consuming events
- AccountStore for in memory balances
- REST APIs for testing with Postman:
- POST /transfer (Debit Service)
- GET /accounts (both services)
- POST /transfer (Debit Service)
Next Steps:
- Use a database: Persist accounts and saga state for production.
- Docker Compose: Combine both services and RabbitMQ into one docker-compose.yml.
- Unit tests: Write tests for each handler using @SpringBootTest and mocks.
- Security: Add authentication/authorization to REST endpoints.
Wrapping Up:
We’ve successfully implemented a distributed transaction system using the Saga pattern. Now you can extend this pattern for more complex workflows and scale it for production. Keep experimenting with advanced features like fault tolerance and resilience, and don’t forget to secure your services for a complete, production ready solution.
If you’re looking to build scalable microservices or distributed systems, explore our end-to-end software development services tailored for modern enterprise needs. You can explore the complete source code on our GitHub repository.

Author's Bio

Onkar Musale is a Senior Software Engineer at Mobisoft Infotech with over 6.5 years of experience in Java backend development and cloud technologies, I specialize in designing scalable microservices and robust RESTful APIs using Java Spring Boot and Golang. I’m passionate about leveraging AWS, Docker, and Kubernetes to build high-performance, cost-efficient solutions. My expertise spans database management (MySQL, PostgreSQL, MongoDB), API documentation, payment gateway integrations, and advanced cloud configurations. I’m a quick learner, resilient, and always eager to adapt to new technologies, dedicated to delivering quality solutions that drive business success.