BigDecimal .equals() trap — 2-hour debug story new BigDecimal("10.00").equals(new BigDecimal("10.0")) → false. If your payment system compares amounts with .equals(), you already have a bug. You just haven't seen the ticket yet. Here's how I learned this — 4 PM on a tired Tuesday last month. A unit test asserting that a calculated total matched an expected value. Values looked identical on screen. Test kept failing. Same number. Different scale. equals() compares both value AND scale. The fix: compareTo() == 0 I had read this in Effective Java, Item 62. I had caught it in code review on someone else's code three months earlier. When I wrote the test myself at 4 PM, I still forgot. Two real lessons: 1. BigDecimal is quietly vicious. Scale matters. Rounding mode matters. And new BigDecimal(0.1) is NOT the same as new BigDecimal("0.1") — the first one is 0.1000000000000000055511151231257827021181583404541015625. Your unit test won't catch it if both sides use the double constructor. 2. Knowing the rule and using the rule are different skills. Reviewing someone else's code with a fresh brain is easy. Catching your own mistake at end of day is not. This is why code review exists — we're blind to our own bugs. I keep a 60-second checklist now for anything money-adjacent: → BigDecimal with String constructor ONLY → compareTo, never equals → RoundingMode always explicit (HALF_EVEN for banking) → No float or double anywhere near currency → Scale set explicitly at the boundary What's the smallest bug that cost you the most time? #Java #BackendDevelopment
BigDecimal equals() trap: scale matters, not just value
More Relevant Posts
-
A subtle Spring behavior that causes real production issues: @Transactional propagation. Most people rely on the default propagation without thinking about transaction boundaries. Example: Method A → @Transactional (REQUIRED) calls Method B → @Transactional (REQUIRES_NEW) What actually happens? Method B runs in a NEW transaction. So even if Method A fails and rolls back, Method B can still commit ❌ Result: Partial data committed → inconsistent state Fix: • Use REQUIRED if operations must succeed or fail together • Use REQUIRES_NEW only when you intentionally need an independent transaction (e.g., audit/logging) • Define transaction boundaries clearly at the service layer Seen this during backend development while handling dependent operations. Lesson: Don’t rely on defaults — design your transaction boundaries consciously. #SpringBoot #Java #Transactions #Microservices #Backend #SoftwareEngineering
To view or add a comment, sign in
-
I sat in a debugging session where the question was embarrassingly simple: did the dependency recover, or did we serve fallback? We had retries, a timeout, a fallback path and the dashboard said: clean success. It took two engineers and forty minutes of log tracing to figure out that "clean success" meant the fallback had been serving cached responses for twenty minutes while upstream recovered. That is the composition problem. Once timeout, retry, fallback, and breaker checks all live in the same part of the request path, the code becomes harder to reason about than the failure itself. Structured concurrency gives you a cleaner boundary: keep the request lifecycle separate from the policies around it. So those policies can be tested, logged, and reviewed independently. The rule I keep coming back to: if a policy changes what the caller sees, it should be visible in the code and visible in the metrics. #Java #StructuredConcurrency #ProjectLoom #BackendEngineering #DistributedSystems
To view or add a comment, sign in
-
-
Your @Transactional might not be working… and Spring won’t warn you. 🚨 I’ve seen this bug more than once: Code looks correct. @Transactional is there. But rollback never happens. Why? 👇 Because Spring transactions work through proxies. That means: Calling a @Transactional method from another method inside the SAME class = Transaction won’t apply. No error. No warning. Just silent failure. Other common traps 👇 → Checked exceptions don’t trigger rollback by default → Private methods won’t be proxied → Async calls break transaction boundaries What I always check 👇 ✔ Is the transactional method being called through Spring proxy? ✔ Is rollback configured for the right exception type? ✔ Are transaction boundaries placed at the service layer? @Transactional is powerful… But dangerous when assumed instead of understood. Framework annotations are not magic. They still follow rules. Have you ever debugged a transaction issue that “should have worked”? 👇 #Java #SpringBoot #Transactional #SpringFramework #BackendDevelopment #SoftwareEngineering
To view or add a comment, sign in
-
-
I’m realizing something interesting as I work more with agents and less with direct typing. I can’t type fast enough anymore to keep up with the volume of communication, corrections, and guidance needed early in a project. Before core frameworks and patterns are established, there’s a lot of iteration, a lot of precision, and frankly a lot of noise. But once those foundations are in place, something shifts. That “overly verbose” style that many developers used to criticize starts to make sense. What was once seen as excessive becomes an advantage. Clear, explicit, structured code and communication isn’t just readable for humans, it’s optimal for agents. Java was designed decades ago to be readable and maintainable. Today, that verbosity turns into a feature, not a flaw. Agents thrive in that environment. They perform better with clarity, structure, and explicit intent. And don’t even get me started on bringing in a framework like Spring Framework. It introduces a consistent, opinionated set of standards that extends that verbosity into architecture level clarity. Conventions, structure, and well defined patterns give both humans and agents a shared language to operate in. Compare that to less structured approaches where iteration often introduces more ambiguity, more rework, and more noise. So maybe what we thought was “too much” was actually preparation for where we are now. Turns out, "this is the way." what is your favorite language to have your Agents use for your critical applications? #springframework #java Josh Long DaShaun C. @danvega Qodo
To view or add a comment, sign in
-
Claude Code has a class of bugs that should make every engineer pause. /tmp/claude-*-cwd: created by every Bash invocation, never deleted. tmpclaude-*-cwd: same pattern, but written into your project directory. They show up in git status. They persist across sessions. Task subagent output under /private/tmp/claude-{uid}/: no TTL, no size cap, no session cleanup. One documented case: 537 GB from a single session, filled a 2 TB disk. The same user hit it six times in 30 days. ~/.claude/ itself: grows unbounded. The root cause of the cwd leak is one missing line. The code reads the temp file with readFileSync, then never calls unlinkSync. An unmatched allocation in a hot path, multiplied across millions of invocations. Every serious engineer internalises this early: — Open a file → close it. — Acquire a lock → release it. — Allocate memory → free it. — Create a temp artefact → unlink it. — Spawn a goroutine → bound its lifetime. We have entire language features dedicated to enforcing the pairing. RAII in C++. defer in Go. try-with-resources in Java. using in C#. Context managers in Python. We encoded the discipline into the syntax, the type system, the runtime, anywhere the compiler could enforce it on our behalf. The moment your code creates something - a file, a connection, a span, a subprocess, a cache entry - you owe the system a matching teardown. If you cannot point to the line of code where that teardown lives, you have a leak. If you are shipping AI-adjacent tooling right now and your users have to write a shell script to keep their drives alive: you have outsourced discipline to your users' disks. That is a design choice. It is not a good one. #ClaudeCode #AIDrivenDevelopment #ShitHitsTheRoof #ReplaceEngineersWithAISlop
To view or add a comment, sign in
-
Strategy Pattern: Keep Your Code Flexible Sometimes your code needs to support multiple ways to solve the same problem. For example: different payment methods, sorting algorithms, or pricing strategies. A common mistake is to handle this with large if–else blocks. This quickly becomes hard to maintain and extend. This is where the Strategy pattern helps. The idea is simple: you extract each algorithm into a separate class and define a common interface. At runtime, you just choose which strategy to use. This allows you to: • easily add new behavior • avoid complex condition logic • keep your code clean and flexible In Java, this usually means: → interface + multiple implementations → inject the needed strategy into your service Small pattern — but very powerful in real systems. #designpatterns #java #backend #softwarearchitecture #cleancode
To view or add a comment, sign in
-
-
🚀 Tired of writing try-catch in every controller? There’s a better way 👇 --- 👉 Problem: @RestController public class UserController { @GetMapping("/user/{id}") public User getUser(@PathVariable int id) { try { return userService.getUser(id); } catch (Exception e) { return null; // ❌ bad practice } } } ❌ Issues: - Repeated code - Messy controllers - Hard to maintain --- ✅ Solution → Global Exception Handling Use @ControllerAdvice + @ExceptionHandler --- 💡 Example: @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<String> handleException(Exception ex) { return new ResponseEntity<>("Something went wrong", HttpStatus.INTERNAL_SERVER_ERROR); } } --- 👉 Now, any exception in your app: ✔ Automatically handled ✔ Clean response returned ✔ No try-catch in controllers --- 🔥 Handle specific exceptions: @ExceptionHandler(UserNotFoundException.class) public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) { return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); } --- ⚡ Real-world impact: Without this: ❌ Inconsistent error responses ❌ Debugging becomes hard With this: ✅ Clean API responses ✅ Centralized error handling ✅ Production-ready backend --- 📌 Key Takeaway: Don’t handle exceptions everywhere… Handle them in ONE place. --- Follow for more real backend learnings 🚀 #SpringBoot #Java #BackendDevelopment #CleanCode #SoftwareEngineer
To view or add a comment, sign in
-
-
🚨 Global Exception Handling in Spring Boot (Simplified) Handling errors properly is just as important as writing business logic. Instead of adding try-catch blocks everywhere, Spring Boot provides a clean way to manage exceptions globally. 🔹 What is Global Exception Handling? It allows us to handle all exceptions in one centralized place using @RestControllerAdvice, making our code cleaner and more maintainable. 🔹 Why use it? ✅ Centralized error handling ✅ Cleaner controllers & services ✅ Consistent API responses ✅ Better client-side debugging 🔹 Basic Flow: An exception occurs in the Service layer It propagates up to the DispatcherServlet Spring routes it to @RestControllerAdvice Matching @ExceptionHandler handles it Structured error response is returned 🔹 Example: @RestControllerAdvice public class GlobalExceptionHandler { // Catch Customized Exception @ExceptionHandler(CustomerNotFoundException.class) public ResponseEntity<ErrorDetail> handleCustomerNotFoundException( CustomerNotFoundException e) { ErrorDetail error = new ErrorDetail( e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value(), LocalDateTime.now() ); return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); } // Catch Generic Exception @ExceptionHandler(Exception.class) public ResponseEntity<ErrorDetail> handleException(Exception e) { ErrorDetail error = new ErrorDetail( e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value(), LocalDateTime.now() ); return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); } } 🔹 Key Tip: Always return meaningful error responses (message, status, timestamp) instead of generic errors. 💡 Pro Insight: If your API returns {} instead of data, check your model class — missing getters can break JSON serialization. #Java #SpringBoot #BackendDevelopment #Microservices #ExceptionHandling #Coding
To view or add a comment, sign in
-
Your code is only 30% of your system. The rest? Dependencies you didn’t write. Backend service starts failing after deployment. No code changes. Same infrastructure. Root cause? 👉 A transitive dependency got auto-upgraded. Most engineers stop at: “npm install” “mvn clean install” “pip install” But production failures live underneath that layer. Let’s break it down technically 👇 🟠 Java (Maven / Gradle) Java is strict. And that’s a good thing. <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.30</version> </dependency> Looks simple. But: • Transitive dependencies can clash • Version conflicts break builds • Resolution order matters 💡 In production: Use dependency locking + enforce versions. 🟢 Node.js (npm / yarn / pnpm) Fastest ecosystem. Also the most dangerous. npm install express That one line pulls in 100+ packages. Now imagine: • One package gets compromised • One version introduces a bug Your app breaks without touching your code. 💡 In production: • Always commit package-lock.json • Avoid loose versioning (^, latest) • Audit dependencies regularly 🔵 Python (pip / poetry) Looks clean. Until it isn’t. pip install requests Without isolation? You’re mixing system + project dependencies. That’s where things go wrong. 💡 In production: • Always use virtual environments • Prefer poetry or pip-tools • Lock dependencies strictly ⚠️ The Real Issue Different tools. Same mistake: 👉 Blind trust in dependency resolution. 🚀 What experienced engineers do differently: • Pin exact versions • Use lock files everywhere • Build reproducible environments • Scan dependencies for vulnerabilities • Treat dependencies like production code
To view or add a comment, sign in
-
-
🚨 I thought I understood Java Multithreading… until it broke my logic I started with a simple idea: 👉 Build a Bank Transaction System (deposit, withdraw, transfer) Easy, right? Then I added threads. And suddenly… Same account was getting updated by multiple threads Balances didn’t make sense Logs were completely messed up Even when code looked “correct”… output wasn’t That’s when I realized: 👉 Multithreading is not about writing threads 👉 It’s about controlling who touches data and when 💥 So I rebuilt it step by step: 1️⃣ Started simple Single-threaded system → everything worked perfectly 2️⃣ Introduced Executor Framework Used ExecutorService to run multiple transactions concurrently → Cleaner and more scalable than manual threads 3️⃣ Hit race conditions Multiple withdrawals on the same account broke consistency 4️⃣ Fixed with synchronized Made deposit & withdraw thread-safe → But transfer was still tricky 5️⃣ Enter ReentrantLock Used locks per account for transfer → Now managing two resources at once 6️⃣ Faced deadlock risk 😬 Two threads: A → B B → A Both got stuck. 7️⃣ Solved it with lock ordering Always lock smaller account ID first → Deadlock gone ✅ 😵 Then came another problem… Even when logic was correct → logs looked WRONG Example: Withdraw shows balance = 400 Deposit also shows 400 Nothing made sense 👉 Root cause: Logs were reading shared state after other threads modified it Final fix 🔥 I introduced a result object: 👉 each operation returns its own exact balance + status Now logs finally reflect reality: pool-1-thread-1 | TXN:101 | WITHDRAW | ACC:1001 | SUCCESS | Balance: 500 pool-1-thread-2 | TXN:108 | TRANSFER | FAILED | Insufficient balance 🧠 What I actually learned: Executor Framework > manual thread creation synchronized vs ReentrantLock (control matters) Race conditions are subtle but dangerous Deadlocks are real — and avoidable with design Logging in multithreaded systems is harder than it looks Concurrency is more about thinking correctly than coding This project changed my understanding from: 👉 “I can use threads” to 👉 “I can design thread-safe systems” If you’ve ever struggled with multithreading, I’d love to hear your experience 👇 #Java #Multithreading #ExecutorService #ReentrantLock #Concurrency #BackendDevelopment #LearningInPublic #SoftwareEngineering #JavaProjects
To view or add a comment, sign in
-
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
I hit this exact bug while building the MoneyUtils class for my payment orchestrator project. Fix is in the Featured section of my profile 👆 if anyone wants to see the final version with JUnit tests. The 3 places it bit me most: 1. Cart total vs discount threshold 2. Idempotency check — stored vs incoming amount 3. Reconciliation — PSP response vs ledger Money math is not number math. Which scenario have you hit?