- String.valueOf(null) picks the char[] overload and throws instead of returning null
03 November 2026 · valueOf(Object) is documented to return the string null for a null argument, but a bare null literal compiles to valueOf(char[]), which is the more specific overload.
- Instant.toString changes length, so sorted strings are not sorted instants
02 November 2026 · ISO_INSTANT prints zero, three, six or nine fraction digits as needed, so a timestamp with no fraction sorts after a later one that has a fraction.
- new HashMap<>(n) resizes before it holds n entries; newHashMap(n) does not
31 October 2026 · The constructor argument is a bucket count, not an expected size. With the default load factor of 0.75, a map built for 1,000 slots rehashes at 769 entries. Since Java 19 there is a factory that takes the size you meant.
- ZonedDateTime.of silently moves a time in the DST gap an hour later
30 October 2026 · A local time that does not exist on the day clocks jump forward is not rejected; java.time shifts it by the length of the gap. In the autumn overlap it picks the earlier offset.
- Optional.orElse evaluates its argument even when a value is present
29 October 2026 · orElse takes a value, so Java computes it before the call, every time; orElseGet takes a Supplier and runs it only when the Optional is empty. The difference shows up as wasted work or side effects.
- Objects.hash of one field is not that field's hashCode, and the javadoc says so
28 October 2026 · Objects.hash(x) hashes a one-element array, so it returns 31 + x.hashCode(); a hashCode written that way silently disagrees with code that expects the field's own hash.
- new BigDecimal(0.1) is not 0.1; BigDecimal.valueOf(0.1) is
26 October 2026 · The constructor that takes a double captures the full binary rounding error of that double. The factory method and the String constructor do not. The javadoc says to prefer them.
- String.strip() and trim() are not the same: Unicode white space vs U+0020
17 October 2026 · Both remove leading and trailing characters, but the Java 25 javadoc defines them differently — trim() cuts anything at or below U+0020, strip() cuts Unicode white space. The gap shows up on real inputs.
- Runtime.totalMemory is the heap the JVM has taken, not the memory you are using
16 October 2026 · freeMemory, totalMemory and maxMemory answer three different questions. Used memory is totalMemory minus freeMemory, and maxMemory can be Long.MAX_VALUE.
- Catching InterruptedException clears the flag, so an empty catch loses the interrupt
15 October 2026 · The javadoc of Thread.sleep says the interrupted status is cleared when the exception is thrown. Swallow it, and code further up the stack can no longer see that anyone asked the thread to stop.
- List.of(...).contains(null) throws, and the javadoc allows it
14 October 2026 · Collection.contains may throw NullPointerException for a null argument when the collection forbids nulls. The unmodifiable lists and sets from List.of and Set.of do exactly that.
- Integer division rounds toward zero, so a negative % picks a negative bucket
13 October 2026 · In Java, -7 / 2 is -3 and -7 % 2 is -1. Code that spreads values over buckets with hash % n breaks on the first negative hash. Math.floorMod and Math.floorDiv give the result most bucketing code expects.
- A concatenated String key collides: 'ab' + 'c' equals 'a' + 'bc'
12 October 2026 · Gluing fields together to build a map or cache key loses the boundary between them, so different inputs share one entry. Use a record or List.of as the key, or encode each field's length.
- int addition wraps silently, so an exclusive end after Integer.MAX_VALUE is negative
11 October 2026 · Java int arithmetic does not throw on overflow: MAX_VALUE + 1 is MIN_VALUE. Turning an inclusive range into an exclusive one is where it bites. Use Math.addExact, a long, or rangeClosed.
- A Set<byte[]> compares arrays by identity, so contains() misses equal bytes
10 October 2026 · Arrays inherit equals and hashCode from Object, so two arrays with the same bytes are different keys. Wrap them in a type that compares content, such as ByteBuffer or a record.
- getOrDefault returns null when the key is there and maps to null
09 October 2026 · The default is used only when the map has no mapping for the key. A key stored with a null value returns null — while merge and computeIfAbsent treat that same null as absent.
- SSLHandshakeException is an IOException, so your retry loop retries it
08 October 2026 · Timeouts, refused connections, unknown hosts and TLS failures share one parent. A retry or fallback written as catch (IOException e) cannot tell a network blip from a certificate it should never trust.
- replaceAll treats the dollar sign and backslash in the replacement as special too
07 October 2026 · Escaping the regex is half the job. A replacement string containing a dollar sign is parsed as a group reference and can throw IndexOutOfBoundsException at run time. Matcher.quoteReplacement exists for this.
- stream.toList() is not collect(Collectors.toList()): one is unmodifiable
06 October 2026 · Replacing collect(Collectors.toList()) with the shorter toList() changes the contract: the list becomes unmodifiable, and add() throws. Collectors.toUnmodifiableList() differs again: it rejects nulls.
- Duration.toHours() is a total; toMinutesPart() is a clock field
06 October 2026 · toHours() and toMinutes() both return the whole duration in their unit, truncated. To print 1h 30m you need toHours() with toMinutesPart(). And both kinds can be negative.
- Math.round(-2.5) is -2: ties round towards positive infinity, not away from zero
05 October 2026 · Java's Math.round breaks ties upwards, so negative halves move towards zero. If your domain expects symmetric rounding or banker's rounding, use BigDecimal with an explicit RoundingMode.
- list.remove(1) on a List<Integer> removes an index, not the value 1
04 October 2026 · List has remove(int) and remove(Object). Overload resolution tries the methods that need no boxing first, so an int literal always picks the index version.
- trim() and strip() disagree on what is space, and both keep U+00A0
03 October 2026 · trim() removes every code point up to U+0020, including NUL. strip() follows Character.isWhitespace, which excludes the non-breaking spaces. Input pasted from a web page survives both.
- Path.normalize() tidies a path; it does not tell you where the path leads
02 October 2026 · The javadoc says normalize never touches the file system and can produce a path to a different file when a symbolic link is involved. A containment check built on it is a check on a string.
- Change a key after you put it in a HashMap and the map may never find it again
02 October 2026 · The Map javadoc says behaviour is not specified when a key changes in a way that affects equals. In practice the entry stays in the bucket of its old hash code, where no lookup goes.
- ConcurrentModificationException is a best-effort bug detector, not a guarantee
01 October 2026 · The javadoc says fail-fast iterators throw on a best-effort basis and that a program must not depend on the exception. It also says the usual cause is one thread, not two.
- ObjectInputFilter's maxarray limits stream arrays, not what readObject allocates
30 September 2026 · A deserialization filter sees arrays and classes as they are read. It never sees an array that a class's own readObject creates from a count, which is exactly how the Guava 33.7.1 bug worked.
- String.length() counts UTF-16 code units, so an emoji is two characters long
29 September 2026 · length(), charAt() and substring() all index char values, not characters. A limit, a column number or a truncation written with them miscounts every character outside the Basic Multilingual Plane.
- Integer.parseInt accepts Arabic-Indic digits; the regex digit class does not
29 September 2026 · parseInt decides what a digit is through Character.digit, which follows Unicode. A regex digit check is ASCII-only by default, so the validator and the parser disagree about the same string.
- Arrays.asList is a fixed-size view of the array, not a new list
28 September 2026 · The list writes through to the array, refuses add and remove with UnsupportedOperationException, and wraps an int[] as a single element. The javadoc says all of it.
- Math.abs can return a negative number, and hash bucketing is where it bites
27 September 2026 · Math.abs(Integer.MIN_VALUE) is Integer.MIN_VALUE. The javadoc says so, and the classic Math.abs(hash) % n turns that into a negative index once in about four billion keys.
- LocalDate.plusMonths clamps to the last valid day, so adding months is not additive
27 September 2026 · Adding one month twice is not the same as adding two months from the 29th, 30th or 31st. A monthly schedule built by repeated addition drifts to the 28th and stays there.
- List.subList is a view, and changing the original list makes it undefined
26 September 2026 · A sublist writes through to its backing list, keeps the whole list alive, and stops having defined behaviour the moment the backing list changes size behind its back.
- DateTimeFormatter's YYYY is the week-based year, and it is wrong a few days a year
26 September 2026 · Pattern letter Y means week-based-year, taken from the locale's week rules. It agrees with the calendar year almost all the time, which is exactly why the bug survives review.
- What NFKC does to a password, and where to look in the JDK
25 September 2026 · A CVE fixed in Quarkus 3.39.5 describes password keyspace reduction via NFKC fullwidth folding. The mechanism is in the Unicode normalisation form, not in the password code.
- TreeMap decides key equality with compareTo, and that quietly breaks the Map contract
25 September 2026 · Sorted maps perform all key comparisons with compareTo or compare, so an ordering inconsistent with equals gives well-defined behaviour that does not obey the general contract of Map.
- size() on a ConcurrentHashMap is for monitoring, not for control flow
24 September 2026 · The javadoc says aggregate status methods are useful only when no concurrent update is in flight, and that their results may be transient.
- Boxed Integers compare equal with == up to 127, then stop
24 September 2026 · Integer.valueOf always caches -128 to 127 and may cache more, so == on boxed values is true for small numbers and false for large ones - and the tests use small numbers.
- toLowerCase() and format() read a locale nobody passed them
23 September 2026 · The no-arg overloads take the JVM default locale, set at startup from the host environment - and they take two different defaults. String.format goes through the FORMAT category, toLowerCase does not, and Formatter substitutes locale-specific digits, not only separators.
- Double.equals says NaN equals NaN and 0.0 does not equal -0.0, the opposite of ==
23 September 2026 · Boxed doubles use representation equivalence, not numerical equality. Every collection goes through equals, so a HashSet can hold a NaN you can find and two zeros you did not expect.
- Collectors.toMap throws on duplicate keys, and not where you are looking
22 September 2026 · The two-argument toMap raises IllegalStateException when mapped keys collide — at collection time, not at call time. The javadoc names the fix: the three-argument overload with a merge function.
- BigDecimal.equals says 2.0 and 2.00 are different, and it means it
22 September 2026 · equals compares value and scale; compareTo compares only value. Put BigDecimals in a HashSet and 1.0 and 1.00 are two elements. The javadoc states this outright.
- String.split silently drops trailing empty fields, and CSV is where it hurts
21 September 2026 · One argument means limit zero, and limit zero discards trailing empty strings. A row ending in two empty columns comes back short, with no error anywhere.
- Map.of gives a different iteration order on every JVM start
21 September 2026 · The javadoc says the order is unspecified and subject to change. In practice it is randomised per JVM run, so a test that passes all afternoon fails in CI.
- computeIfAbsent that touches the same map corrupts it or throws
21 September 2026 · The mapping function must not modify the map. Before Java 9 that silently produced a broken HashMap; since then it throws ConcurrentModificationException.
- Files.lines holds the file open until you close the stream
21 September 2026 · It returns a Stream backed by an open handle. Used without try-with-resources it leaks a descriptor per call, and on Windows it also locks the file.
- Collectors.toMap throws on a duplicate key, and the message shows the value
20 September 2026 · The two-argument form has no opinion about collisions, so it refuses. Deciding what a collision means is the whole job, and there are three honest answers.
- A thread with no name costs you the next production stack dump
20 September 2026 · pool-3-thread-7 tells you nothing at 3am. One argument to the factory turns a thread dump into a map of the system.
- Two identical-looking strings that are not equal in Java
20 September 2026 · equals() compares code points, not glyphs. A filename from macOS and the same name typed on Windows can differ, and the map lookup that fails will not tell you why.
- unmodifiableList gives a view, List.copyOf gives a copy
19 September 2026 · One of them keeps changing under you. The other rejects nulls in a place you did not expect.
- The exception that closed your resource hides the real one
19 September 2026 · try-with-resources suppresses the close() failure and most logging throws it away. getSuppressed() is where the real cause went.
- Java records are not structs
16 September 2026 · Value semantics, mutable components, and the equals() trap.
- Virtual threads pin the carrier inside synchronized
12 September 2026 · Why your virtual threads are not scaling, and the flag that tells you.
- Optional is a return type, not a field type
08 September 2026 · Where Optional earns its cost, and the three places it does not.