Most developers fix bugs. Few actually understand why the bug exists. Recently, I debugged a simple issue: Two currencies (COP, PEN) were missing from a dropdown. At first, it looked like a UI problem. But digging deeper revealed something more interesting. What was happening? * Dropdown values were coming from a transaction table * Selected value was inserted back into the same system * Which again fed the dropdown DB ->Dropdown ->Insert -> DB The problem: This created a circular dependency.🥲 * If a currency doesn’t exist in the DB * It won’t appear in the dropdown * If it’s not in the dropdown * It can’t be inserted * If it’s not inserted * It will never exist in the DB New data becomes impossible to introduce. Hidden issue: tight coupling The UI was tightly coupled with the transaction database. Meaning: UI behavior depended directly on the current state of the DB. Why this is a problem: * Any DB limitation immediately affects the UI * Introducing new values becomes difficult * Changes in one layer impact multiple parts of the system * The system becomes fragile and harder to scale Example: Allowed roles dropdown = SELECT DISTINCT role FROM user_table Now try adding a new role: ADMIN * Not in DB * Not in dropdown * Cannot be assigned * Never gets into DB Same circular problem. Key learning: UI should be driven by master or configuration tables, not transactional data. Ideal approach: Master Table → UI → Insert → Transaction Table 📝Takeaways * Separate master data from transaction data * Avoid tight coupling between UI and database state * Watch for circular dependencies in legacy systems Debugging is not just about fixing errors. It is about understanding systems. #SoftwareEngineering #Debugging #SystemDesign #Backend #Java #designpatterns #microservices #springboot
Ankit Singh’s Post
More Relevant Posts
-
Day 19. I stopped putting business logic in controllers. Not because it doesn’t work. Because it turns into a mess. I had code like this: @PostMapping("/users") public ResponseEntity<User> createUser(@RequestBody User user) { if (userRepository.existsByEmail(user.getEmail())) { throw new RuntimeException("Email already exists"); } user.setCreatedAt(LocalDateTime.now()); return ResponseEntity.ok(userRepository.save(user)); } Looks fine. Until your project grows. Now your controller is: → validating business rules → talking to the database → handling the request That’s not a controller. That’s 3 responsibilities in one place. And it only gets worse with time. That’s when it clicked. Controllers shouldn’t think. They should delegate. So I changed it. (see implementation below 👇) What I gained: → Clarity — each layer has one job → Testability — logic tested without HTTP → Reusability — service can be used anywhere The hard truth: → Fat controllers work in tutorials → They fail in real systems → Senior devs spot this instantly Writing endpoints is easy. Keeping your layers clean is what makes you a backend developer. Are your controllers doing too much? 👇 Drop it below #SpringBoot #Java #BackendDevelopment #CleanCode #JavaDeveloper
To view or add a comment, sign in
-
-
“Can I use GET to create data? Or POST to fetch it?” 🤔 Looks harmless… until it hits production. We all know: GET → read data POST → change data But here’s where it gets serious 👇 ⚠️ If you use GET to create data Technically it works. But under the hood: -> GET can be cached (browser / proxy / CDN) -> It can be retried automatically -> It can be triggered again via URL or refresh 💥 Real-world impact (Banking example) Imagine a money transfer API built using GET: User clicks “Pay” → ₹10,000 transferred Network retries → ₹10,000 transferred again Page refresh → ₹10,000 transferred again Now suddenly: 👉 ₹30,000 is gone 👉 Logs look normal 👉 No obvious error This becomes a production incident, not a bug. ❓ What about POST for fetching? Used sometimes (complex or secure requests), but for simple reads → GET is predictable and safe. 🎯 Simple rule (that saves systems) If nothing should change → GET If something changes → POST Small API decisions like this decide whether your system is 👉 predictable 👉 or a nightmare in production #Java #SpringBoot #Backend #REST #SystemDesign #ApiDesign #Interview
To view or add a comment, sign in
-
☕ Understanding @Transactional in Spring Boot One annotation that quietly protects your data integrity: @Transactional It ensures multiple DB operations either: ✅ All succeed ❌ Or all rollback No partial data corruption. 🔍 Real Example @Transactional public void transferMoney(Account a, Account b, int amount) { withdraw(a, amount); deposit(b, amount); } If deposit fails → Spring rolls back withdraw automatically. 🧩 What Happens Under the Hood Spring creates a proxy around the method: 1️⃣ Start transaction 2️⃣ Execute method 3️⃣ Commit if success 4️⃣ Rollback if exception 🚨 Critical Rules Many Developers Miss • Works only on public methods • Works only on Spring-managed beans • Self-invocation bypasses transaction • RuntimeException triggers rollback by default 🧠 Production Insight Transactions define system consistency boundaries. Too large → locks & slow DB Too small → inconsistent state 💡 Best Practice Keep transactions: • Short • Focused • Database-only @Transactional is not just annotation magic — it’s a core reliability guarantee in backend systems. #SpringBoot #Java #BackendEngineering #Transactions #LearnInPublic
To view or add a comment, sign in
-
🚨 Production Issue → Simple Fix → Big Lesson 🚨 Ever had a bug that looks complex… but the fix turns out to be one line? Recently, we were dealing with inconsistent calculations in a critical flow. Everything looked fine at first glance — logic, APIs, database… all good. But under the hood? 👉 Precision issues were silently breaking things. The culprit: using Integer (and primitive types) where precision actually mattered. The fix: ➡️ Switched calculations to BigDecimal And just like that: ✅ Calculation accuracy restored ✅ Edge cases handled properly ✅ Production issue resolved Tested thoroughly ✔️ Validated with real data ✔️ Deployed successfully 🚀 💡 Lesson learned: In backend systems — especially finance, payments, or high-precision domains — 👉 Data types are not just technical choices… they are business-critical decisions Sometimes, the smallest changes make the biggest impact. #Java #BackendDevelopment #ProductionIssue #Debugging #SoftwareEngineering #Microservices #Learning #BigDecimal
To view or add a comment, sign in
-
-
🚀 Most developers use @Transactional… but don’t understand how it actually works. And that’s where bugs start. Let’s simplify it 👇 --- 👉 What does @Transactional do? It ensures that a group of database operations: ✔ Either ALL succeed ✔ Or ALL fail (rollback) --- 💡 Example: @Transactional public void placeOrder() { saveOrder(); makePayment(); } 👉 If makePayment() fails ❌ → saveOrder() will also be rolled back --- 🔥 But here’s the catch (VERY IMPORTANT): @Transactional works using Spring AOP (proxy) 👉 Spring creates a proxy around your class → That proxy manages transaction start/commit/rollback --- ⚠️ Common mistake (many developers miss this): @Service public class OrderService { @Transactional public void createOrder() { saveOrder(); // ❌ Transaction may NOT work } @Transactional public void saveOrder() { // DB logic } } 👉 Why? Because internal method calls bypass the proxy 😮 --- ❌ Result: Transaction might not be applied properly --- ✅ Fix: Option 1: Call method from another service (bean) Option 2: Design properly (separate responsibilities) --- 🔥 Real-world impact: In payment systems: ❌ Partial data saved ❌ Inconsistent state This can cause serious bugs 🚨 --- 📌 Key Takeaway: @Transactional is NOT magic → It depends on proxy behavior Understand this = avoid production bugs --- Follow for more real backend learnings 🚀 #SpringBoot #Java #BackendDevelopment #SystemDesign #SoftwareEngineer
To view or add a comment, sign in
-
-
Day 27. I put @Transactional in my controller. My senior caught it in code review. That conversation changed how I think about layers. I had this: @RestController public class UserController { @Transactional @PostMapping("/users") public ResponseEntity<?> createUser(@RequestBody User user) { userRepository.save(user); return ResponseEntity.ok("User created"); } } It worked. No errors. Everything looked fine. Looked clean. Until a senior looked at it. Here’s what actually happens: → Transaction starts at the controller → It stays open longer than needed → Higher chance of locks & performance issues → Blurred business logic boundaries That’s when it clicked. Transactions don’t belong in controllers. Controllers should: → Handle HTTP → Delegate work Not manage transactions. So I changed it. (see implementation below 👇) What I learned: → Transactions define business boundaries → They should live close to business logic (service layer) → Not at the API layer The hard truth: → Working code and well-designed code are two different things → Most tutorials only teach you the first one Writing working code is easy. Placing responsibility in the right layer is what makes you a backend developer. If your senior reviewed your controller today — would @Transactional be there? 👇 Drop your take #SpringBoot #Java #BackendDevelopment #CleanCode #Hibernate #JavaDeveloper
To view or add a comment, sign in
-
-
A recent issue reminded me that performance optimizations can sometimes become production problems. We had an API that: 1️⃣ Fetches initial details 2️⃣ Extracts IDs from the response 3️⃣ Makes another database call to fetch larger secondary data To speed up step 3, parallel processing was introduced using a fixed thread pool. Sounds reasonable — until load testing began. Under heavy traffic, thread creation kept increasing across instances until limits were hit, leading to: ⚠️ "Can't create new native thread" The interesting part? The optimization worked for individual requests. But at scale, the resource model didn’t. A request with a small number of IDs didn’t always need dedicated worker threads, yet threads were still being allocated repeatedly under concurrent load. The fix was moving to a shared/reusable thread pool model with better resource control. 💡 My takeaway: Code that is fast in isolation may fail under concurrency. When designing for performance, it’s important to ask: - How does this behave at 1 request? - How does this behave at 1000 requests? - What resources grow with traffic? Scalability is often less about speed, more about control. #BackendEngineering #Java #PerformanceTesting #Scalability #Concurrency
To view or add a comment, sign in
-
🚀 I’ve been thinking about thread usage in backend services recently… Most of our services do a lot of: • DB calls • S3 reads • External API calls Basically… a lot of waiting. Traditionally, we use thread pools. It works fine, but here’s what I’ve noticed: When a thread makes an API or DB call, it just sits there waiting. It’s not doing any work… but still consuming memory. At scale: More requests → more threads → more pods → more cost. To solve this, we used patterns like DeferredResult in Spring. Idea was simple: 👉 Don’t block request thread 👉 Process in background 👉 Send response later It works… But honestly: • More complex code • Harder to debug • Still need to manage thread pools internally Recently, I started exploring virtual threads (Java 21), and it feels much simpler. You just write normal code: • Call API • Save to DB • Return response Even if it blocks, virtual threads handle it efficiently. For use cases like: • Vendor API calls • Reading from S3 • Writing to DB Virtual threads seem like a natural fit. From what I understand so far: • Thread pools → limited + need tuning • DeferredResult → non-blocking but adds complexity • Virtual threads → simple + scalable And better resource usage usually means lower infra cost. Still exploring edge cases (CPU-heavy tasks, locking, etc.) But for typical backend services, this feels like a solid improvement. 💬 Curious if anyone has replaced DeferredResult / async handling with virtual threads in production? How was your experience?
To view or add a comment, sign in
-
-
1. ThreadLocal: The "Private Locker" Strategy When you use ThreadLocal, each thread gets its own independent copy of a variable. There is no shared data, so there is no contention. The Use Case: Imagine handling multiple money transfer requests. Request 1: Customer C101, Txn ID: TXN1001 Request 2: Customer C202, Txn ID: TXN2001 We use ThreadLocal to store the Transaction ID so that every log or service call within that thread knows which transaction it’s working on without passing it as a method parameter everywhere. public class RequestContext { private static ThreadLocal<String> txnId = new ThreadLocal<>(); public static void setTxnId(String id) { txnId.set(id); } public static String getTxnId() { return txnId.get(); } public static void clear() { txnId.remove(); } // Always clean up! } Why not synchronize here? Because Thread 1 doesn't care about Thread 2's ID. We need Isolation, not locking. 2. Synchronization: The "Gatekeeper" Strategy We use synchronized when threads must access the exact same piece of data (like a bank balance). If two threads try to debit the same account at the exact same time, you’ll end up with incorrect data without a lock. public synchronized void debit(int amount) { if (balance >= amount) { balance -= amount; } } Why not use ThreadLocal here? If each thread had its own "copy" of the balance, the actual account would never be updated globally. We need Consistency, which requires a lock. Key Takeaway: Use ThreadLocal when you want to avoid synchronization overhead for data that is specific to a thread's execution context (e.g., User IDs, DB Connections, Transaction IDs). Use synchronized when threads must modify the same shared resource and you need to ensure data integrity. #Java #BackendDevelopment #SoftwareEngineering #MultiThreading #Concurrency #JavaPerformance #CodingTips #Programming #SystemDesign
To view or add a comment, sign in
-
🚀 Day 55 of my #100DaysOfCode Journey Today, I solved LeetCode problem Richest Customer Wealth Problem Insight: We are given a 2D array where each row represents a customer and each column represents their wealth in different banks. We need to find the maximum total wealth among all customers. Approach: • Traverse each row of the 2D array (each customer) • Calculate the sum of all bank accounts for that customer • Keep updating the maximum wealth found so far • Compare each row sum with the current maximum Code Idea Used: Nested loop traversal (matrix row-wise summation) Time Complexity: O(m × n) | Space Complexity:** O(1) Key Takeaway: Simple nested loops are powerful for matrix problems—focus on row-wise or column-wise aggregation depending on the requirement. #LeetCode #DSA #ProblemSolving #Java #CodingJourney #100DaysOfCode #Arrays #MatrixProblems
To view or add a comment, sign in
-
Explore related topics
Explore content categories
- Career
- Productivity
- Finance
- Soft Skills & Emotional Intelligence
- Project Management
- Education
- Technology
- Leadership
- Ecommerce
- User Experience
- Recruitment & HR
- Customer Experience
- Real Estate
- Marketing
- Sales
- Retail & Merchandising
- Science
- Supply Chain Management
- Future Of Work
- Consulting
- Writing
- Economics
- Artificial Intelligence
- Employee Experience
- Workplace Trends
- Fundraising
- Networking
- Corporate Social Responsibility
- Negotiation
- Communication
- Engineering
- Hospitality & Tourism
- Business Strategy
- Change Management
- Organizational Culture
- Design
- Innovation
- Event Planning
- Training & Development