Common Java Pitfalls Cookbook
This document compiles common errors and subtle behaviors in Java that can lead to hard-to-detect bugs. Each entry follows the outline problem → solution → discussion so you can identify, understand, and avoid these errors in your code.
1. Integer comparison outside the cached range
Problem: The operator == returns false for integers greater than 127 (or less than -128) even if the numerical values are equal.
Solution: Use .equals() to compare values of Integer. For performance or simplicity, use int if you don't need nulls.
Discussion: Java caches the Integer in the range [-128, 127]. Outside that range, Integer.valueOf() returns distinct instances; == compare references, not content.
2. Comparing String with ==
Problem: == between chains sometimes it gives true and others false for the same text, which confuses beginners.
Solution: Compare strings with .equals() either .equalsIgnoreCase().
Discussion: He string pool You can make two literals point to the same reference, but objects created with new String() No. == compare references; .equals() compare content.
3. Modify a collection during iteration
Problem: Remove or add items to a collection while browsing with for-each lance ConcurrentModificationException.
Solution: Use a Iterator and his remove(), or collect first and modify later; alternatively, use concurrent collections.
Discussion: Many collections detect structural changes during iteration. Concurrent collections (e.g., CopyOnWriteArrayList) have different semantics and additional costs.
4. NullPointerException when unboxing
Problem: Convert a Integer null to int produces NullPointerException.
Solution: Valid null before unboxing or use OptionalInt / default values.
Discussion: Unboxing internally invokes intValue() about the reference; if it is null, fails. Avoid this wherever possible by using primitive types.
5. Arrays do not deep copy
Problem: Assigning an array to another variable copies the reference; changes in one view affect the other.
Solution: Use Arrays.copyOf() either clone() to copy the array; for arrays/objects, implements an explicit deep copy.
Discussion: Copying by reference is fast but surprising for those who expect data independence. For nested structures, it copies each level.
6. Lack of break in switch (fall-through)
Problem: Forget break runs additional cases unexpectedly.
Solution: Duck break or use switch with expressions (Java 14+) and ->, which does not do fall-through default.
Discussion: He fall-through It is useful in grouped cases, but it is often a source of bugs. Expressions switch modern technologies reduce this risk.
7. Virtual calls in constructors (incomplete initialization)
Problem: Calling overridden methods from a constructor may use uninitialized fields of the subclass.
Solution: Do not invoke overridden methods in constructors; use methods end or explicit initialization.
Discussion: The superclass constructor executes before initializing the subclass fields. The virtual call accesses an incomplete state.
8. Arrays of generic types
Problem: Arrays of specific generic types cannot be created, e.g., new ArrayList [10].
Solution: Use List [] or preferably collections, avoiding generic arrays.
Discussion: Arrays are covariant and reified; generics use type erasure. Mixing breaks type safety at runtime.
9. Mutable keys in HashMap/HashSet
Problem: Changing a key after entering it prevents it from being found again.
Solution: Use immutable keys (e.g., record or immutable classes) or do not modify the keys after using them.
Discussion: hashCode() and equals() determine the location. If they change, the map loses its reference to the entry.
10. Optional.get() without checking presence
Problem: Call to get() about a Optional empty spear NoSuchElementException.
Solution: Use orElse(), orElseGet(), orElseThrow() either ifPresent().
Discussion: Optional aims to avoid null; wear get() breaks its intent. Prefers expressive APIs.
11. Variable shadowing (field hiding)
Problem: The method parameter hides the instance field and assignments like name = name; They do nothing useful.
Solution: Use this.name = name; or change the parameter name.
Discussion: Shadowing hampers maintenance and causes silent bugs. Enabling IDE inspections helps detect it.
12. Attendance: lack of volatile (visibility)
Problem: A thread does not observe changes by another thread to a variable without synchronization.
Solution: Declare the variable as volatile or synchronize accesses (synchronized, Lock).
Discussion: The Java memory model allows for per-thread reordering and caching. volatile guarantees visibility and order of publication/reading.
13. Concurrency: Non-atomic increments
Problem: Operations such as value++ lose updates under concurrency.
Solution: Use AtomicInteger.incrementAndGet(), blocks synchronized or accumulators (LongAdder).
Discussion: value++ implies read-modify-write. Without mutual exclusion, two threads can overwrite results.
14. Calendar: zero-based month
Problem: Calendar#set(year, month, day) interprets January as 0; 10 It's November, not October.
Solution: Use constants (Calendar.OCTOBER) or migrates to java.time.
Discussion: The old date API is prone to errors. java.time (JSR-310) is clearer and more immutable.
15. LocalDate.parse() with non-ISO formats
Problem: LocalDate.parse("05/10/2025") lance DateTimeParseException.
Solution: Define a formatter: DateTimeFormatter.ofPattern("dd/MM/yyyy") and pass it to parse().
Discussion: Default, parse() wait yyyy-MM-ddFor other formats you must specify the pattern explicitly.
