- Published on
Java vs Rust: The Bug That Compiles Fine and Kills You at Runtime
- Authors

- Name
- Mehdi Akiki
Reference
One subtle difference between Java and Rust: in Java, mutating a Map inside computeIfAbsent can compile fine and fail at runtime with a ConcurrentModificationException — or worse, cause silent data corruption in older JDKs. In Rust, the same pattern does not even compile. The borrow checker stops you cold.
This is what "safe by design" really means: entire classes of bugs are eliminated before your code ever runs.
Java: Compiles, Crashes at Runtime
Map<String, Integer> map = new HashMap<>();
// Compiles fine, throws ConcurrentModificationException at runtime
map.computeIfAbsent("B", key -> {
map.put("C", 2); // mutating the same map inside the closure
return 3;
});
The Java compiler only checks types. It sees a valid lambda matching the Function interface and signs off. The runtime is left to catch the illegal reentrant modification — and throw a ConcurrentModificationException to fail fast.
In Java 9+ you at least get a crash. In Java 8, this exact code could silently corrupt the map's internal node links or spin into an infinite loop. No warning. No error. Just broken state in production.
Rust: Won't Even Compile
let mut map = HashMap::new();
// Compiler error: cannot borrow `map` as mutable more than once
map.entry("B").or_insert_with(|| {
map.insert("C", 2);
3
});
error[E0499]: cannot borrow `map` as mutable more than once at a time
Rust's borrow checker enforces one rule: you can have either one mutable reference or multiple immutable references, never both at the same time. Calling .entry("B") takes an exclusive mutable borrow of the map. The closure tries to borrow it again to insert "C". The compiler refuses — and you see the error in your IDE before the code ever runs.
The Real Difference
Both languages prevent you from doing something dangerous. The question is when they stop you.
Java relies on runtime exceptions. You need perfect test coverage to catch every edge case in every environment. Rust relies on compile-time guarantees. The bug cannot exist in your deployed binary because it never compiled in the first place.
Catching bugs in production vs. catching them in the IDE. The choice shapes everything about how you write software.