Skip to main content

πŸ” "Billing Gone Wrong: How Lack of Synchronization Led to Swapped Bills (And What I Learned!)"

☕ A Walk Down Memory Lane: My First Bug Nightmare


About 9 years ago, as a fresher, I worked on a billing system for a retail application. The system was accessed both from mobile and web UI, and the actual billing logic was written in stored procedures (SQL-based backend).


Everything looked fine... until customers started reporting weird issues:


πŸ› The Bug: Bills Were Swapping Items! 😱

🧾 Scenario:

  • Two different users generated separate bills around the same time

  • One from Mobile, the other from Web

  • The output bills had mixed-up items — each invoice had products that belonged to the other one! 🧨

πŸ” Initial Questions:

"Is this a DB issue?"
"Are stored procedures broken?"
"Race condition in app layer?"

Turns out...

🚫 The code calling stored procedures wasn’t thread-safe.
There was no synchronized mechanism to ensure each bill transaction was isolated.


🧠 The Root Cause Analysis (RCA):

We were calling a shared BillingService.calculate() method across different threads, and it wasn’t synchronized. When multiple threads hit the same method at the same time, data leakage occurred between bills.

πŸ“Œ The stored procedure was NOT the problem — the problem was how the Java app was managing calls to it.


🀯 A Doubt I Had Later...

πŸ’­ "In modern microservices — or even back then — each request runs in its own thread. So why would two requests ever clash? Shouldn’t each request be isolated in memory?"

✅ You're right — each incoming HTTP request is handled by a separate thread. But...

⚠️ Here's What Was Actually Happening:

The Java service class (BillingService) had some shared, mutable fields — like:

public class BillingService {
    private List<Item> items = new ArrayList<>(); // shared state ❌

    public void calculate(BillRequest req) {
        items.clear(); // modifies shared state
        items.addAll(req.getItems());
        billingDao.callStoredProcedure(items);
    }
}

So multiple threads using this shared object were modifying the same list at the same time — causing one bill's data to show up in another. 🀯


πŸ› ️ How We Fixed It: With synchronized πŸ’‘

We locked access to the calculate() method so only one thread could execute it at a time:

public class BillingService {
    public synchronized void calculate(BillRequest req) {
        List<Item> items = new ArrayList<>(); // local state ✅
        items.addAll(req.getItems());
        billingDao.callStoredProcedure(items);
    }
}

OR

public class BillingService {
    public void calculate(BillRequest req) {
        synchronized (this) {
            List<Item> items = new ArrayList<>();
            items.addAll(req.getItems());
            billingDao.callStoredProcedure(items);
        }
    }
}

✅ No more shared state.
✅ No more item swapping.
✅ Happy customers.


πŸ” What is synchronized in Java?

synchronized is a Java keyword used to prevent concurrent thread access to a block or method.

It ensures:

  • Only one thread can execute the synchronized code at a time

  • Prevents race conditions and data corruption


🧱 Two Ways to Use synchronized:

1️⃣ Synchronized Method:

public synchronized void processBill(BillRequest req) {
    // entire method is locked
}

2️⃣ Synchronized Block:

public void processBill(BillRequest req) {
    synchronized(this) {
        // only this part is locked
    }
}

πŸ“Œ Block-level is better for performance — it only locks what needs locking.


πŸ”„ Bonus: Should I Use Locks Instead?

Yes, in high-concurrency apps, consider:

  • ReentrantLock

  • ReadWriteLock

  • Optimistic locking in DB (version columns)

But for simple, state-sensitive flows — synchronized works beautifully πŸ’―


πŸ”š Wrapping Up

This bug taught me a valuable lesson early in my career:

Always think about thread safety — especially when dealing with shared resources like billing engines or inventory systems.

πŸ” Synchronization might seem simple, but when forgotten, it can lead to serious production messes.

Got a wild thread-safety story or billing bug nightmare? Drop it in the comments — I’d love to hear it.


Until next time,
– Anand ☕ @ Java Bean Bag

Comments

Popular posts from this blog

🐱 Tomcat vs ⚡ Netty – Which One Should You Use?

🐱 Tomcat vs ⚡ Netty – Which One Should You Use? So recently I got curious about this too πŸ€”. Everywhere in Spring Boot tutorials we see Tomcat . Then suddenly while exploring Spring WebFlux , the name Netty pops up. And I was like – “Wait, who’s this Netty guy trying to replace Tomcat?” πŸ˜… Let’s break it down with real-time examples , icons , and fun comparisons . 🐱 Tomcat – The Traditional Web Server Type: Servlet Container (blocking I/O) World: Used with Spring MVC Style: Thread-per-request model πŸ‘©‍πŸ’» Pros: Stable, widely used, battle-tested Cons: Struggles with huge concurrent connections πŸ‘‰ Example in real life: Tomcat is like a restaurant with fixed waiters 🍴. - Each customer = one thread/waiter - If too many customers come in at once → waiters run out → customers wait outside πŸšͺ ⚡ Netty – The Reactive Rockstar Type: Asynchronous Event-Driven Network Framework World: Default for Spring WebFlux Style: Event-lo...

🎭 Spring’s Secret: Why @Transactional & Friends Betray You Silently

πŸ’‘ Lesson Learned — Not a Prod Bug, But a Real Pain No, this wasn’t a production outage. Nobody screamed at me. But I sat for 3 hours wondering: “Why the heck is my @Transactional not rolling back!?” 😡‍πŸ’« “Why is Redis cache not working?” 🀯 Turned out, the issue was one silent villain: 🧱 Self-invocation 🀷 What Is @Transactional ? If you're new: @Transactional = Tells Spring to start a DB transaction when a method is called. It’ll commit if everything’s okay. It’ll rollback if something fails. 🧠 Think of it like wrapping your code in: try { beginTransaction(); // your logic commit(); } catch(Exception e) { rollback(); } πŸ•΅️ Real-Life Analogy — The Gateway Community 🏘️ Let me tell you about my society — it has a strict watchman at the gate. Here’s how it works: πŸ›‚ Watchman = Spring Proxy 🏠 Your apartment = Your service class πŸšͺ Your room = A method inside that class πŸƒ Scenario 1: Outsider Visits Your friend from outside...

🌟 My Journey – From Zero to Senior Java Tech Lead 🌟

 There’s one thing I truly believe… If I can become a Java developer, then anyone in the world can. πŸ’― Sounds crazy? Let me take you back. πŸ•“ Back in 2015… I had zero coding knowledge . Not just that — I had no interest in coding either. But life has its own plans. In 2016, I got a chance to move to Bangalore and joined a Java course at a training center. That’s where it all started — Every day, every session made me feel like: "Ohhh! Even I can be a developer!" That course didn’t just teach Java — it gave me confidence . πŸ§ͺ Two Life-Changing Incidents 1️⃣ The Interview That Wasn't Planned Halfway through my course, I had to urgently travel to Chennai to donate blood to a family member. After that emotional rollercoaster, I found myself reflecting on my skills and the future. The next day, as I was preparing for my move to Bangalore to complete the remaining four months of my course, I randomly thought — "Let me test my skills... let me just see...