Skip to content

Releases: jdereg/java-util

4.108.0

Choose a tag to compare

@jdereg jdereg released this 12 Jul 22:08

java-util 4.108.0

A small, test-only release shipped in lock-step with json-io 4.108.0 (which fixes a slow-path escape bug in its CharStreamTokenizer — the bug was not in java-util).

Testing — FastReader buffer-boundary regression coverage. New differential sweeps exercise the internal 8192-char buffer refill boundary (and 16384) for readUntil, readLine (including a \r\n line ending straddling the boundary), bulk read(char[]), and the borrowed readUntil + fallback pattern. Each test reconstructs the stream and compares against the original, confirming FastReader handles delimiters and line endings correctly across a refill. No behavioral change.

The json-io test-scope dependency remains 4.107.0 — it always trails java-util by one release to avoid the java-util↔json-io dependency cycle.


Full changelog: https://github.com/jdereg/java-util/blob/master/changelog.md

4.107.0

Choose a tag to compare

@jdereg jdereg released this 06 Jul 15:10

java-util 4.107.0

Performance (concurrency) — removed the dominant multi-threaded deserialization lock.
ClassUtilities.trySetAccessible probed a Collections.synchronizedMap(WeakHashMap) on every object instantiation and field injection. Under concurrent load that single monitor serialized worker threads — 13-thread profiling of concurrent json-io reads showed ~74% of threads BLOCKED on it. It's now a lock-free ConcurrentHashMap; post-change profiling shows zero threads blocked. Single-threaded performance is unchanged. This directly improves json-io's concurrent read scaling.

Performance — ReflectionUtils.getAllConstructors(Class) no longer allocates on a cache hit. It's now keyed directly by Class with a get()-first fast path (no per-probe key allocation, no capturing compute lambda on hits); the redundant SortedConstructorsCacheKey wrapper was removed. Anti-poisoning/security semantics are unchanged.

Documentation — ClassValueMap / ClassValueSet Javadoc, userguide.md, and README.md now document their critical usage constraints. They are specialists for a few, pre-populated, read-mostly class registries — not general-purpose faster Map<Class,V> / Set<Class>. The docs now cover: the non-local cost (every Class has one small, PROBE_LIMIT-bounded ClassValue cache array shared across all ClassValue instances, so many instances thrash each other and the advantage inverts past ~10); that lazy get-then-put population is an anti-pattern (put() invalidates the whole ClassValue → ≈O(N²) warm-up); and that the strong-referencing backing store forfeits ClassValue's leak-safety. No behavioral change.


Full changelog: https://github.com/jdereg/java-util/blob/master/changelog.md

4.106.0

Choose a tag to compare

@jdereg jdereg released this 04 Jul 17:05

java-util 4.106.0

Maven Central: com.cedarsoftware:java-util:4.106.0

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 changelog.md.

Highlights

Security

  • Turkish-locale defect class fixed across DateUtilities, IOUtilities, and SystemUtilities — default-locale case conversion (e.g. "Windows""wındows", "FILE""fıle") silently disabled Windows system-path checks and protocol allow-listing; all security-relevant conversions now use Locale.ROOT.
  • ReDoS timeout protection now actually stops runaway matches in DateUtilities and RegexUtilitiesjava.util.regex 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 CharSequence.
  • IOUtilities upload paths now get the same SSRF protocol validation as downloads; ClassUtilities blocked-supertype and ClassLoader-leak fixes.

Correctness

  • MultiKeyMap — lock-free size()/isEmpty() no longer transiently collapse toward 0 during a resize.
  • CaseInsensitiveMap/CaseInsensitiveSet — serialization now works, and copy-constructors preserve the source's concurrency/ordering.
  • SafeSimpleDateFormatclone() no longer entangles the copy with the original; serialization fixed.
  • ConcurrentList deadlock hazard, IntervalSet reader atomicity, DeepEquals large-value hash saturation, Converter user-override cache masking, and more.

Performance

  • Lock-free read fast paths for ClassUtilities.forName (~51× at 8 threads) and the ReflectionUtils caches (~3× at 8 threads).
  • CompactMap/CompactSet back to a single instance field (memory) with faster equals/hashCode; Converter negative-result caching; widespread collection presizing.

API

  • UrlUtilities is deprecated, scheduled for removal in 5.0 — no ecosystem consumers; prefer java.net.http.HttpClient and IOUtilities.

JDK compatibility: 8+.

4.105.0

Choose a tag to compare

@jdereg jdereg released this 13 Jun 14:39

java-util 4.105.0

Maven Central: com.cedarsoftware:java-util:4.105.0

Highlights

Bug fix (supersedes 4.104.0)

  • DateUtilities.parseDate — the 4.104.0 strict-ISO fast path mishandled bracketed [zone] forms (e.g. ...Z[GMT], ...-04:56[America/New_York]): it skipped the GMT → Etc/GMT zone normalization and trusted a minute-truncated written offset over the zone region, shifting the instant by the dropped offset-seconds for ancient LMT dates. Such forms now defer to the flexible path, which reconciles the region against the offset and preserves the instant. Common machine forms (trailing Z, bare offset) keep the fast path. 4.104.0 is superseded by this release.

Also in the 4.104.x line (carried from 4.104.0): strict-ISO fast path in DateUtilities.parseDate for fully-zoned forms, and a lock-free read fast path for the ReflectionUtils caches (8-thread contended micro-benchmark 42 → 141 M ops/s).

JDK compatibility: 8 – 26.

4.104.0

Choose a tag to compare

@jdereg jdereg released this 13 Jun 13:37

java-util 4.104.0

Maven Central: com.cedarsoftware:java-util:4.104.0

Highlights

Performance

  • Strict-ISO fast path in DateUtilities.parseDate. Fully-zoned ISO-8601 date-times (the wire format json-io / Jackson / java.time.toString() emit) parse via the JDK's strict parsers (Instant.parse / ZonedDateTime.parse) before the regex recognizer; every Converter String → temporal conversion benefits. Output parity is exact — bare-offset keeps GMT±HH:MM zones, +00:00GMT, zone-less uses the caller's default ZoneId, non-ISO/epoch formats unchanged — and bracketed-offset forms like ...+05:30[+05:30] now parse instead of throwing. Per-parse: trailing-Z instants 759 → ~470 ns, nanos 924 → ~480 ns, bare-offset 849 → ~600 ns, bracketed-zone 1212 → ~690 ns.
  • Lock-free read fast path for the ReflectionUtils caches. The populate-once constructor / method / field / annotation caches went through Map.computeIfAbsent, whose default LRUCache LOCKING strategy takes the cache's exclusive lock even on a hit — serializing concurrent reflection (e.g. multi-threaded deserialization). Lookups now try the non-blocking get() first, falling back to computeIfAbsent only on a miss; behavior is identical (negative lookups are sentinels, never null). Single-class contended micro-benchmark (12-core, 8 threads): 42 → 141 M ops/s (~3.3×) — the old path never scaled past ~42 M ops/s at any thread count. Single-threaded marginally faster; no downside.

JDK compatibility: 8 – 26.

4.103.0

Choose a tag to compare

@jdereg jdereg released this 06 Jun 15:16

java-util 4.103.0

Maven Central: com.cedarsoftware:java-util:4.103.0

Highlights

New APIs

  • com.cedarsoftware.util.internal.VectorizedArrays — exposes equalsRange / mismatchRange / compareRange for both char[] and byte[]. Dispatches at runtime to JDK 9+ SIMD-vectorized Arrays.* intrinsics, with hand-rolled loop fallbacks for JDK 8. Resolution happens once at class load via MethodHandles, so per-call cost is a static-field read + invokeExact. Internal package for now; promoted to public API once the contract stabilizes.
  • Converter.convert(String, byte[].class) (and transitively String → ByteBuffer) performs multi-format detection — stringified JSON number arrays, spaced hex, unspaced hex (length ≥ 8, catches CAFEBABE/DEADBEEF magic numbers, compact UUIDs, SHA hashes), URL-safe Base64, then standard Base64. Tightness rules prevent short tokens like "DATA"/"ABCD" from being mis-classified and let them fall through to the historical charset path.
  • MapConversions.toByteArray (and refactored toByteBuffer) handles the wrapped-form contract {"@type":"<type>","value":"<base64>"}. Strict Base64 decode first to preserve round-trip for short blobs; falls through to general Converter dispatch on failure. Enables clean round-trip of the wrapped form that JsonGenerator.writeBinary emits in json-io 4.103.0.

Performance

  • Cycle-tracking IdentityHashMap allocation deferred to first-need across ArrayConversions.arrayToArray / collectionToArray and CollectionConversions.arrayToCollection / collectionToCollection — and skipped entirely for primitive-target conversions (primitive arrays can't form cycles). Profiling showed ~560 ms of accumulated allocation+put cost in arrayToArray alone.
  • ClassUtilities.newInstance() — cached constructor plans now fast-invoke non-varargs constructors when supplied arguments are already in constructor-parameter order and directly assignable, bypassing matcher allocations on repeated positional construction. No-arg cached construction reuses the empty argument-shape key and skips argument matching. Plan-cache entries retain constructor parameter metadata, avoiding repeated Constructor.getParameters() calls.

Build

  • Test-scope dependency bumps — junit-jupiter 5.14.3 → 5.14.4; agrona 1.22.0 → 1.23.1 (still JDK 8 compatible).
  • Registered the JDK 9+ standard @apiNote / @implSpec / @implNote tags with maven-javadoc-plugin so the tool no longer emits "unknown tag" warnings.

Full changelog: https://github.com/jdereg/java-util/blob/master/changelog.md

4.102.0

Choose a tag to compare

@jdereg jdereg released this 06 Jun 15:16

java-util 4.102.0

Maven Central: com.cedarsoftware:java-util:4.102.0

Highlights

Performance

  • Vendored Werner Randelshofer's FastDoubleParser (MIT, Java 8) under com.cedarsoftware.util.fastdoubleparser. Eisel-Lemire algorithm for Double/Float; recursive-multiplication for BigDecimal/BigInteger. 2-4× faster than Double.parseDouble; 5-50× faster than new BigInteger(String) on 100+ digit values. Keeps java-util dependency-free (mirrors Jackson's vendoring choice).
  • MathUtilities — added 20 fast number-parsing entry points (parseDouble/parseFloat/parseBigDecimal/parseBigInteger × CharSequence/char[]/char[]+offset+length/byte[]/byte[]+offset+length). The char[]/byte[] overloads avoid intermediate String materialization. Converter StringConversions routes through them, so Converter.convert(String, Double.class) and analogous calls pick up the speedup transparently.
  • DateUtilities — two regex hot-path fixes: cached security-config getters now use a volatile tri-state instead of System.getProperty(...) per parse (removes the synchronized Hashtable lock); per-Pattern thread-local Matcher reuse instead of pattern.matcher(input) per call. Together: ~2.7s self-time eliminated on JsonPerformanceTest.
  • RegexUtilities.SafeMatchResult — replaced eager group extraction with a lazy MatchResult snapshot. Unaccessed capturing groups no longer allocate a String per call. ~840ms of JsonPerformanceTest wall time recovered.
  • FastReader — added borrowed-slice fast paths readUntilBorrowed(...) and readLineBorrowed(...) for callers that can consume directly from the internal buffer. Existing delimiter semantics preserved; falls back cleanly to copying APIs when pushback is active or the token crosses a buffer boundary.

Correctness

  • FastReader.readUntilBorrowed() now clamps its scan boundary by available buffer length instead of computing pos + maxLen (which could overflow on very large maxLen with a non-zero buffer position).
  • FastReader.BufferSlice — added assertion-checked release() lifecycle for borrowed slices. Callers must release before the next read/pushback/close; outstanding borrows fail fast under -ea. Production behavior unchanged.

Build

  • jackson-databind test dependency bumped 2.21.2 → 2.21.3 (test-scope only).

Full changelog: https://github.com/jdereg/java-util/blob/master/changelog.md

4.101.0

Choose a tag to compare

@jdereg jdereg released this 19 Apr 22:19

java-util 4.101.0

Maven Central: com.cedarsoftware:java-util:4.101.0

Highlights

New APIs

  • ClassValueMap.getByClass(Class) — typed fast-path lookup that skips the instanceof Class guard in get(Object), compiling to a near-direct ClassValue.get(type) call. Converted 30+ hot-path call sites across ClassUtilities, Converter.ClassPairMap, MultiKeyMap, TypeUtilities, Unsafe, CompactMap, and CollectionHandling.
  • ClassValueSet.containsClass(Class) — parallel typed fast path on the Set side. Skips the null check and instanceof Class guard in contains(Object). Converted 9 hot-path call sites in MultiKeyMap (flatten/hash/equals paths) and ClassUtilities.SECURITY_CHECK_CACHE.
  • ReflectionUtils.getDirectClassAnnotation(Class, Class) — strict JDK-semantics companion to getClassAnnotation. Matches Class.getAnnotation(Class) exactly (respects @Inherited, never walks interfaces), for identity-tied annotations like @Entity, @JsonNaming, @IoNaming.
  • StringUtilities.getChars(String, int) + getChars(String) — SIMD bulk-copy helpers for hot-path charAt-loop replacements.

Performance

  • Converter — refactored 4 internal caches (CONVERSION_DB, FULL_CONVERSION_CACHE, USER_DB, cacheInheritancePairs) from Map<ConversionPair, V> to nested ClassValueMap<ClassValueMap<V>>, eliminating per-lookup ConversionPair allocation from every convert() call. ConversionPair is gone from the JFR hot-path allocation top-5.
  • Converter.USER_DB — dropped redundant instanceId from cache keys (legacy from an earlier static design).
  • FastReader.readUntil / readLine — 30% reduction in GETFIELD field accesses on the common path (scan-loop local hoisting, elided unconditional PUTFIELD writes, refill moved to loop top).
  • FastReader.readLine(char[], int, int) — ~22% self-time reduction via explicit CSE hoisting of b[scanPos].
  • StringUtilities — SIMD-intrinsic String.getChars() bulk-copy pattern applied to levenshteinDistance, damerauLevenshteinDistance, and decode(String) (hex). Loop-invariant chars hoisted out of the Levenshtein inner loops.

Correctness

  • ClassValueMap — marked cachedEntrySet, cachedValues, cachedUnmodifiableView volatile to close a latent DCL publication race.
  • ReflectionUtils.getAllConstructorsInternal() — added deterministic tie-breaker to constructor sort (fixes ~30% intermittent failure rate in ClassUtilitiesVarargsArrayStoreTest under JaCoCo).

Security

  • ReflectionUtils.isTrustedCaller() — stack walk no longer misidentifies java-util's own internal cache infrastructure as a trusted caller. Previous prefix-match allowed external callers to bypass dangerous-class blocking on paths routed through cache-populating lambdas.

Build

  • JaCoCo code coverage plugin wired in permanently. Reports auto-generated on every mvn test at target/site/jacoco/.

Testing

  • 19,964 tests total — +380 new coverage tests across EncryptionUtilities, ReflectionUtils, UrlUtilities, IOUtilities, DeepEquals, MultiKeyMap, ClassUtilities, plus new ClassValueMapTest and ClassValueSetTest entries for the typed fast paths.

Full changelog: https://github.com/jdereg/java-util/blob/master/changelog.md

4.100.0

Choose a tag to compare

@jdereg jdereg released this 10 Apr 13:23

What's Changed

  • BUG FIX: Fixed unbounded memory leak in Converter.FULL_CONVERSION_CACHE. Each short-lived Converter instance (e.g., one per JsonIo.toJava() call) cached inheritance-resolved conversions keyed by its unique instanceId in a static ConcurrentHashMap. When the instance was GC'd, its entries remained forever. In production under sustained load, this grew to 1GB+. Fix: cache resolved conversions at the shared level (instanceId=0L) when no user-added conversions exist; skip static caching otherwise. The cache is now bounded by the number of unique (source, target) type pairs, not by the number of Converter instances created.

  • PERFORMANCE: Optimized Converter.getCachedConverter() to skip the now-unnecessary instance-specific lookup, eliminating a wasted ConversionPair allocation per call.

  • Updated test dependencies: JUnit 5.14.2→5.14.3, Jackson 2.21.1→2.21.2

4.99.0

Choose a tag to compare

@jdereg jdereg released this 31 Mar 00:47

4.99.0 - 2026-03-28

  • FEATURE: New StringUtilities.getChars(String s) — public API that returns a ThreadLocal char[] buffer populated via String.getChars() (SIMD-optimized bulk copy). Callers can replace str.charAt(i) loops with direct buf[i] array access.
  • PERFORMANCE: StringUtilities.hashCodeIgnoreCase(String) — uses StringUtilities.getChars() into a ThreadLocal char[] buffer, then hashes from the array directly. CaseInsensitiveMap GET improved 70-75%, PUT improved 48-52%, and MIXED-CASE GET improved 68% on 100K entries.
  • PERFORMANCE: New FastReader.readLine(char[] dest, int off, int maxLen) — dedicated line-reading method optimized for TOON's line-oriented parsing. Combines scanning, copying, and line-ending consumption into a single call. JFR shows TOON line-reading improved 28%.
  • PERFORMANCE: FastReader.readUntil() pushback drain loop now uses a local variable for pushbackPosition, avoiding load/store through this on each iteration. JFR shows 14.8% reduction in FastReader CPU share.
  • PERFORMANCE: FastReader.readUntil() replaced Math.min() with inline ternary in tight buffer-scan loop. JFR confirmed 3.5% wall-clock improvement.
  • PERFORMANCE: FastReader.readUntil() now caches this.limit into a local variable after fill(). JFR shows readUntil self-time dropped ~15%.
  • PERFORMANCE: FastReader.fill() extracted core I/O into refill() which uses a local int n for the in.read() result, reducing PUTFIELD/GETFIELD overhead.