dependabot[bot] opened a new pull request, #24460: URL: https://github.com/apache/camel/pull/24460
Bumps [com.cedarsoftware:java-util](https://github.com/jdereg/java-util) from 4.105.0 to 4.106.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/jdereg/java-util/releases">com.cedarsoftware:java-util's releases</a>.</em></p> <blockquote> <h2>4.106.0</h2> <h2>java-util 4.106.0</h2> <p>Maven Central: <code>com.cedarsoftware:java-util:4.106.0</code></p> <p>A broad, class-by-class hardening pass across the library — real correctness, security, and performance fixes in the core utilities, each pinned by new tests. Full details in <a href="https://github.com/jdereg/java-util/blob/master/changelog.md">changelog.md</a>.</p> <h3>Highlights</h3> <p><strong>Security</strong></p> <ul> <li><strong>Turkish-locale defect class fixed</strong> across <code>DateUtilities</code>, <code>IOUtilities</code>, and <code>SystemUtilities</code> — default-locale case conversion (e.g. <code>"Windows"</code> → <code>"wındows"</code>, <code>"FILE"</code> → <code>"fıle"</code>) silently disabled Windows system-path checks and protocol allow-listing; all security-relevant conversions now use <code>Locale.ROOT</code>.</li> <li><strong>ReDoS timeout protection now actually stops runaway matches</strong> in <code>DateUtilities</code> and <code>RegexUtilities</code> — <code>java.util.regex</code> never checks interrupts, so timed-out matches previously kept burning pool threads (turning ReDoS into thread/CPU exhaustion). Matching now runs over an interrupt-checking <code>CharSequence</code>.</li> <li><code>IOUtilities</code> upload paths now get the same SSRF protocol validation as downloads; <code>ClassUtilities</code> blocked-supertype and ClassLoader-leak fixes.</li> </ul> <p><strong>Correctness</strong></p> <ul> <li><code>MultiKeyMap</code> — lock-free <code>size()</code>/<code>isEmpty()</code> no longer transiently collapse toward 0 during a resize.</li> <li><code>CaseInsensitiveMap</code>/<code>CaseInsensitiveSet</code> — serialization now works, and copy-constructors preserve the source's concurrency/ordering.</li> <li><code>SafeSimpleDateFormat</code> — <code>clone()</code> no longer entangles the copy with the original; serialization fixed.</li> <li><code>ConcurrentList</code> deadlock hazard, <code>IntervalSet</code> reader atomicity, <code>DeepEquals</code> large-value hash saturation, <code>Converter</code> user-override cache masking, and more.</li> </ul> <p><strong>Performance</strong></p> <ul> <li>Lock-free read fast paths for <code>ClassUtilities.forName</code> (~51× at 8 threads) and the <code>ReflectionUtils</code> caches (~3× at 8 threads).</li> <li><code>CompactMap</code>/<code>CompactSet</code> back to a single instance field (memory) with faster <code>equals</code>/<code>hashCode</code>; <code>Converter</code> negative-result caching; widespread collection presizing.</li> </ul> <p><strong>API</strong></p> <ul> <li><code>UrlUtilities</code> is <strong>deprecated, scheduled for removal in 5.0</strong> — no ecosystem consumers; prefer <code>java.net.http.HttpClient</code> and <code>IOUtilities</code>.</li> </ul> <p><strong>JDK compatibility:</strong> 8+.</p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/jdereg/java-util/blob/master/changelog.md">com.cedarsoftware:java-util's changelog</a>.</em></p> <blockquote> <h4>4.106.0 - 2026-07-04</h4> <ul> <li><strong>API DEPRECATION</strong>: <strong><code>UrlUtilities</code> is deprecated and scheduled for removal in java-util 5.0.</strong> A GitHub/GitLab/web code-search sweep found no independent consumer: json-io and the rest of java-util do not use it, <code>jdereg/n-cube</code> already refactored off it, and the only remaining callers are a dormant <code>web-spider</code> (last touched 2019, pinned to java-util 1.34.0) and a stale collaborator fork of old n-cube. A dependency-bloat research corpus even flags the class as <em>unused</em> where java-util appears transitively. The class wraps the legacy <code>HttpURLConnection</code> stack with manual cookie stores, an all-trusting <code>TrustManager</code>/<code>HostnameVerifier</code>, and global mutable user-agent/referrer state. New code should use <code>java.net.http.HttpClient</code> (JDK 11+) for HTTP and <code>IOUtilities</code> for stream transfer/compression/<code>URLConnection</code> input; the naive-SSL and cookie he lpers have no planned replacement (they are unsafe for production). Class-level <code>@Deprecated</code> only in the 4.x line — behavior unchanged; the class remains present and functional through 4.x and will be deleted in the 5.0 major release (removing a public class is a binary-breaking change reserved for a major bump).</li> <li><strong>DOC / PERFORMANCE</strong>: <code>Traverser</code>'s three <code>@deprecated</code> javadocs pointed callers at <code>{@link #traverse(Object, Set, Consumer)}</code> — a signature that resolves only to the <em>private</em> internal overload, so the suggested replacement was uncallable; they now point at the public <code>traverse(Object, Consumer, Set)</code>. <code>TrackingMap</code>'s <code>readKeys</code> tracking set is presized with load-factor headroom (<code>size/0.75 + 1</code>) so tracking every key of a fully-read map never rehashes (<code>new HashSet<>(size)</code> under-sizes the table and rehashes at 0.75×size). Reviewed <code>TypeUtilities</code> and <code>Unsafe</code> in the same pass — no defects found (the type-resolution cache tolerates null values, <code>getAllDeclaredFields</code>'s custom-filter path is cache-keyed by the stable singleton filter, and <code>Unsafe</code>'s ReflectionFactory→sun.misc.Unsafe fallback chain with per-class constructor caching is correct). Note: <code>TypeUtilities.ParameterizedTypeImpl.getActualTypeArguments()</code> deliberately returns its internal array (documented perf choice; library consumers treat it read-only) — left as-is.</li> <li><strong>SECURITY</strong>: <strong><code>RegexUtilities</code> timeout protection now actually stops runaway matches.</strong> Same defect class fixed in <code>DateUtilities</code> earlier this release: <code>java.util.regex</code> never checks interrupts, so <code>Future.cancel(true)</code> left each timed-out match burning a pool thread's CPU until backtracking completed. <code>RegexUtilities</code>' executor is <em>bounded</em> (≤8 threads), so a handful of ReDoS inputs permanently saturated the pool — after which <strong>every</strong> <code>safe*</code> operation failed, including trivial ones (the "protection" converted ReDoS into a total regex outage). All five timeout-protected operations (<code>safeMatches</code>/<code>safeFind</code>/<code>safeReplaceFirst</code>/<code>safeReplaceAll</code>/<code>safeSplit</code>) now match over an interrupt-checking <code>CharSequence</code>, so cancellation aborts the match promptly and the thread returns to the pool. Pinned by a test that fires 10 catastrophic matches (more than the pool's thread count) and verifies trivial operations still work — note the attack pattern uses sequential unbounded quantifiers, since JDK 9+'s loop memoization defuses the classic <code>(a+)+$</code> form. Also fixed the "rejected due timeout" message typo.</li> <li><strong>BUG FIX</strong>: <strong><code>SafeSimpleDateFormat</code> — <code>clone()</code> no longer entangles the copy with the original, and serialization works.</strong> The inherited <code>DateFormat.clone()</code> made a shallow copy sharing the <code>AtomicReference</code> configuration state, so <code>clone.setTimeZone(...)</code> silently reconfigured the original (and vice versa); <code>clone()</code> now detaches the state. The class has always declared <code>serialVersionUID</code> (with <code>DateFormat</code> being <code>Serializable</code>), but its internal <code>State</code>/<code>NFSig</code> classes weren't — <code>writeObject</code> threw <code>NotSerializableException</code>; both are now <code>Serializable</code> and round-trip with pattern/timezone intact. Also <code>StreamGobbler._result</code> is now <code>volatile</code>: it's written by the gobbler thread and read by the launcher, and <code>Executor</code> joins with a <em>deadline</em> — a time d-out join read the field with no happens-before edge. And <code>SystemUtilities.OS_NAME</code> (public constant) now lowercases with <code>Locale.ROOT</code> — under a Turkish default locale, "Windows" lowercased to "wındows" (dotless ı), breaking any consumer's <code>contains("windows")</code> check. Reviewed <code>StringUtilities</code> in the same pass — no defects found (the equalsIgnoreCase/hashCodeIgnoreCase fold consistency, Levenshtein buffers, and repeat() overflow guards are all sound).</li> <li><strong>BUG FIX</strong>: <strong><code>MultiKeyMap.size()</code>/<code>isEmpty()</code>/<code>longSize()</code> no longer transiently collapse toward 0 during a resize.</strong> <code>resizeInternal</code> zeroed <code>atomicSize</code> and re-incremented it once per entry while rehashing under the all-stripes lock — but the size accessors are lock-free, so a concurrent reader could observe <code>isEmpty() == true</code> (or any partial count) on a fully populated map mid-resize. The entry count is invariant during a resize, so the counter is no longer touched — which also removes n atomic RMWs from the global-pause window. Pinned by an insert-only reader/writer test asserting size monotonicity across multiple resizes. Also fixed a wrong-variable bug in <code>compareObjectArrays</code>' large-Set (>6 elements) cross-bucket fallback: it compared <code>elem1</code> (the SET_OPEN sentinel at that point) instead of the set element being matched, so the hash-collision fallb ack could never match (latent — value-aligned hashing keeps equal elements in the same bucket for all built-in types, and the sibling methods use the correct variable); and presized the <code>keySet()</code>/<code>values()</code>/<code>entrySet()</code> snapshot containers (previously default-sized, guaranteeing repeated rehash/grow on large maps).</li> <li><strong>DOC / PERFORMANCE</strong>: <code>MapUtilities.mapOf</code>/<code>mapOfEntries</code> javadoc claimed <code>NullPointerException</code> for null keys/values — LinkedHashMap semantics have always permitted them (java-util is deliberately null-friendly); docs now match behavior, pinned by tests. <code>mapOfEntries</code> backing map presized with load-factor headroom (was sized to entry count, guaranteeing a rehash for any input beyond 12 entries). <code>LoggingConfig.UniformFormatter</code> drops the per-log-record <code>String.format("%-5s", ...)</code> (format-string parse + varargs boxing on every record) for manual padding — output byte-identical, pinned by test. Reviewed <code>MathUtilities</code> in the same pass — no defects found (<code>parseToMinimalNumericType</code>'s mantissa/exponent scanning, security-limit caching, and permutation logic are all sound).</li> <li><strong>SECURITY / BUG FIX</strong>: <strong><code>IOUtilities</code> security checks no longer degrade on Turkish-locale JVMs, and uploads get the same SSRF validation as downloads.</strong> All security-relevant case conversions used default-locale <code>toLowerCase()</code>: under <code>tr-TR</code>, an <code>io.allowed.protocols</code> setting of "FILE" lowercased to "fıle" (dotless ı) and never matched a URL's "file" protocol (legitimate access blocked), and — worse — <code>os.name</code> "Windows…" lowercased to "wındows…", silently disabling every Windows system-path security check (<code>system32</code>/<code>syswow64</code>/SAM detection) on Turkish-locale Windows machines. All conversions now use <code>Locale.ROOT</code> (same bug class fixed in <code>DateUtilities</code> earlier this release). Additionally: <code>transfer(File, URLConnection, cb)</code> and <code>transfer(URLConnection, byte[])</code> went straight to <code>getOutputStream()</code> with no protocol validation — the documented SSRF protection only covered the download path; both upload paths now validate first. <code>getInputStream</code> no longer leaks the underlying connection stream (socket) when the GZIP header read fails; <code>uncompressBytes(bytes, offset, len)</code> no longer misclassifies a <code>len < 2</code> slice as gzip by reading past the caller's declared range (a 1-byte slice of a gzip array now round-trips instead of throwing); <code>compressBytes(byte[])</code>/<code>uncompressBytes(byte[])</code> fail fast with a clear message on null input instead of a bare NPE from <code>bytes.length</code>.</li> <li><strong>BUG FIX</strong>: <strong><code>IntervalSet</code> lock-free readers can no longer catch covered values transiently uncovered during merge/split.</strong> The class documents lock-free reads with writes guarded by an internal lock, but <code>addWithMerge</code> and <code>removeRangeWithSplitting</code> mutated remove-first: merging <code>[0,100)</code> with an overlapping add removed <code>[0,100)</code> from the skip-list before putting the merged interval back, so a concurrent <code>contains(10)</code> in that window returned <code>false</code> for a value covered both before and after the operation (same for the retained fragments of a range removal). Mutations now write replacements/fragments <em>before</em> removing what they absorb — retained coverage is visible in the map's entry union at every instant (in-place value extension for same-key merges; right-fragment-then-trim for splits). Final states are byte-identical to the old logic (all 141 existing Interval Set tests pass unchanged); a reader-vs-writer hammer test pins the guarantee.</li> <li><strong>BUG FIX / PERFORMANCE</strong>: <code>GraphComparator</code> resize/position deltas (<code>LIST_RESIZE</code>, <code>ARRAY_RESIZE</code>, <code>ARRAY_SET_ELEMENT</code>, <code>LIST_SET_ELEMENT</code>) truncated <code>Long</code>/<code>BigInteger</code> <code>optionalKey</code> values with <code>intValue()</code>: 2^32 silently became 0 — clearing the list/array instead of failing. Values must now be exactly representable as a non-negative <code>int</code>; out-of-range values produce the documented <code>DeltaError</code>. Also presized <code>compareSets</code>' two id-to-value maps with load-factor headroom (they were sized to element count, guaranteeing a rehash mid-comparison for sets over ~12 elements). Reviewed <code>IdentitySet</code> and <code>InetAddressUtilities</code> in the same pass — no defects found (IdentitySet's DELETED-sentinel probe chains, rehash-on-tombstone-buildup trigger, and iterator removal are correct; its identity-hash open addressing is sound). — <code>json-io</code> 4.103.0 → 4.105.0 (always trails java-util by one release to avoid a dependency cycle); <code>jackson-databind</code> 2.22.0 → 2.21.4 — 2.22.0 is the single version covered by the <code>@JsonIgnoreProperties</code> case-insensitive-bypass advisory (Dependabot <a href="https://redirect.github.com/jdereg/java-util/issues/13">#13</a>) and its fix (2.22.1) is not yet on Maven Central, so this steps back to the latest unaffected release; revisit once 2.22.1 ships. Test-scope only — no consumer-facing surface. Also <code>central-publishing-maven-plugin</code> 0.10.0 → 0.11.0. All other dependencies/plugins verified current at their latest JDK 8-compatible versions (<code>agrona</code> and <code>mockito</code> intentionally pinned).</li> <li><strong>BUG FIX / DOC</strong>: Review pass over <code>ExceptionUtilities</code>, <code>Executor</code>, <code>EncryptionUtilities</code>, and the Fast I/O quartet (<code>FastByteArrayInputStream</code>/<code>FastByteArrayOutputStream</code>/<code>FastReader</code>/<code>FastWriter</code>). <code>ExceptionUtilities.safelyIgnoreException(Callable, T)</code>: a null callable was swallowed by the catch-all and silently returned the default — it now throws the documented <code>IllegalArgumentException</code>. <code>FastByteArrayOutputStream.write(byte[], off, len)</code>: an invalid offset with <code>len == 0</code> bypassed the bounds check via the early return; per the <code>OutputStream</code> contract it now throws <code>IndexOutOfBoundsException</code>. <code>Executor</code>: the <code>envp</code> javadoc (copied from <code>Runtime.exec</code>) claimed a non-null environment array replaces the child environment — the <code>ProcessBuilder</code>-based implementation has al ways <em>merged</em> it into the inherited environment; docs now state the merge semantics with an explicit warning not to rely on <code>envp</code> for environment isolation (behavior unchanged: strict replacement would break <code>PATH</code>-dependent shell resolution). <code>FastReader</code>/<code>FastWriter</code>/<code>FastByteArrayInputStream</code>/<code>EncryptionUtilities</code> reviewed with no defects found (EncryptionUtilities' weak spots — deterministic CBC IV, MD5 key derivation, unauthenticated legacy decrypt fallback — are all confined to <code>@Deprecated</code> backward-compatibility paths; the modern path is GCM + random salt/IV + PBKDF2).</li> <li><strong>BUG FIX</strong>: <strong><code>DateUtilities</code> no longer breaks on Turkish-locale JVMs.</strong> Month-name and timezone-abbreviation lookups used default-locale <code>toLowerCase()</code>/<code>toUpperCase()</code>: under <code>tr-TR</code>, "APRIL" lowercases to "aprıl" (dotless ı) and missed the month map — <code>parseDate("APRIL 21st, 2024")</code> threw <code>NullPointerException</code> — and "ist" uppercased to "İST", missing the abbreviation map. All case conversions now use <code>Locale.ROOT</code>; pinned by tests that run under a Turkish default locale.</li> <li><strong>SECURITY</strong>: <code>DateUtilities</code> opt-in ReDoS timeout protection now actually stops the runaway match: <code>java.util.regex</code> never checks interrupts, so <code>Future.cancel(true)</code> left each timed-out match burning a pool thread's CPU until backtracking completed (and the cached pool grows unboundedly under repeated attacks — the "protection" converted ReDoS into thread/CPU exhaustion). Matching now runs over an interrupt-checking <code>CharSequence</code>, so cancellation aborts the regex promptly; post-match <code>group()</code> reads bypass the check and are unaffected.</li> <li><strong>PERFORMANCE / BUG FIX</strong>: <strong><code>DeepEquals.deepHashCode</code> no longer collapses all large floats/doubles into one hash bucket.</strong> The epsilon-alignment quantization (<code>Math.round(value * scale)</code>) saturates at the integer max: every float above ~21,474 and every double above ~9.2e8 produced the identical hash (verified: 30000.0f, 99999.0f, and 1e30f all hashed alike), silently degrading unordered collection/map comparison to O(n²) pairwise deep-probes for such data. Large magnitudes now quantize relatively (mantissa-bit masking at the same ~1e-12/1e-6 relative granularity as <code>nearlyEqual</code>); near-equal pairs straddling a mask boundary are still matched by the existing cross-bucket fallback, preserving correctness.</li> <li><strong>BUG FIX</strong>: <code>DataGeneratorInputStream.withRandomStrings</code> with <code>minWordLen</code> 0 generated an empty word and threw <code>ArrayIndexOutOfBoundsException</code> on the first read; bounds are now validated up front (<code>minWordLen >= 1</code>, <code>maxWordLen >= minWordLen</code>).</li> <li><strong>BUG FIX</strong>: <code>ConcurrentList.remove(int)</code> threw <code>NoSuchElementException</code> instead of the contractual <code>IndexOutOfBoundsException</code> for <code>remove(0)</code> on an empty list (it routed through <code>removeFirst()</code>); out-of-range indexes for <code>remove(int)</code>/<code>add(int, E)</code> now fail fast with a proper bounds check before any delegation. Also <code>equals()</code> no longer iterates the other collection while holding this list's read lock — two <code>ConcurrentList</code>s comparing against each other while writers were queued on both could deadlock (each reader blocked behind the other list's queued writer); it now compares against a snapshot. <code>remove(Object)</code>/<code>removeLastOccurrence</code> use a single direct scan (<code>indexOf</code>/<code>lastIndexOf</code>) instead of re-acquiring the read lock per element via <code>get(i)</code>.</li> <li><strong>DOC</strong>: <code>ConcurrentList</code> javadoc no longer claims lock-free operation: every mutation takes the exclusive write lock and reads take the shared read lock (parallel among readers; <code>size()</code>/<code>isEmpty()</code> are genuinely lock-free). The performance table, advantages, and thread-safety sections now describe the actual <code>ReentrantReadWriteLock</code> design. Reviewed <code>ConcurrentSet</code> and <code>ConcurrentNavigableSetNullSafe</code> in the same pass — no defects found (sentinel handling, <code>toArray</code> race guard, and spliterator characteristics in <code>ConcurrentSet</code> are correct; <code>ConcurrentNavigableSetNullSafe</code>'s comparator wrapping is JDK-consistent).</li> <li><strong>MEMORY</strong>: <strong><code>CompactMap</code> is back to a single instance field.</strong> The <code>modCount</code> field added in February for fail-fast iteration violated the class's design axiom (one <code>val</code> field whose type encodes the state) and cost 8 bytes per instance after alignment — a 50% object-size overhead multiplied across every map json-io allocates. Fail-fast detection now rides on state the map already has: structural changes in the array/single/empty states always replace the <code>val</code> reference, so iterators identity-compare a snapshot (still catching add+remove sequences that leave the size unchanged — the case <code>modCount</code> was added for); in MAP state detection is inherited from the backing map's own fail-fast iterator, and MAP→array/EMPTY boundary transitions replace <code>val</code> and are caught by the snapshot. All six concurrent-modification detection tests pass unchanged. Semantics note: concurrent backing maps (e.g. <code>ConcurrentHashMap</code>) now give weakly-consistent MAP-state iteration instead of best-effort CME, matching the backing map's own contract.</li> <li><strong>BUG FIX / PERFORMANCE</strong>: <strong><code>CompactMap.builder().build()</code> is ~15x faster (7.2 → 0.49 µs) and no longer rewrites live maps' configuration.</strong> Two compounding defects in the template-class machinery: (1) the generated-class lookup consulted the parent ClassLoader, which can never see classes defined in the private template loader — so <em>every</em> build (json-io's <code>CompactMap</code>/<code>CompactSet</code> factories build one per deserialized instance) constructed and swallowed two <code>ClassNotFoundException</code>s, re-cloned and re-patched the 2.7KB bytecode template, and re-ran the static-field injection on the already-defined class; (2) the config hash used for template-class identity omitted the inner map type, so two case-insensitive configs differing only in backing map (e.g. <code>HashMap</code> vs <code>ConcurrentHashMap</code>) shared one template class — and each rebuild retroactively rewrote the shared class's inj ected config, silently changing <code>getNewMap()</code> for all existing instances. Lookups now hit the template loader's own cache exception-free, the inner map type participates in the hash, and the template constructor is cached per class. Also: a non-String <code>singleValueKey</code> no longer crashes with a bare <code>ClassCastException</code> during class-name generation.</li> <li><strong>PERFORMANCE</strong>: <strong><code>CompactSet.equals()</code>/<code>hashCode()</code> no longer construct a backing map via reflection per call</strong> — the case-sensitivity check went through <code>map.getConfig()</code>, which builds a config map and instantiates a throwaway backing map (<code>Class.forName</code> + constructor scan for template classes) on every invocation; it now asks the map directly. Measured (10-element set): <code>hashCode</code> 227 → 14 ns (~16x), <code>equals</code> 312 → ~100 ns (~3x). Also <code>CompactMap</code> iterator creation on legacy subclasses (<code>CompactLinkedMap</code>, <code>CompactCIHashMap</code>, …) no longer instantiates a throwaway map via <code>getNewMap()</code> to decide whether iteration needs sorting — the decision is cached per class (single <code>ClassValueMap</code> probe, iteration measured at parity); and <code>CompactMap.putAll</code> javadoc corrected (null is tolerated as a no-op, not rejected w ith NPE as documented).</li> <li><strong>PERFORMANCE</strong>: <code>ClassValueSet.removeAll</code>/<code>retainAll</code> with a <code>ClassValueSet</code> argument probe the argument's backing set directly instead of its <code>ClassValue</code>-cached <code>contains()</code> — the cached path installed a negative membership entry (a synchronized write into the Class's ClassValue map) for every miss, polluting the argument set's cache as a side effect of a bulk read.</li> <li><strong>BUG FIX</strong>: <strong><code>CaseInsensitiveSet</code> serialization actually works now.</strong> The class has always declared <code>implements Serializable</code> (with a <code>serialVersionUID</code>), but its backing <code>CaseInsensitiveMap</code> never did — so <code>ObjectOutputStream.writeObject</code> threw <code>NotSerializableException</code> at runtime. <code>CaseInsensitiveMap</code> now implements <code>Serializable</code> (its cached-view fields were already marked <code>transient</code>, evidence of the original intent); both classes round-trip with case-insensitive semantics and original key case intact, pinned by tests. Serializability remains conditional on the chosen backing map, as with any collection wrapper.</li> <li><strong>PERFORMANCE</strong>: <code>CaseInsensitiveSet.addAll</code> from another <code>CaseInsensitiveSet</code> bulk-copies already-normalized keys through the <code>CaseInsensitiveMap.putAll</code> fast path (skipping per-element unwrap/re-wrap and hash recomputation), and the copy-constructor's default backing map is presized with load-factor headroom so population never rehashes. Also corrected constructor javadoc: null source collections are tolerated (create an empty set), not rejected with <code>NullPointerException</code> as documented.</li> <li><strong>PERFORMANCE</strong>: <strong><code>CaseInsensitiveMap</code> — <code>clear()</code>, <code>containsValue()</code>, and <code>values()</code> now delegate to the backing map</strong> instead of inheriting <code>AbstractMap</code>'s entrySet-routed implementations, which allocated a <code>CaseInsensitiveEntry</code> wrapper per element on every scan (<code>clear()</code> even removed entries one at a time through the iterator). Values are stored unchanged, so direct delegation is semantics-preserving; MultiKeyMap-backed instances keep the entrySet route (MultiKeyMap's values view has different semantics). Measured (10K entries): <code>containsValue</code> miss 34.6 → 21.4 µs (~1.6x), fill+clear cycle 28.3 → 21.5 µs. Also: <code>putAll</code> from another <code>CaseInsensitiveMap</code> bulk-copies already-normalized keys (skips per-key unwrap/re-wrap); backing maps created by the copy constructor/registry are presized with load-factor headroom so copy-constructi on never rehashes; the <code>copy()</code> bulk path no longer reaches into a MultiKeyMap-backed source's internal key representations; removed dead <code>convertKeys()</code> and orphaned javadoc blocks from previously-removed multi-key methods.</li> <li><strong>PERFORMANCE</strong>: <code>ByteUtilities.encode</code>/<code>decode</code> no longer read three system properties per call when security is disabled (the default) — a single master-switch read now gates the limit checks, mirroring the <code>ArrayUtilities</code> fix below. <code>encode</code> sits on the <code>EncryptionUtilities</code> hash path (every MD5/SHA hex conversion): measured 16-byte digest encode 18.8 → 17.0 ns/op single-threaded, 236 → 277 M ops/s at 8 threads (~17%); larger on JDK 8 where <code>System.getProperty</code> hits a synchronized <code>Hashtable</code>. Runtime toggling still honored; also removed the unused <code>isSecurityEnabled()</code> helper.</li> <li><strong>SECURITY</strong>: <code>ArrayUtilities</code> dangerous-class validation can no longer be bypassed with nested array types: <code>nullToEmpty(Runtime[].class, …)</code>/<code>toArray(Runtime[].class, …)</code> creates a <code>Runtime[][]</code> whose component name <code>[Ljava.lang.Runtime;</code> matched neither the exact nor the package-prefix patterns. Component types are now unwrapped to their leaf type before the pattern check. (Only affects the opt-in <code>arrayutilities.security.enabled</code> mode.)</li> <li><strong>PERFORMANCE</strong>: <code>ArrayUtilities.addAll</code>/<code>addItem</code>/<code>removeItem</code>/<code>toArray</code>/<code>nullToEmpty</code> no longer re-read up to 25 system properties per call. The security validators consulted three boolean accessors that each rebuilt the five-property config snapshot — with security <em>disabled</em> (the default). A single master-switch read now gates the validators (runtime toggling still honored): measured <code>addAll</code> 33.9 → 13.3 ns/op single-threaded (~2.5x), 167 → 269 M ops/s at 8 threads; larger on JDK 8 where <code>System.getProperty</code> hits a synchronized <code>Hashtable</code>.</li> <li><strong>BUG FIX</strong>: <code>ArrayUtilities.setElement</code>/<code>setPrimitiveElement</code> on a <code>boolean[]</code> silently stored <code>false</code> for any non-Boolean element (e.g. <code>"true"</code>, <code>Integer 1</code>); every other primitive branch throws <code>IllegalArgumentException</code> for a type mismatch. Non-Boolean elements now throw. Also <code>addAll</code>/<code>addItem</code> now raise a descriptive <code>IllegalArgumentException</code> when the combined length exceeds the JVM array limit instead of a bare <code>NegativeArraySizeException</code> from <code>int</code> truncation (previously only checked when security was enabled), and <code>getArraySubset</code> documents the correct exception (<code>IllegalArgumentException</code>, not <code>ArrayIndexOutOfBoundsException</code>) for <code>start > end</code>.</li> <li><strong>BUG FIX</strong>: <strong><code>ConcurrentNavigableMapNullSafe</code> — distinct non-Comparable keys whose identity hash codes collide no longer silently overwrite each other.</strong> With no comparator, same-class non-Comparable keys are ordered by <code>System.identityHashCode</code>; identity hashes are 31-bit, so at ~50K such keys a collision is more likely than not, and colliding keys compared equal — the second <code>put</code> replaced the first with no error. Colliding distinct keys now receive stable sequence numbers from a weak-keyed registry (Guava <code>Ordering.arbitrary()</code> approach) touched only on actual collision, so the common comparison paths are unchanged. Proven by a test that manufactures a real identity-hash collision via the birthday bound.</li> <li><strong>BUG FIX</strong>: <code>AbstractConcurrentNullSafeMap.equals()</code> (affects <code>ConcurrentHashMapNullSafe</code>, <code>ConcurrentNavigableMapNullSafe</code>) returned <code>true</code>/threw depending on the other map: comparing a map holding a <code>null</code> key against a null-hostile map (<code>ConcurrentHashMap</code>, <code>Hashtable</code>, <code>TreeMap</code>) threw <code>NullPointerException</code> from <code>other.get(null)</code> instead of returning <code>false</code>. Now guarded with the same <code>ClassCastException</code>/<code>NullPointerException</code> handling as <code>AbstractMap.equals</code>.</li> <li><strong>BUG FIX</strong>: <code>AbstractConcurrentNullSafeMap.computeIfAbsent</code> removed an existing <code>key → null</code> mapping when the mapping function returned null; JDK semantics (<code>HashMap</code>, <code>Map</code> default method) retain the entry — only new mappings are suppressed. <code>containsKey</code> no longer flips to <code>false</code>. Also <code>compute()</code> now fails fast on a null remapping function like its siblings, and the <code>ConcurrentHashMapNullSafe(Map)</code> copy constructor no longer double-compensates for the load factor (which could allocate a 2x-sized table).</li> <li><strong>API REMOVAL</strong>: <code>MapUtilities.cloneMapOfSets</code> and <code>MapUtilities.cloneMapOfMaps</code> removed. Despite the "deep clone" javadoc, the <code>immutable=true</code> variants returned unmodifiable <em>views</em> over the original inner collections — mutations to the source showed through the "clone," making them a defensive-copy trap. No known consumers (json-io and the rest of the Cedar ecosystem never used them); <code>dupe()</code> remains and does copy-then-wrap correctly.</li> <li><strong>PERFORMANCE</strong>: <strong>Six of the seven <code>ReflectionUtils</code> caches switched from the LOCKING to the THREADED LRU strategy</strong> (method, field-name, constructor, sorted-constructors, class-annotation, method-annotation). These are populate-once and read-almost-always, yet the LOCKING strategy's <code>get()</code> takes the cache's exclusive <code>ReentrantLock</code> via <code>tryLock()</code> to reorder its LRU list on every hit; even non-blocking, that CAS on one shared lock word ping-pongs the cache line across cores, so concurrent reflection barely scaled (measured 8 threads ≈ 1 thread). THREADED <code>get()</code> is lock-free (<code>ConcurrentHashMap</code>-backed, sampled background eviction): <strong>~1.3x single-threaded and ~3x at 8 threads</strong> (35M → ~110M lookups/s on a 12-core box) for the shared <code>getMethod</code>/<code>getField</code> hot path json-io hammers during multi-threaded (de)serialization. (<code>FIELDS_CACHE</co de> intentionally stays on LOCKING — see below.) All THREADED caches share one daemon cleanup thread, so no per-cache threads are added.</li> <li><strong>CORRECTNESS</strong>: <strong><code>ThreadedLRUCacheStrategy</code> recency tracking was frozen — the "LRU" cache actually evicted FIFO.</strong> <code>Node.updateTimestamp()</code> gated its refresh on the node's own stored timestamp (<code>this.timestamp & 0x7</code>), but once a node's timestamp landed on a non-multiple-of-8 value — 7/8 of the time, immediately — the gate was false forever and the timestamp never advanced again regardless of access. So recency was pinned at insertion time: a hot-but-old entry was evicted ahead of cold, recently-inserted ones (measured: a key accessed 50,000× survived sample-eviction in <strong>0/40</strong> trials). Fixed by gating on a per-access <code>ThreadLocalRandom</code> draw (contention-free), restoring true ~12.5%-sampled LRU (<strong>40/40</strong>). This is why the reflection-cache speedup above is ~3x rather than the ~10x an earlier build showed — that build's higher number came from this bug (froz en timestamps mean zero recency-write contention); correct LRU touches the shared recency clock on ~1/8 of hits, which is the right trade for an eviction-correct cache and still clears LOCKING by ~3x.</li> <li><strong>CORRECTNESS</strong>: <code>ReflectionUtils.FIELDS_CACHE</code> kept on the LOCKING strategy (the other six moved to THREADED). Its deep variant (<code>getAllDeclaredFields</code>) populates the cache from inside its own <code>computeIfAbsent</code> mapping by calling <code>getDeclaredFields</code>, which does <code>computeIfAbsent</code> on the <strong>same</strong> cache. LOCKING uses a reentrant lock and tolerates that same-thread nesting; THREADED delegates to <code>ConcurrentHashMap.compute()</code>, whose nested update on the same map throws <code>IllegalStateException("Recursive update")</code> when the two keys collide in a bin (reproduced: a cold <code>getAllDeclaredFields</code> sweep threw intermittently).</li> <li><strong>BUG FIX</strong>: <code>ReflectionUtils.isTrustedCaller()</code> (dangerous-class security gate) now recognizes cache-infrastructure stack frames even when they are lambda/inner-class frames (<code>Owner$$Lambda...</code>), and includes <code>AbstractConcurrentNullSafeMap</code>. The THREADED cache invokes the security check from inside a <code>com.cedarsoftware.util.*</code> cache-internal lambda; the previous exact-<code>equals</code> frame check would have mis-read that lambda as a trusted java-util caller and bypassed the dangerous-class block for external callers. (This tightens the trusted set — strictly safer — and only matters when the opt-in security features are enabled.)</li> <li><strong>ROBUSTNESS</strong>: <code>ReflectionUtils.getClassNameFromByteCode</code> throws its documented <code>IllegalStateException</code> instead of a <code>NegativeArraySizeException</code> when given a malformed class file whose <code>constant_pool_count</code> is 0.</li> <li><strong>BUG FIX</strong>: <strong><code>Converter</code> user conversions can no longer be silently masked by the shared resolution cache.</strong> <code>convert()</code> and <code>isConversionSupportedFor()</code> consulted the static <code>FULL_CONVERSION_CACHE</code> <em>before</em> the instance's user conversions, so (a) an override supplied via <code>ConverterOptions.getConverterOverrides()</code> was ignored for any (source, target) pair previously converted by ANY plain Converter instance in the JVM (json-io's default converter populates this cache constantly, so in practice constructor overrides for built-in pairs never fired), and (b) an <code>addConversion()</code> override worked only until any plain instance converted the same pair and repopulated the shared cache — after which the override instance silently reverted to the built-in conversion (order-dependent, race-prone). This also affected the static <code>Converter.addConversion()</code> facade API. User conv ersions (guarded by a flag, so the no-override hot path is unchanged — verified ~8 ns/op cache hits before and after) are now consulted first, per the documented precedence.</li> <li><strong>BUG FIX</strong>: <code>Converter</code> — overrides supplied via <code>ConverterOptions</code> now receive the same primitive/wrapper variation expansion as <code>addConversion()</code>: an override registered with <code>int.class</code> also fires for <code>Integer.class</code> lookups and vice versa. Previously only the exact registered pair matched, so the unregistered variant silently fell through to the built-in conversion.</li> <li><strong>PERFORMANCE</strong>: <code>Converter</code> negative lookup results are now actually cached. <code>isConversionSupportedFor()</code> always intended to cache "unsupported" results, but the cache-write helper silently refused the UNSUPPORTED sentinel, so every repeated miss re-walked the full inheritance pair list — and json-io's <code>Resolver</code> probes these per value during graph resolution. Misses now resolve O(1) after first check: measured 41–52 → 4–8 ns/op (shallow hierarchy; deep hierarchies benefit far more). Safe with the precedence fix above: instances holding user conversions check their own conversions before trusting a shared negative, and <code>addConversion()</code> purges affected entries.</li> <li><strong>PERFORMANCE</strong>: <code>Converter.getInheritedConverter</code> skips the guaranteed-empty user-conversion probe per inheritance pair on instances with no user conversions (the common case), halving map lookups on the inheritance-resolution walk.</li> <li><strong>PERFORMANCE</strong>: <strong>Lock-free fast path for the <code>ClassUtilities.forName</code> name cache.</strong> The per-ClassLoader class-name cache sits behind a synchronized <code>WeakHashMap</code>, so every <code>forName()</code> hit — the json-io <code>@type</code>-resolution hot path — took one global lock, which didn't just cap concurrency, it inverted it (measured 12-core: 1 thread ≈ 123 M ops/s, 8 threads ≈ 22 M ops/s <em>aggregate</em>). A one-entry (loader → cache) memo, identity-checked and held via <code>WeakReference</code> so it never pins a ClassLoader, now fronts the map: 8 threads <strong>22 M → ~1.1 B ops/s (~51×)</strong>, single-thread <strong>123 → ~188 M ops/s (~1.5×)</strong>. <code>clearCaches()</code> drops the memo with the map; per-loader isolation is pinned by new tests.</li> <li><strong>BUG FIX</strong>: <code>ClassUtilities.newInstance</code> — array-typed constructor parameters now default to an <strong>empty array of the correct type</strong>. Every Java array implements <code>Cloneable</code>, so the assignable-interface scan in the default-value lookup matched <code>Cloneable → ArrayList</code> for any array type outside the direct map (e.g. <code>Date[]</code>, <code>String[][]</code>), a type-incompatible default that forced the first constructor pass to fail and fall back to <code>null</code>; the intended empty-array branch was unreachable.</li> <li><strong>BUG FIX</strong>: <code>ClassUtilities.newInstance</code> argument matching passed <code>(target, source)</code> to <code>Converter.isSimpleTypeConversionSupported(source, target)</code> — swapped. Mostly masked because the simple-type matrix is largely bidirectional, but direction-specific registrations (e.g. user converter overrides, one-way conversions) were judged backwards in the convertible-match phase.</li> <li><strong>SECURITY</strong>: <code>ClassUtilities.SecurityChecker</code> hierarchy check now consults the blocked-<strong>name</strong> list for supertypes as well as the blocked-class set. Previously a class that merely extends/implements a blocked-by-name type (e.g. a subclass of <code>javax.script.ScriptEngineManager</code>, or a <code>javax.script.ScriptEngine</code> implementation) passed <code>verifyClass()</code> because only its own name was compared; loading such a type by its blocked name would have been refused.</li> <li><strong>SECURITY / ROBUSTNESS</strong>: <code>ClassUtilities.forName</code> catches <code>LinkageError</code> (e.g. <code>ClassFormatError</code>, <code>UnsupportedClassVersionError</code>, <code>NoClassDefFoundError</code> from a broken dependency) and returns <code>null</code> per its contract, instead of letting an <code>Error</code> raised by a bad — possibly attacker-supplied — type name escape and kill the calling thread.</li> <li><strong>MEMORY LEAK</strong>: <strong><code>ClassUtilities.SecurityChecker</code> no longer pins every verified class — and its ClassLoader — for the JVM's lifetime.</strong> The security-verdict path recorded each checked class in two static strong-reference <code>ClassValueSet</code> memo caches (<code>VERIFIED_SAFE_CLASSES</code> / <code>INHERITS_FROM_BLOCKED</code>). Those sets could never produce a hit the fronting <code>ClassValue</code> verdict cache wouldn't — the verdict computes at most once per <code>Class</code> lifetime, and a re-loaded class is a new identity that must be re-verified — but because <code>verifyClass</code> runs unconditionally in <code>newInstance</code>, every class instantiated through <code>ClassUtilities</code>/json-io was retained forever, surviving even <code>clearCaches()</code>, leaking ClassLoaders in redeploy/dynamic-class environments. Removed the memo sets (the <code>ClassValue</code> entries live inside each <code>Class</code> and pin nothing); proven by a disposable-ClassLoader GC test that fails before and passes after. Also folded <code>ASSIGNABLE_TYPE_CACHE</code> (keyed by arbitrary constructor argument types) into <code>clearCaches()</code> — it was the one remaining per-user-class static cache with no release path — and documented the strong-retention trade-off of long-lived sets in <code>ClassValueSet</code>'s javadoc.</li> <li><strong>BUG FIX</strong>: <strong><code>CollectionUtilities.deepCopyContainers</code> silently lost elements when copying sets of collections.</strong> Element copies were inserted into content-sensitive parent collections (<code>LinkedHashSet</code>, <code>TreeSet</code>, <code>PriorityQueue</code>) while still empty — the iterative work queue fills them later — so two distinct-but-unfilled containers compared equal and were deduplicated (runtime-proven: a <code>Set</code> of two lists copied to a one-element set, same for <code>TreeSet</code> with a content comparator; content-inspecting comparators could also throw on the empty shells, and <code>PriorityQueue</code> heaps were built from wrong comparisons). Elements destined for such targets are now buffered and installed after the whole graph is copied, children before parents; pure cycles fall back deepest-first, and <code>EnumSet</code> still fills directly (enum elements are berries, never copies). Also affects the <code>ArrayUtilities.deepCopyContainers</code> delegate. While there: <code>setOf</code> presizes its <code>LinkedHashSet</code> with load-factor headroom so construction never rehashes (the old <code>new LinkedHashSet<>(n)</code> rehashed for power-of-2 sizes).</li> <li><strong>BUG FIX / FEATURE</strong>: <strong><code>CompactMap.builder()</code> / <code>newMap()</code> no longer refuse to run on a JRE.</strong> Both entry points still guarded on <code>javax.tools.JavaCompiler</code> being present — vestigial from the old javac-based implementation — and threw <code>IllegalStateException</code> in JRE-only environments, even though the current implementation patches a pre-compiled bytecode template and needs no compiler at all (the class javadoc already promised "works on both JDK and JRE"). Guards removed, docs reconciled; proven with a simulated-JRE test (<code>java.util.force.jre</code>).</li> <li><strong>BUG FIX</strong>: <strong><code>CompactMap</code> got permanently stuck in backing-MAP state when a null-valued key was removed at the downsize boundary.</strong> <code>removeFromMap</code> judged key presence by calling <code>containsKey</code> <em>after</em> <code>map.remove()</code> — which can never distinguish "was absent" from "was present mapped to null" — so removing a null-valued mapping returned early and skipped the MAP→array (at <code>compactSize</code>) and →EMPTY transitions. Since the array transition only fires exactly at the boundary, missing it once meant the map never compacted again (defeating the point of the class). Presence is now judged by size delta.</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/jdereg/java-util/commit/229429fd54c90da194c3c1171920443557b3f6ff"><code>229429f</code></a> Docs: set 4.106.0 changelog date to release date 2026-07-04</li> <li><a href="https://github.com/jdereg/java-util/commit/2e368f2dccc840309c76b856ebb897b988880966"><code>2e368f2</code></a> Build: remove unused jackson-databind test dependency (resolves Dependabot <a href="https://redirect.github.com/jdereg/java-util/issues/14">#14</a>)</li> <li><a href="https://github.com/jdereg/java-util/commit/90ed990587e2fbedc993ecc7d3f241d74b21913a"><code>90ed990</code></a> Docs: infographic + README — CaseInsensitiveMap, 1,800+ conversions, JDK 8+, ...</li> <li><a href="https://github.com/jdereg/java-util/commit/aa4544399e398e7ec16128c404ef98549f0513e3"><code>aa45443</code></a> Deprecate UrlUtilities for removal in 5.0</li> <li><a href="https://github.com/jdereg/java-util/commit/c297aa05b8ec880eb9e517298455f36ee5ff484c"><code>c297aa0</code></a> Doc/Perf: Traverser deprecation links; TrackingMap readKeys presize</li> <li><a href="https://github.com/jdereg/java-util/commit/8c1c674885d2d78af64658ea88a2ebe8dd5a0765"><code>8c1c674</code></a> Bug Fix: RegexUtilities ReDoS pool starvation; SafeSimpleDateFormat clone/ser...</li> <li><a href="https://github.com/jdereg/java-util/commit/bbeb7cc5f345c70429acee008613a1462020a7ee"><code>bbeb7cc</code></a> Bug Fix: MultiKeyMap resize size-collapse; MapUtilities/LoggingConfig cleanups</li> <li><a href="https://github.com/jdereg/java-util/commit/38f77665e67fd41b0e7ba61e4f3a292c10343f2e"><code>38f7766</code></a> Bug Fix: IOUtilities locale + SSRF upload gap; IntervalSet reader atomicity; ...</li> <li><a href="https://github.com/jdereg/java-util/commit/6a3b8b9998af84f0b42c525d15b0f852032dab34"><code>6a3b8b9</code></a> Test: pin CaseInsensitiveSet/Map copy-constructor backing dispatch</li> <li><a href="https://github.com/jdereg/java-util/commit/4f9cb262f7825fa4995ce83ca648822786bd7ace"><code>4f9cb26</code></a> Harden CaseInsensitiveMap/Set against double-wrapping</li> <li>Additional commits viewable in <a href="https://github.com/jdereg/java-util/compare/4.105.0...4.106.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
