π¬ “When I was a fresher, I used to wonder: what things should I never do in Java?”
That curiosity led me to explore tons of weird bugs and war stories people had faced in production. Some were scary, some silly — but all of them were super valuable to understand deeply.
Today, I’m sharing one such issue I came across.
⚠️ P.S. I didn’t write this code — but I sure learned a lot digging into what went wrong and how it was fixed. π
This post is for the curious devs out there — especially freshers who want to learn the "why" behind Java's behaviors, not just memorize "what to do".
π₯ The Problem: ConcurrentModificationException
in a Simple Loop
πEnvironment:
This happened in a Spring Boot microservice deployed on OpenShift (Linux-based). Interestingly, it didn’t throw any error in the developer's local Windows machine during testing. But in prod? BOOM. π£
Here’s the actual buggy code snippet:
And here’s the ugly surprise from the logs:
❓My Doubt: “Why ‘Concurrent’ if I'm Not Using Threads?”
That word "Concurrent" was super misleading to me back then.
I wasn’t using any threads! It was just a simple for-loop and a list.
So I started digging:
What does this error really mean? When and why does it happen?
π¬ Root Cause Analysis (RCA)
Here’s the twist: even though it looks like you’re looping normally, Java’s for-each loop actually uses an Iterator under the hood.
Let’s break it down:
-
Java’s
ArrayList
maintains an internal mod count (modCount
) to track structural changes. -
When you iterate using
for (item : list)
, it’s essentially doing this:
-
Now, when you do
users.remove(user)
, you're modifying the list directly, while the iterator is still running. -
The iterator notices that the list changed without using its own
remove()
method — and throwsConcurrentModificationException
as a fail-fast mechanism.
π ️ The Fix: Remove Safely Without Angering the Iterator Gods π
✅ Approach 1: Use Iterator Properly
✅ Approach 2: Use Java Streams to Filter
π§ Why Does This Work?
Using it.remove()
is safe because the iterator knows about the removal and updates its internal state. It’s like you politely told the iterator:
“Hey, I’m removing this one — please adjust your internal counter accordingly.” ☕
In the stream version, you’re not modifying the original list at all — you’re creating a new filtered list, which avoids the mutation problem entirely.
"Whaaatttt, then what does iterator.remove()
do internally, and why does that work while list.remove()
doesn’t?"
π¬ What Happens Internally When iterator.remove()
is Called?
Time to go inside Java’s ArrayList
and Iterator
π
π Internals of ArrayList.iterator()
Java’s ArrayList
uses an internal class called Itr
as its iterator.
Let’s see its key variables:
π What .remove()
does:
When you call iterator.remove()
, it internally calls the parent list’s remove method, but also updates internal state safely.
π‘ So it does 3 crucial things:
-
Removes element at correct position (
lastRet
) -
Moves the cursor properly so next iteration continues smoothly
-
Updates
expectedModCount
to matchmodCount
so that CME is NOT thrown
π§ So Why list.remove()
(outside iterator) fails?
Let’s say:
-
Internally:
-
modCount = 0
-
expectedModCount = 0
-
-
When you call
list.remove(1)
directly:-
modCount++ → modCount = 1
-
But
expectedModCount
in the iterator stays0
-
-
On next
it.next()
, it checks:
π₯ BOOM! That's your exception.
π§ Final Analogy Time |
Think of iterator like a security guard:
-
If you try to remove someone from the party (direct list.remove), the guard freaks out π± — CME!
-
But if the guard (iterator.remove) removes them himself, all is cool π
π΅ Why Didn't It Fail in Local?
This is a classic gotcha.
-
Small dataset in local? Iterator might never throw.
-
JVM behavior or optimizations on your machine might not enforce the check immediately.
-
But in production (large dataset + different JVM or OS), the fail-fast behavior gets enforced reliably.
That’s why the same code works locally but crashes in prod — the worst kind of bug. π
π§© What I Learned
-
ConcurrentModificationException
is not about threads — it’s about modifying collections during iteration in an unsafe way. -
Always respect the Iterator pattern if you’re doing removals inside a loop.
-
And never assume something is safe just because it runs locally!
π¨ Wrapping Up
I didn’t write this buggy code — but man, I’m glad I found it while exploring “what not to do” in Java. It helped me understand how iteration really works under the hood.
If you're just starting your Java journey, I highly recommend doing this too:
Don’t just learn “how” to fix something — learn why it broke in the first place.
It’ll make you 10x stronger as a dev. π
π Next Weird Bug coming soon!
Got questions or thoughts? Drop them in the comments or connect with me — I love geeking out over stuff like this. π
Comments
Post a Comment