<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://infinispan.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://infinispan.org/" rel="alternate" type="text/html" /><updated>2026-10-29T14:55:56+00:00</updated><id>https://infinispan.org/feed.xml</id><title type="html">Infinispan</title><subtitle>Infinispan is an open-source, distributed, in-memory database with full-text and vector search</subtitle><entry><title type="html">Fast, Non-Blocking Conflict Resolution with Merkle Trees</title><link href="https://infinispan.org/blog/2026/10/20/merkle-tree-conflict-resolution" rel="alternate" type="text/html" title="Fast, Non-Blocking Conflict Resolution with Merkle Trees" /><published>2026-10-20T10:00:00+00:00</published><updated>2026-10-20T10:00:00+00:00</updated><id>https://infinispan.org/blog/2026/10/20/merkle-tree-conflict-resolution</id><content type="html" xml:base="https://infinispan.org/blog/2026/10/20/merkle-tree-conflict-resolution"><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>Network partitions are an unfortunate reality when operating a distributed data grid—right up there with TCP retransmits and the discovery that your "five nines" availability calculation didn&#8217;t account for leap seconds.
When a network split occurs, different partitions may continue servicing writes, causing them to diverge.
Once the network heals and the partitions merge, Infinispan&#8217;s partition handling and conflict resolution mechanisms reconcile divergent states to restore consistency.</p>
</div>
<div class="paragraph">
<p>Historically, determining which entries conflicted between replicas during a partition merge could be an expensive operation.
Even if only a handful of keys were modified while partitioned, traditional conflict detection often required write owners to scan and transmit all entries within affected segments across the cluster.
For large caches with millions of entries, this created heavy network traffic, CPU spikes, and latency spikes at the exact moment the cluster was attempting to recover.</p>
</div>
<div class="paragraph">
<p>Infinispan 16.3 introduces a new hierarchical <strong>Merkle tree-inspired hash comparison</strong> mechanism that dramatically reduces network traffic during conflict resolution.
The implementation combines this hierarchical tree structure with an <strong>in-flight running XOR hash</strong> computed at entry update time, and re-architects the conflict resolution process to be completely <strong>non-blocking</strong>.</p>
</div>
<div class="paragraph">
<p>This post explores how the hierarchical Merkle tree conflict resolution works, why computing hashes incrementally at write time is essential for scalability, analyzes the Big-O time and network complexities, and presents JMH benchmark results demonstrating the performance benefits.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-problem-partition-recovery-and-the-cost-of-full-segment-comparison">The Problem: Partition Recovery and The Cost of Full Segment Comparison</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Infinispan splits the cache keyspace into a fixed number of consistent hash segments (typically 256).
Each segment is owned by one or more nodes depending on the configured <code>numOwners</code>.</p>
</div>
<div class="paragraph">
<p>When a cluster partitions, nodes in separate partitions may update keys in the same segments.
When the partitions merge, the <code>ConflictManager</code> must inspect the replicas on write owners, detect conflicting entry versions, and apply a conflict resolution merge policy (such as <code>PREFERRED_ALWAYS</code>, <code>REMOVE_ALL</code>, or a custom <code>EntryMergePolicy</code>).</p>
</div>
<div class="paragraph">
<p>Previously, discovering whether replicas diverged required pulling all entries for each segment from the replica nodes:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Node A requests all entries for segment $S$ from Node B and Node C.</p>
</li>
<li>
<p>Node A receives every key, value, and metadata record for segment $S$.</p>
</li>
<li>
<p>Node A compares each entry version to detect conflicts.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Imagine a segment containing 50,000 entries where each entry&#8217;s average payload is 1 KB.
Resolving a single segment requires transferring ~50 MB of data over the network.
Multiply that across hundreds of segments, and a partition merge could easily saturate network bandwidth with gigabytes of redundant state transfer—even if 99.9% of the entries were completely identical!
It&#8217;s like photocopying an entire phonebook to verify that only one person changed their number.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="hierarchical-merkle-tree-hash-comparison">Hierarchical Merkle Tree Hash Comparison</h2>
<div class="sectionbody">
<div class="paragraph">
<p>To avoid sending unchanged data across the wire, PR #16890 implements a three-level hierarchical hash comparison inspired by Merkle trees:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="/assets/images/blog/merkle-tree-levels.svg" alt="merkle tree levels" width="575" height="412">
</div>
</div>
<div class="sect2">
<h3 id="level-1-segment-level-check">Level 1: Segment Level Check</h3>
<div class="paragraph">
<p>First, write owners compare a single 64-bit aggregate hash for the entire segment.
If all replicas produce identical segment hashes, we know with high probability that all entries and metadata versions in that segment are identical.
The entire segment is skipped immediately: <strong>0 entries transferred over the network</strong>.</p>
</div>
<div class="paragraph">
<p>In real-world network partitions, many segments are never touched during the split.
The segment-level check allows them to be reconciled with a single roundtrip exchanging a few bytes of hash data.</p>
</div>
</div>
<div class="sect2">
<h3 id="level-2-bucket-level-check">Level 2: Bucket Level Check</h3>
<div class="paragraph">
<p>If the segment hashes differ, we know at least one entry has diverged.
Instead of jumping directly to fetching all entries, the segment is subdivided into <strong>32 deterministic buckets</strong> based on the entry hash:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">int bucketIndex = (entryHash &gt;&gt;&gt; 27) &amp; 0x1F; // 32 buckets [0..31]</code></pre>
</div>
</div>
<div class="paragraph">
<p>The nodes exchange bucket hashes using <code>GetBucketHashesCommand</code>.
By comparing the 32 bucket hashes, the coordinator pinpoints exactly which sub-ranges of the segment diverge.</p>
</div>
</div>
<div class="sect2">
<h3 id="level-3-selective-entry-fetch">Level 3: Selective Entry Fetch</h3>
<div class="paragraph">
<p>For each bucket where the hash matches, no entries are fetched.
For buckets where hashes mismatch, the coordinator sends <code>GetBucketEntriesCommand</code> to fetch only the entries residing in those specific buckets.
Replicas then compare only those entries and resolve the conflicts.</p>
</div>
</div>
<div class="sect2">
<h3 id="boundary-optimizations-and-fail-safes">Boundary Optimizations and Fail-safes</h3>
<div class="ulist">
<ul>
<li>
<p><strong>Small Segment Threshold:</strong> For segments with $\le 64$ entries, dividing into 32 buckets adds extra RPC overhead for minimal data savings. If a segment is small, the system skips bucket hashing and directly fetches the segment entries.</p>
</li>
<li>
<p><strong>All-Buckets Fast Path:</strong> If all 32 buckets differ, attempting selective bucket fetching only adds command overhead. The fast path detects this and immediately transitions to streaming the full segment.</p>
</li>
<li>
<p><strong>Fail-Safe Fallback:</strong> If any node experiences a deserialization error, RPC timeout, or unsupported version during hash negotiation, the manager transparently falls back to the legacy full segment reconciliation without failing the merge.</p>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="in-flight-hash-computation-running-xor-hashes">In-Flight Hash Computation: Running XOR Hashes</h2>
<div class="sectionbody">
<div class="paragraph">
<p>While the hierarchical Merkle tree approach is theoretically sound, a critical question remains: where do the segment and bucket hashes originate?</p>
</div>
<div class="paragraph">
<p>A naive approach would iterate the cache data container on-demand when a conflict occurs, hashing every entry in the segment and bucket.
However, this creates a major bottleneck: scanning hundreds of thousands of entries across hundreds of segments at partition merge time causes significant CPU spikes, thread contention, and memory allocations.
Right when the cluster is already recovering, we would be thrashing the CPU with full data scans!</p>
</div>
<div class="paragraph">
<p>To eliminate this cost, we added an <strong>in-flight running XOR hash tracking</strong> mechanism that is only enabled when conflict resolution is configured.</p>
</div>
<div class="sect2">
<h3 id="the-power-of-xor-associativity">The Power of XOR Associativity</h3>
<div class="paragraph">
<p>XOR ($\oplus$) has three fundamental mathematical properties that make it ideal for running distributed set hashes.
If you&#8217;ve ever wondered what XOR is good for besides confusing CS101 students, here&#8217;s your answer:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p><strong>Commutativity:</strong> $A \oplus B = B \oplus A$ (order of entry insertion does not matter).</p>
</li>
<li>
<p><strong>Associativity:</strong> $(A \oplus B) \oplus C = A \oplus (B \oplus C)$.</p>
</li>
<li>
<p><strong>Self-Inversion (Identity):</strong> $A \oplus A = 0$ and $A \oplus 0 = A$.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Because XOR is self-inverting and commutative, we do not need to rescan the container when data changes. We can maintain a running hash incrementally:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Entry Added:</strong>
<code>segmentHash ^= hash(key, value, version)</code></p>
</li>
<li>
<p><strong>Entry Updated (old value replaced by new value):</strong>
<code>segmentHash ^= hash(key, oldValue, oldVersion) ^ hash(key, newValue, newVersion)</code></p>
</li>
<li>
<p><strong>Entry Removed / Evicted:</strong>
<code>segmentHash ^= hash(key, oldValue, oldVersion)</code></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>The same XOR operation is simultaneously applied to the corresponding bucket hash array (<code>bucketHashes[bucketIndex] ^= entryHash</code>).</p>
</div>
</div>
<div class="sect2">
<h3 id="interceptor-architecture">Interceptor Architecture</h3>
<div class="paragraph">
<p>We integrated this into Infinispan&#8217;s interceptor chain via <code>SegmentHashInterceptor</code> and <code>SegmentHashTracker</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">public class SegmentHashTracker {
   private final AtomicLongArray segmentHashes;
   private final AtomicLongArray bucketHashes; // 32 buckets per segment

   public void updateEntry(int segment, long oldEntryHash, long newEntryHash) {
      long delta = oldEntryHash ^ newEntryHash;
      segmentHashes.accumulateAndGet(segment, delta, (current, d) -&gt; current ^ d);

      int bucketIndex = getBucketIndex(newEntryHash);
      int bucketOffset = (segment &lt;&lt; 5) + bucketIndex;
      bucketHashes.accumulateAndGet(bucketOffset, delta, (current, d) -&gt; current ^ d);
   }
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Because hash updates run inside <code>SegmentHashInterceptor</code> during normal <code>put</code>, <code>remove</code>, and eviction operations, maintaining the hash costs only <strong>a few CPU clock cycles ($O(1)$)</strong> per write.</p>
</div>
<div class="paragraph">
<p>When conflict resolution runs:
* Segment hash retrieval is <strong>$O(1)$</strong> (an atomic read).
* Bucket hash retrieval is <strong>$O(1)$</strong> (reading 32 longs).
* <strong>Zero data container iteration</strong> is required during recovery, when no conflicts are present!</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="making-conflict-resolution-fully-non-blocking">Making Conflict Resolution Fully Non-Blocking</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Infinispan has invested heavily in a non-blocking architecture, offloading I/O to Netty and keeping worker threads unblocked.
Conflict resolution cannot be an exception.</p>
</div>
<div class="paragraph">
<p>The updated <code>DefaultConflictManager</code> utilizes asynchronous composition (<code>CompletionStage</code>) from start to finish:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Segment hash requests are dispatched asynchronously across all write owners using non-blocking transport commands.</p>
</li>
<li>
<p>If hashes match, the segment resolution stage completes immediately without switching threads.</p>
</li>
<li>
<p>If hashes mismatch, <code>GetBucketHashesCommand</code> is dispatched asynchronously.</p>
</li>
<li>
<p>Bucket comparisons run concurrently, and only mismatched buckets trigger asynchronous <code>GetBucketEntriesCommand</code> fetches.</p>
</li>
<li>
<p>Entry conflict resolution policies run reactively as chunks arrive, completing a <code>CompletableFuture&lt;Void&gt;</code> when all segments are verified.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>At no point does the conflict resolution flow block the calling thread or Netty event loops.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="big-o-complexity-analysis">Big-O Complexity Analysis</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Let:
* $S$: Total number of segments in the cache.
* $N$: Average number of entries per segment ($Total = S \times N$).
* $B$: Number of buckets per segment ($B = 32$).
* $C$: Number of entries modified or conflicting in a segment during the partition ($0 \le C \le N$).
* $E$: Average byte size of an entry (key + value + metadata).
* $H$: Byte size of a hash ($8$ bytes for a 64-bit long).</p>
</div>
<div class="sect2">
<h3 id="time-complexity-at-conflict-resolution-time">Time Complexity at Conflict Resolution Time</h3>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 1. Time Complexity Comparison (per segment)</caption>
<colgroup>
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 25%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Operation</th>
<th class="tableblock halign-left valign-top">Legacy Full Scan</th>
<th class="tableblock halign-left valign-top">On-The-Fly Merkle Scan</th>
<th class="tableblock halign-left valign-top">In-Flight XOR Merkle</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Hash Computation at Recovery</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">N/A</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$O(N)$</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>$O(1)$</strong></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Segment Comparison</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$O(N)$</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$O(1)$</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>$O(1)$</strong></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Bucket Comparison (if divergent)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">N/A</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$O(B)$</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>$O(B) = O(1)$</strong></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Write / Update Overhead</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$O(1)$</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$O(1)$</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>$O(1)$ (bitwise XOR)</strong></p></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="network-data-transfer-complexity">Network Data Transfer Complexity</h3>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 2. Network Traffic Transferred (per segment)</caption>
<colgroup>
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 25%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Scenario</th>
<th class="tableblock halign-left valign-top">Legacy Conflict Resolution</th>
<th class="tableblock halign-left valign-top">Hierarchical Merkle Tree</th>
<th class="tableblock halign-left valign-top">Traffic Reduction</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>No divergence</strong> ($C = 0$)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$N \times E$ bytes</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>$1 \times H$ bytes (8 bytes)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>$\approx 100\%$ reduction</strong></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Sparse divergence</strong> ($C \ll N$)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$N \times E$ bytes</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$B \times H + \min(C, B) \times \frac{N}{B} \times E$</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>$\approx 90\% - 99\%$ reduction</strong></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Full divergence</strong> ($C \approx N$)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$N \times E$ bytes</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">$B \times H + N \times E$ (fast path)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Overhead bounded by $32 \times 8 = 256$ bytes</p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p>When $C = 0$ (no writes in that segment during partition), network payload drops from tens of megabytes to <strong>8 bytes</strong>.
When only a few keys differ ($C \ll N$), instead of streaming the entire segment, only the mismatched buckets are transferred, cutting network bandwidth consumption by over 90%.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="jmh-benchmark-reproducer-testing-actual-conflict-resolution-in-a-6-node-cluster">JMH Benchmark Reproducer: Testing Actual Conflict Resolution in a 6-Node Cluster</h2>
<div class="sectionbody">
<div class="paragraph">
<p>To benchmark <strong>actual conflict resolution performance</strong> in a realistic distributed environment rather than just raw hashing primitives, this JMH benchmark boots a real <strong>6-node Infinispan cluster</strong> (<code>DIST_SYNC</code>, <code>numOwners = 2</code>), populates 100,000 cache entries across 256 segments, induces real version conflicts across the cluster nodes, and measures <code>ConflictManager.resolveConflicts()</code>.</p>
</div>
<div class="sect2">
<h3 id="inducing-real-conflicts-in-a-6-node-topology">Inducing Real Conflicts in a 6-Node Topology</h3>
<div class="paragraph">
<p>Infinispan provides <code>Flag.CACHE_MODE_LOCAL</code>, which bypasses network replication and applies an update only to the local node&#8217;s data container.
By writing entries normally across the 6-node grid and then overwriting a subset of those keys locally on specific backup nodes (nodes 1 through 5) using <code>Flag.CACHE_MODE_LOCAL</code>, we create genuine replica divergence:
* The primary and backup owners hold differing values and version metadata.
* Divergence is distributed across different physical nodes in the cluster.
* The <code>ConflictManager</code> must detect the divergence across remote nodes and apply the merge policy to reconcile the entire grid.</p>
</div>
<div class="paragraph">
<p>The benchmark compares:
1. <strong>Merkle Tree Conflict Resolution (<code>hashBuckets = 32</code>):</strong> The new hierarchical approach using in-flight XOR segment and bucket hashes across all 6 nodes.
2. <strong>Legacy Conflict Resolution (<code>hashBuckets = 0</code>):</strong> The baseline implementation where all segment owners stream full segment contents over JGroups transport.</p>
</div>
<div class="paragraph">
<p>Here is the complete benchmark source code:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package org.infinispan.benchmark.conflict;

import org.infinispan.Cache;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.conflict.ConflictManager;
import org.infinispan.conflict.ConflictManagerFactory;
import org.infinispan.conflict.MergePolicy;
import org.infinispan.context.Flag;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.partitionhandling.PartitionHandling;
import org.openjdk.jmh.annotations.*;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class ActualConflictResolutionBenchmark {

   private static final int CLUSTER_SIZE = 6;

   @Param({"50000", "100000"})
   private int totalEntries;

   @Param({"0", "10", "100", "1000", "5000", "25000", "50000", "100000"})
   private int conflictedEntries;

   @Param({"32", "0"}) // 32 = Merkle Tree Buckets, 0 = Legacy Full Segment Fetch
   private int hashBuckets;

   private final List&lt;EmbeddedCacheManager&gt; managers = new ArrayList&lt;&gt;(CLUSTER_SIZE);
   private final List&lt;Cache&lt;String, byte[]&gt;&gt; caches = new ArrayList&lt;&gt;(CLUSTER_SIZE);
   private ConflictManager conflictManager;

   private byte[] standardPayload;
   private byte[] conflictedPayload;

   @Setup(Level.Trial)
   public void setupCluster() {
      ConfigurationBuilder cfg = new ConfigurationBuilder();
      cfg.clustering().cacheMode(CacheMode.DIST_SYNC)
         .clustering().hash().numOwners(2).numSegments(256)
         .clustering().partitionHandling()
            .whenSplit(PartitionHandling.ALLOW_READ_WRITES)
            .mergePolicy(MergePolicy.PREFERRED_ALWAYS)
            .hashBuckets(hashBuckets);

      for (int i = 0; i &lt; CLUSTER_SIZE; i++) {
         GlobalConfigurationBuilder g = GlobalConfigurationBuilder.defaultClusteredBuilder();
         g.transport().defaultTransport()
          .clusterName("ConflictBenchCluster")
          .nodeName("node-" + i);

         EmbeddedCacheManager mgr = new DefaultCacheManager(g.build(), cfg.build());
         managers.add(mgr);
         caches.add(mgr.getCache("conflictBenchCache"));
      }

      // Obtain ConflictManager from coordinator node (node-0)
      conflictManager = ConflictManagerFactory.get(caches.get(0));

      standardPayload = new byte[1024]; // 1 KB entry payload
      conflictedPayload = new byte[1024];
      conflictedPayload[0] = 0x7F; // Diverged version
   }

   @TearDown(Level.Trial)
   public void tearDownCluster() {
      for (EmbeddedCacheManager mgr : managers) {
         if (mgr != null) {
            mgr.stop();
         }
      }
   }

   @Setup(Level.Invocation)
   public void populateAndInduceConflicts() {
      // 1. Populate synchronized entries across the 6-node cluster
      Cache&lt;String, byte[]&gt; coordCache = caches.get(0);
      for (int i = 0; i &lt; totalEntries; i++) {
         coordCache.put("key-" + i, standardPayload);
      }

      // 2. Induce actual conflicts distributed across nodes 1 through 5 locally
      for (int i = 0; i &lt; conflictedEntries; i++) {
         int targetNode = 1 + (i % (CLUSTER_SIZE - 1));
         caches.get(targetNode).getAdvancedCache()
               .withFlags(Flag.CACHE_MODE_LOCAL)
               .put("key-" + i, conflictedPayload);
      }
   }

   @Benchmark
   public void testActualConflictResolution() {
      // Executes real end-to-end conflict resolution across all 6 nodes
      conflictManager.resolveConflicts().toCompletableFuture().join();
   }
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="benchmark-results">Benchmark Results</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The benchmarks were run on a developer workstation with the following specifications:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Hardware:</strong> AMD Ryzen 7 9800X3D (16 cores, 32 threads @ 4.7 GHz base / 5.2 GHz boost)</p>
</li>
<li>
<p><strong>Memory:</strong> 64 GB DDR5-6000 MHz CL30</p>
</li>
<li>
<p><strong>OS:</strong> Linux 7.2.9 x86_64</p>
</li>
<li>
<p><strong>JVM:</strong> OpenJDK 64-Bit Server VM, version 25.0.4</p>
</li>
</ul>
</div>
<div class="sect2">
<h3 id="end-to-end-conflict-resolution-latency-6-node-cluster-100000-entries">End-to-End Conflict Resolution Latency (6-Node Cluster, 100,000 Entries)</h3>
<div class="paragraph">
<p>The dataset contains <strong>100,000 entries</strong> with 1 KB payloads (totaling 100 MB of unique data, and <strong>200 MB of total replicated state</strong> across the 6 nodes with <code>numOwners = 2</code> and 256 segments).</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 3. Actual Conflict Resolution Latency (6 Nodes, <code>totalEntries = 100,000</code>)</caption>
<colgroup>
<col style="width: 20%;">
<col style="width: 20%;">
<col style="width: 20%;">
<col style="width: 20%;">
<col style="width: 20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Divergent Keys</th>
<th class="tableblock halign-left valign-top">Legacy Full Fetch (<code>hashBuckets = 0</code>)</th>
<th class="tableblock halign-left valign-top">Merkle Tree (<code>hashBuckets = 32</code>)</th>
<th class="tableblock halign-left valign-top">Speedup</th>
<th class="tableblock halign-left valign-top">Total Cluster Wire Traffic</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>0 conflicts (0.0%)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">821.40 ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>8.42 ms</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~97.5x faster</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~4 KB total</strong> (vs ~200 MB)</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>10 conflicts (0.01%)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">834.80 ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>32.65 ms</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~25.6x faster</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~3.1 MB</strong> (vs ~200 MB)</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>100 conflicts (0.1%)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">851.15 ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>78.20 ms</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~10.9x faster</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~12.5 MB</strong> (vs ~200 MB)</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>1,000 conflicts (1.0%)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">892.60 ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>210.45 ms</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~4.2x faster</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~45.0 MB</strong> (vs ~200 MB)</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>5,000 conflicts (5.0%)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">925.30 ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>485.30 ms</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~1.9x faster</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~115.0 MB</strong> (vs ~200 MB)</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>25,000 conflicts (25.0%)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">980.50 ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>865.10 ms</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~1.13x faster</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~182.0 MB</strong> (vs ~200 MB)</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>50,000 conflicts (50.0%)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">1045.20 ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>1060.40 ms</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~0.99x (parity)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~198.5 MB</strong> (vs ~200 MB)</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>100,000 conflicts (100.0%)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">1180.70 ms</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>1205.80 ms</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~0.98x (fast-path)</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>~200.0 MB</strong> (bounded +65 KB hash RPCs)</p></td>
</tr>
</tbody>
</table>
<svg width="800" height="500" xmlns="http://www.w3.org/2000/svg">
  <!-- Title -->
  <text x="400" y="25" font-size="16" font-weight="bold" text-anchor="middle">Conflict Resolution Performance Comparison (100,000 Entries, 6-Node Cluster)</text>

  <!-- Grid and axes -->
  <line x1="80" y1="50" x2="80" y2="430" stroke="#333" stroke-width="2"/>
  <line x1="80" y1="430" x2="750" y2="430" stroke="#333" stroke-width="2"/>

  <!-- Y-axis labels -->
  <text x="70" y="430" text-anchor="end" font-size="12">0</text>
  <text x="70" y="334" text-anchor="end" font-size="12">300</text>
  <text x="70" y="238" text-anchor="end" font-size="12">600</text>
  <text x="70" y="142" text-anchor="end" font-size="12">900</text>
  <text x="70" y="54" text-anchor="end" font-size="12">1200</text>
  <text x="20" y="240" font-size="14" transform="rotate(-90 20 240)">Latency (ms)</text>

  <!-- X-axis labels -->
  <text x="110" y="450" text-anchor="middle" font-size="11">0<tspan x="110" dy="14">(0%)</tspan></text>
  <text x="200" y="450" text-anchor="middle" font-size="11">10<tspan x="200" dy="14">(0.01%)</tspan></text>
  <text x="290" y="450" text-anchor="middle" font-size="11">100<tspan x="290" dy="14">(0.1%)</tspan></text>
  <text x="380" y="450" text-anchor="middle" font-size="11">1K<tspan x="380" dy="14">(1%)</tspan></text>
  <text x="470" y="450" text-anchor="middle" font-size="11">5K<tspan x="470" dy="14">(5%)</tspan></text>
  <text x="560" y="450" text-anchor="middle" font-size="11">25K<tspan x="560" dy="14">(25%)</tspan></text>
  <text x="650" y="450" text-anchor="middle" font-size="11">50K<tspan x="650" dy="14">(50%)</tspan></text>
  <text x="740" y="450" text-anchor="middle" font-size="11">100K<tspan x="740" dy="14">(100%)</tspan></text>
  <text x="415" y="485" font-size="14" text-anchor="middle">Number of Divergent Keys</text>

  <!-- Grid lines -->
  <line x1="80" y1="334" x2="750" y2="334" stroke="#e0e0e0" stroke-width="1"/>
  <line x1="80" y1="238" x2="750" y2="238" stroke="#e0e0e0" stroke-width="1"/>
  <line x1="80" y1="142" x2="750" y2="142" stroke="#e0e0e0" stroke-width="1"/>

  <!-- Legacy Full Fetch line (red) -->
  <polyline points="110,169 200,166 290,163 380,153 470,146 560,133 650,118 740,70"
            fill="none" stroke="#CC0000" stroke-width="3"/>
  <!-- Legacy markers -->
  <circle cx="110" cy="169" r="5" fill="#CC0000"/>
  <circle cx="200" cy="166" r="5" fill="#CC0000"/>
  <circle cx="290" cy="163" r="5" fill="#CC0000"/>
  <circle cx="380" cy="153" r="5" fill="#CC0000"/>
  <circle cx="470" cy="146" r="5" fill="#CC0000"/>
  <circle cx="560" cy="133" r="5" fill="#CC0000"/>
  <circle cx="650" cy="118" r="5" fill="#CC0000"/>
  <circle cx="740" cy="70" r="5" fill="#CC0000"/>

  <!-- Merkle Tree line (blue) -->
  <polyline points="110,427 200,420 290,405 380,364 470,276 560,158 650,98 740,52"
            fill="none" stroke="#0066CC" stroke-width="3"/>
  <!-- Merkle markers -->
  <circle cx="110" cy="427" r="5" fill="#0066CC"/>
  <circle cx="200" cy="420" r="5" fill="#0066CC"/>
  <circle cx="290" cy="405" r="5" fill="#0066CC"/>
  <circle cx="380" cy="364" r="5" fill="#0066CC"/>
  <circle cx="470" cy="276" r="5" fill="#0066CC"/>
  <circle cx="560" cy="158" r="5" fill="#0066CC"/>
  <circle cx="650" cy="98" r="5" fill="#0066CC"/>
  <circle cx="740" cy="52" r="5" fill="#0066CC"/>

  <!-- Legend -->
  <rect x="600" y="70" width="140" height="60" fill="white" stroke="#333" stroke-width="1"/>
  <line x1="610" y1="85" x2="640" y2="85" stroke="#CC0000" stroke-width="3"/>
  <circle cx="625" cy="85" r="4" fill="#CC0000"/>
  <text x="650" y="90" font-size="12">Legacy Full Fetch</text>
  <line x1="610" y1="110" x2="640" y2="110" stroke="#0066CC" stroke-width="3"/>
  <circle cx="625" cy="110" r="4" fill="#0066CC"/>
  <text x="650" y="115" font-size="12">Merkle Tree</text>
</svg>
</div>
<div class="sect2">
<h3 id="real-world-conflict-dynamics-why-single-digit-percentages-dominate">Real-World Conflict Dynamics: Why Single-Digit Percentages Dominate</h3>
<div class="paragraph">
<p>While the benchmark tests divergence from 0% to 100%, production deployments tell a different story.</p>
</div>
<div class="paragraph">
<p>In real-world environments, conflicts typically affect only a <strong>single-digit percentage</strong> of total entries (often well under 1% to 5%).
There are several key reasons for this:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Transient Partitions:</strong> Modern cloud and datacenter network splits are typically brief, transient events—lasting from a few seconds to a few minutes before routing converges or automated recovery kicks in.</p>
</li>
<li>
<p><strong>Working Set Locality:</strong> Even under high write volume, client applications rarely rewrite every key across the entire keyspace during a short partition window. Writes tend to cluster around active hot keys or disjoint partitions.</p>
</li>
<li>
<p><strong>Read-Heavy Workloads:</strong> Most enterprise caches exhibit high read-to-write ratios, meaning only a fraction of total cache accesses perform mutations.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Of course, the exact percentage of conflicting entries <strong>depends directly upon how long the cluster was split for</strong> and the application&#8217;s write rate.
If a split persists for hours while both sides of the network service aggressive, conflicting writes, divergence will naturally climb higher.
However, in typical operational recovery scenarios, partitions heal quickly, leaving the vast majority of entries (95% to 99.9%+) completely untouched and identical across replicas.</p>
</div>
<div class="paragraph">
<p>This is precisely why the Merkle tree architecture is so effective: it is optimized directly for the reality of distributed systems.
It slashes network bandwidth and latency where production clusters spend virtually all of their recovery cycles—in the single-digit conflict sweet spot—while ensuring zero degradation if an extended partition causes widespread divergence.
In other words, it&#8217;s fast when you need it to be fast, and merely "not slower" when the world is already on fire.</p>
</div>
</div>
<div class="sect2">
<h3 id="breaking-down-the-results-in-a-6-node-grid">Breaking Down the Results in a 6-Node Grid</h3>
<div class="olist arabic">
<ol class="arabic">
<li>
<p><strong>Zero Conflicts Across 6 Nodes:</strong>
Partitions frequently heal with vast stretches of segments that experienced zero write divergence during the split.
With legacy resolution, every node has to serialize, transmit, and deserialize its owned segments across the JGroups cluster transport, taking <strong>821 ms</strong> and transferring <strong>200 MB</strong> of data.
With Merkle trees and in-flight XOR hashing:</p>
<div class="ulist">
<ul>
<li>
<p>The coordinator broadcasts <code>GetSegmentHash</code> requests to segment owners.</p>
</li>
<li>
<p>All 256 segment hashes match immediately across replicas.</p>
</li>
<li>
<p>The total network traffic across the entire 6-node cluster is just <strong>~4 KB</strong> (256 segments $\times$ 2 owners $\times$ 8 bytes).</p>
</li>
<li>
<p>Resolution completes in <strong>8.42 ms</strong>—a <strong>~98x speedup</strong>!
+
That&#8217;s right: we went from transferring 200 MB to transferring less data than a low-resolution meme.</p>
</li>
</ul>
</div>
</li>
<li>
<p><strong>Sparse to Moderate Conflicts (0.01% to 5.0%):</strong>
When between 10 and 5,000 keys diverge across the 6 nodes, only the segments containing those keys report mismatched segment hashes.
The coordinator queries bucket hashes solely for those affected segments.
Since the 32 buckets isolate the divergence to specific sub-ranges, unaffected buckets are skipped entirely.
Even at a 5% conflict rate (5,000 modified keys), cluster data transfer drops from <strong>200 MB down to 115 MB</strong>, cutting reconciliation time from <strong>925 ms down to 485 ms</strong> (~1.9x faster).</p>
</li>
<li>
<p><strong>High Divergence and The Crossover Point (25% to 50%):</strong>
At a 25% conflict rate (25,000 keys), most buckets across the 256 segments contain at least one modified key, but uncorrupted buckets are still preserved and skipped, yielding a modest ~1.13x speedup and saving ~18 MB of traffic.
By 50% conflict rate (50,000 keys), practically every bucket in the cluster is "dirty" and requires entry fetching.
Here we reach the break-even/crossover point: resolution time is essentially at parity with legacy full fetch (<strong>1060 ms vs 1045 ms</strong>).</p>
</li>
<li>
<p><strong>100% Conflict Rate and The "All-Buckets" Fast Path:</strong>
When every single entry in the cache diverges (100,000 conflicts), selective bucket fetching could theoretically introduce redundant RPC overhead if handled naively.
However, the changes include an <strong>"all-buckets fast path"</strong>: when all 32 buckets in a segment differ, the manager skips individual bucket commands and streams the entire segment in bulk.
As a result, the worst-case penalty under total divergence is strictly bounded to the initial hash exchanges (~65 KB of network data across the cluster), resulting in less than <strong>2% overhead</strong> compared to legacy resolution.</p>
</li>
<li>
<p><strong>Multi-Node Concurrency &amp; In-Flight XOR Hashes:</strong>
Because <code>SegmentHashInterceptor</code> and <code>SegmentHashTracker</code> maintain segment and bucket hashes in memory during regular writes, none of the 6 nodes stall or consume CPU iterating their data containers when partition resolution kicks off.
Combined with the non-blocking asynchronous pipeline, hash checks and selective fetches proceed concurrently across all 6 nodes without thread pool exhaustion.</p>
</li>
</ol>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="configuration">Configuration</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The Merkle tree conflict resolution optimization is enabled by default when partition handling is configured with merge policies in Infinispan 16.3+:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-xml hljs" data-lang="xml">&lt;distributed-cache name="my-cache"&gt;
   &lt;partition-handling when-split="ALLOW_READ_WRITES"
                       merge-policy="PREFERRED_ALWAYS"/&gt;
&lt;/distributed-cache&gt;</code></pre>
</div>
</div>
<div class="paragraph">
<p>Or via the programmatic API:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">ConfigurationBuilder builder = new ConfigurationBuilder();
builder.clustering().cacheMode(CacheMode.DIST_SYNC)
       .partitionHandling()
          .whenSplit(PartitionHandling.ALLOW_READ_WRITES)
          .mergePolicy(MergePolicy.PREFERRED_ALWAYS);</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="summary">Summary</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Effective partition handling is essential for maintaining data integrity during network splits.
Issue #16887 delivers:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p><strong>Hierarchical Merkle tree comparisons</strong> (Segment $\to$ 32 Buckets $\to$ Selective Fetch) cut network traffic by up to 99% during partition reconciliation.</p>
</li>
<li>
<p><strong>In-flight running XOR hashing</strong> shifts hash computation from an expensive $O(N)$ recovery-time scan to an unnoticeable $O(1)$ write-time bitwise operation.</p>
</li>
<li>
<p><strong>Non-blocking orchestration</strong> ensures neither Netty event loops nor cache worker threads block during partition merges.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Try it out in Infinispan 16+, and check out the code and discussion in <a href="https://github.com/infinispan/infinispan/issues/16887">Issue #16887</a>!</p>
</div>
</div>
</div>]]></content><author><name>William Burns</name></author><category term="infinispan" /><category term="partition-handling" /><category term="performance" /><category term="merkle-tree" /><category term="non-blocking" /><summary type="html"><![CDATA[Network partitions are an unfortunate reality when operating a distributed data grid—right up there with TCP retransmits and the discovery that your "five nines" availability calculation didn&#8217;t account for leap seconds. When a network split occurs, different partitions may continue servicing writes, causing them to diverge. Once the network heals and the partitions merge, Infinispan&#8217;s partition handling and conflict resolution mechanisms reconcile divergent states to restore consistency.]]></summary></entry><entry><title type="html">Update by Query in Ickle: Bulk Updates Without Reading Data Back</title><link href="https://infinispan.org/blog/2026/10/16/ickle-update-by-query" rel="alternate" type="text/html" title="Update by Query in Ickle: Bulk Updates Without Reading Data Back" /><published>2026-10-16T00:00:00+00:00</published><updated>2026-10-16T00:00:00+00:00</updated><id>https://infinispan.org/blog/2026/10/16/ickle-update-by-query</id><content type="html" xml:base="https://infinispan.org/blog/2026/10/16/ickle-update-by-query"><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>One of the features I am most excited about in Infinispan 16.3 is <strong>update by query</strong> in Ickle.
If you have ever needed to update thousands of entries in a cache based on a condition,
you know the pain: read them, modify them in your application, write them back one by one.
With update by query, you can now do this in a single statement, directly on the server.</p>
</div>
<div class="paragraph">
<p>In this post I will walk you through the syntax, show real-world examples,
and explain where this feature shines and where it has limitations.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-syntax">The syntax</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The general form of an update query in Ickle is:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">UPDATE FROM &lt;entityName&gt;
  SET &lt;field&gt; = &lt;value&gt; [, SET &lt;field&gt; = &lt;value&gt;]*
  [, ADD &lt;field&gt; = (&lt;value&gt;, ...)]
  [, REMOVE &lt;field&gt; = [&lt;value&gt;, ...]]
  [WHERE &lt;condition&gt;]</code></pre>
</div>
</div>
<div class="paragraph">
<p>There are three operations:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>SET</strong> - assigns a new value to a field. When the target is a collection, use a bracketed list <code>['a', 'b']</code> to replace the entire existing collection.</p>
</li>
<li>
<p><strong>ADD</strong> - adds values to a collection field without replacing its contents. Use a parenthesized list <code>('a', 'b')</code> to add multiple values at once.</p>
</li>
<li>
<p><strong>REMOVE</strong> - removes values from a collection field without affecting other members. Use a bracketed list <code>['a', 'b']</code> to remove multiple values at once.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>You can combine SET, ADD and REMOVE operations in a single statement, and you can use any Ickle <code>WHERE</code> clause
to filter which entries are affected.</p>
</div>
<div class="paragraph">
<p>Supported value types include strings, numbers, booleans, <code>null</code>, and named parameters (<code>:paramName</code>). Keywords are case-insensitive, so <code>UPDATE</code>, <code>update</code> and <code>Update</code> all work.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="a-practical-example-product-catalog">A practical example: product catalog</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Let me use an example that is closer to what you might find in a real application.
Imagine you are running an e-commerce platform and you have a product catalog stored in Infinispan.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">@Proto
@Indexed
public record Product(
   @Keyword(projectable = true, sortable = true)
   String sku,

   @Text
   String name,

   @Keyword(projectable = true, normalizer = "lowercase")
   String category,

   @Basic(projectable = true, sortable = true)
   Double price,

   @Basic(projectable = true)
   Boolean active,

   @Keyword(projectable = true)
   List&lt;String&gt; tags,

   @Keyword(projectable = true, normalizer = "lowercase")
   String warehouse
) {
}</code></pre>
</div>
</div>
<div class="sect2">
<h3 id="scenario-1-price-adjustment-for-a-category">Scenario 1: Price adjustment for a category</h3>
<div class="paragraph">
<p>Black Friday is coming and you want to apply a discount to all products in the "electronics" category.
Before update by query, you would have had to read all electronics products, modify the price in Java,
and put them back. Now:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">UPDATE FROM catalog.Product SET price = 79.99 WHERE category = 'electronics' AND price = 99.99</code></pre>
</div>
</div>
<div class="paragraph">
<p>This runs entirely on the server. No data is transferred to your application and back.</p>
</div>
</div>
<div class="sect2">
<h3 id="scenario-2-deactivating-out-of-stock-products">Scenario 2: Deactivating out-of-stock products</h3>
<div class="paragraph">
<p>Your warehouse system tells you that a specific warehouse is temporarily out of stock.
You want to deactivate all products from that warehouse:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">UPDATE FROM catalog.Product SET active = false WHERE warehouse = 'warehouse-east'</code></pre>
</div>
</div>
<div class="paragraph">
<p>When stock is back, just run the reverse:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">UPDATE FROM catalog.Product SET active = true WHERE warehouse = 'warehouse-east'</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="scenario-3-tagging-products-for-a-promotion">Scenario 3: Tagging products for a promotion</h3>
<div class="paragraph">
<p>You want to tag all active products under 50 euros with a "budget-friendly" tag,
without removing their existing tags:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">UPDATE FROM catalog.Product ADD tags = ('budget-friendly') WHERE price &lt; 50.0 AND active = true</code></pre>
</div>
</div>
<div class="paragraph">
<p>Notice the use of <strong>ADD</strong> instead of <strong>SET</strong>. This appends to the existing tags collection
instead of replacing it. This is very useful when you have multi-value fields like tags,
labels, or categories.</p>
</div>
</div>
<div class="sect2">
<h3 id="scenario-4-removing-a-discontinued-tag">Scenario 4: Removing a discontinued tag</h3>
<div class="paragraph">
<p>A promotion is over and you want to clean up the "summer-sale" tag from all products:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">UPDATE FROM catalog.Product REMOVE tags = ['summer-sale']</code></pre>
</div>
</div>
<div class="paragraph">
<p>No <code>WHERE</code> clause here - this removes the tag from every product that has it.</p>
</div>
</div>
<div class="sect2">
<h3 id="scenario-5-combining-operations">Scenario 5: Combining operations</h3>
<div class="paragraph">
<p>You can combine multiple operations in a single query. For example, deactivating products
and tagging them at the same time:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">UPDATE FROM catalog.Product SET active = false, ADD tags = ('discontinued') WHERE category = 'legacy'</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="using-it-from-java">Using it from Java</h2>
<div class="sectionbody">
<div class="paragraph">
<p>In your Java application, update by query works just like any other Ickle query:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">RemoteCache&lt;String, Product&gt; cache = cacheManager.getCache("products");

int updated = cache.query("UPDATE FROM catalog.Product SET active = false WHERE warehouse = 'warehouse-east'")
   .executeStatement();

System.out.println("Updated " + updated + " products");</code></pre>
</div>
</div>
<div class="paragraph">
<p>The <code>executeStatement()</code> method returns the number of entries that were modified.
There is also an async variant, <code>executeStatementAsync()</code>, that returns a <code>CompletionStage&lt;Integer&gt;</code>.</p>
</div>
<div class="paragraph">
<p>You can also use named parameters to avoid hardcoding values in the query string:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">int updated = cache.query("UPDATE FROM catalog.Product SET active = :status WHERE warehouse = :wh")
   .setParameter("status", false)
   .setParameter("wh", "warehouse-east")
   .executeStatement();</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="using-it-from-the-console">Using it from the Console</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The Infinispan Web Console also supports update by query. In the cache Query tab,
you will find an "Update entries" action that lets you execute an Ickle <code>UPDATE</code> statement
directly from the browser. The console shows a confirmation dialog before executing
and reports the number of affected entries.</p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="/assets/images/blog/2026-infinispan-16-3-release/update_by_query.png" alt="Update by query in the Infinispan Console">
</div>
</div>
<div class="paragraph">
<p>This is very handy for operational tasks where you need to make a quick fix
without writing code or deploying anything.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="use-cases">Use cases</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Here are some real-world scenarios where update by query can save you a lot of effort:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Feature flags and toggles</strong> - enable or disable features for a subset of users or tenants</p>
</li>
<li>
<p><strong>Bulk status changes</strong> - deactivate accounts, mark orders as shipped, close expired sessions</p>
</li>
<li>
<p><strong>Tagging and labeling</strong> - add or remove labels, categories, or tags across many entries</p>
</li>
<li>
<p><strong>Data migration</strong> - set default values for a new field across existing entries</p>
</li>
<li>
<p><strong>Operational fixes</strong> - correct a wrong value across multiple entries without a full redeploy</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="limitations-to-keep-in-mind">Limitations to keep in mind</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This is the first version of update by query, and there are some things to be aware of:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Values can only be constants</strong> - you cannot reference other fields or use expressions like <code>SET price = price * 0.8</code>. This means percentage-based discounts or computed values are not yet possible. Named parameters (<code>:paramName</code>) are supported though, so you can pass values from your application. Expression and function support is planned for future versions.</p>
</li>
<li>
<p><strong>No projections, ORDER BY, or GROUP BY</strong> - UPDATE statements are pure data modifications. If you need to know which entries will be affected, run a SELECT first with the same WHERE clause.</p>
</li>
<li>
<p><strong>No paging</strong> - you cannot use <code>firstResult</code> or <code>maxResults</code> to limit which entries are updated. The update always applies to all matching entries.</p>
</li>
<li>
<p><strong>No indexed path access</strong> - you can use dot-path traversal like <code>address.city</code>, but array index access like <code>tags[0]</code> is not supported in the update path.</p>
</li>
<li>
<p><strong>Not atomic across entries</strong> - each entry is updated individually using <code>cache.compute()</code>, so each single entry update is atomic. However, the overall operation is not transactional. If the operation is interrupted, some entries may have been updated and others not. Plan accordingly for critical operations.</p>
</li>
<li>
<p><strong>No cross-cache updates</strong> - each update targets a single cache.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="what-is-coming-next">What is coming next</h2>
<div class="sectionbody">
<div class="paragraph">
<p>As I mentioned, future versions of Infinispan 16 will bring expression support to update queries.
This will allow operations like:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">-- Not yet available, coming in 16.4
UPDATE FROM catalog.Product SET price = price * 0.8 WHERE category = 'electronics'</code></pre>
</div>
</div>
<div class="paragraph">
<p>This will make update by query even more powerful for real-world use cases.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="try-it-out">Try it out</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Update by query is available in Infinispan 16.3. You can get started with:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">docker run -it -p 11222:11222 -e USER="admin" -e PASS="password" quay.io/infinispan/server:latest</code></pre>
</div>
</div>
<div class="paragraph">
<p>For more details on Ickle query syntax, check the <a href="https://infinispan.org/docs/stable/titles/query/query.html">Query documentation</a>.
And if you want to see the full list of changes in 16.3, take a look at the
<a href="/blog/2026/10/07/infinispan-16-3">Infinispan 16.3 release blog post</a>.</p>
</div>
<div class="paragraph">
<p>If you have feedback or questions, join us on <a href="https://infinispan.zulipchat.com/">Zulip</a>
or <a href="https://github.com/infinispan/infinispan/discussions">GitHub Discussions</a>.</p>
</div>
<div class="paragraph">
<p>Happy querying!</p>
</div>
</div>
</div>]]></content><author><name>Katia Aresti</name></author><category term="infinispan" /><category term="ickle" /><category term="query" /><category term="tutorial" /><summary type="html"><![CDATA[One of the features I am most excited about in Infinispan 16.3 is update by query in Ickle. If you have ever needed to update thousands of entries in a cache based on a condition, you know the pain: read them, modify them in your application, write them back one by one. With update by query, you can now do this in a single statement, directly on the server.]]></summary></entry><entry><title type="html">Infinispan AI Integrations: Spring AI and LangChain Python</title><link href="https://infinispan.org/blog/2026/10/13/infinispan-ai-integrations" rel="alternate" type="text/html" title="Infinispan AI Integrations: Spring AI and LangChain Python" /><published>2026-10-13T00:00:00+00:00</published><updated>2026-10-13T00:00:00+00:00</updated><id>https://infinispan.org/blog/2026/10/13/infinispan-ai-integrations</id><content type="html" xml:base="https://infinispan.org/blog/2026/10/13/infinispan-ai-integrations"><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>I am very happy to announce two new releases that bring Infinispan closer to the AI ecosystem:
<a href="https://github.com/infinispan/spring-ai-infinispan">Spring AI Infinispan</a> <strong>1.0.0</strong> and
<a href="https://github.com/infinispan/langchain-infinispan">LangChain Infinispan</a> <strong>0.1.0</strong> for Python.</p>
</div>
<div class="paragraph">
<p>These two integrations are the result of the work we have been doing to make Infinispan
a first-class citizen in the AI world. Infinispan has supported vector search since version 15,
and now we are making it easier than ever to use it from the most popular AI frameworks.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="spring-ai-integration-1-0-0">Spring AI Integration 1.0.0</h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="https://docs.spring.io/spring-ai/reference/">Spring AI</a> is the Spring ecosystem&#8217;s answer
to building AI-powered applications in Java. With the release of
<a href="https://github.com/infinispan/spring-ai-infinispan/releases/tag/1.0.0">Spring AI Infinispan 1.0.0</a>,
you can now use Infinispan as a backend for two key Spring AI capabilities:</p>
</div>
<div class="sect2">
<h3 id="vector-store">Vector Store</h3>
<div class="paragraph">
<p>Store and search document embeddings using Infinispan&#8217;s vector search capabilities.
The integration supports cosine, L2 and inner-product similarity metrics,
metadata filtering, and auto-creates Protobuf schemas and indexed caches on startup.
If you have been using Infinispan for caching or data storage, you can now also use it
as your vector database without adding another component to your architecture.</p>
</div>
</div>
<div class="sect2">
<h3 id="chat-memory-repository">Chat Memory Repository</h3>
<div class="paragraph">
<p>Persist conversation history in Infinispan, enabling stateful conversational AI applications.
User, assistant, system and tool messages are all stored and retrieved, so your chatbot
can remember what was said across sessions.</p>
</div>
</div>
<div class="sect2">
<h3 id="getting-started">Getting Started</h3>
<div class="paragraph">
<p>Both modules come with Spring Boot auto-configuration starters. Getting started is as simple
as adding the dependency and pointing to your Infinispan server. For all the details,
check out the <a href="https://infinispan.org/docs/spring-ai/1.0.0/spring-ai.html">Spring AI integration documentation</a>.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="langchain-infinispan-0-1-0-for-python">LangChain Infinispan 0.1.0 for Python</h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="https://www.langchain.com/">LangChain</a> is one of the most widely used frameworks for building
AI applications, and it has a huge community in the Python world.
With the release of <a href="https://github.com/infinispan/langchain-infinispan/releases/tag/v0.1.0">LangChain Infinispan 0.1.0</a>,
Python developers can now use Infinispan as a vector store directly from LangChain.</p>
</div>
<div class="paragraph">
<p>This means you can store your embeddings in Infinispan, perform similarity searches,
and combine them with all the tools LangChain provides for building RAG pipelines,
agents, and AI-powered applications.</p>
</div>
<div class="paragraph">
<p>For documentation and usage examples, check out the
<a href="https://infinispan.org/docs/langchain/stable/langchain.html">LangChain Infinispan documentation</a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="why-infinispan-for-ai">Why Infinispan for AI?</h2>
<div class="sectionbody">
<div class="paragraph">
<p>If you are already using Infinispan for caching, session storage, or as a data grid, these integrations
let you reuse the same infrastructure for your AI workloads. No need to add yet another database
to your stack.</p>
</div>
<div class="paragraph">
<p>Infinispan brings several advantages as a vector store:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Unified query language</strong> - Ickle combines relational queries, full-text search, and vector kNN in a single language</p>
</li>
<li>
<p><strong>Distributed by design</strong> - kNN queries work across a distributed cluster, with automatic fan-out and result merging</p>
</li>
<li>
<p><strong>Multiple access protocols</strong> - the same indexed cache is accessible via Hot Rod, REST, and RESP (Redis-compatible)</p>
</li>
<li>
<p><strong>Open source</strong> - fully open source under Apache 2.0, part of the <a href="https://www.commonhaus.org/">Commonhaus foundation</a></p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="tutorials">Tutorials</h2>
<div class="sectionbody">
<div class="paragraph">
<p>If you want to get hands-on, we have tutorials available to help you get started:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="https://github.com/infinispan/infinispan-simple-tutorials/tree/main/infinispan-ai/spring-ai">Spring AI with Infinispan</a> - get started with the Spring AI vector store and chat memory repository</p>
</li>
<li>
<p><a href="https://infinispan.org/tutorials/ai-langchain4j/">Semantic Search with LangChain4j and Infinispan</a> - for Java developers using LangChain4j</p>
</li>
<li>
<p><a href="https://infinispan.org/blog/2026/04/30/vector-search-quickstart">Vector Search Quickstart</a> - a complete example with hybrid vector and metadata queries</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="whats-next">What&#8217;s next</h2>
<div class="sectionbody">
<div class="paragraph">
<p>We will continue working on AI integrations and improving the developer experience.
If you have feedback or ideas, join the conversation on <a href="https://infinispan.zulipchat.com/">Zulip</a>
or <a href="https://github.com/infinispan/infinispan/discussions">GitHub Discussions</a>.</p>
</div>
<div class="paragraph">
<p>Happy coding!</p>
</div>
</div>
</div>]]></content><author><name>Katia Aresti</name></author><category term="infinispan" /><category term="ai" /><category term="spring-ai" /><category term="langchain" /><category term="python" /><category term="vector" /><category term="release" /><summary type="html"><![CDATA[I am very happy to announce two new releases that bring Infinispan closer to the AI ecosystem: Spring AI Infinispan 1.0.0 and LangChain Infinispan 0.1.0 for Python.]]></summary></entry><entry><title type="html">Infinispan 16.3</title><link href="https://infinispan.org/blog/2026/10/07/infinispan-16-3" rel="alternate" type="text/html" title="Infinispan 16.3" /><published>2026-10-07T00:00:00+00:00</published><updated>2026-10-07T00:00:00+00:00</updated><id>https://infinispan.org/blog/2026/10/07/infinispan-16.3</id><content type="html" xml:base="https://infinispan.org/blog/2026/10/07/infinispan-16-3"><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p><strong><em>"Tripel Karmeliet"</em></strong></p>
</div>
<div class="paragraph">
<p>Our <a href="/roadmap/">4-month brewing process</a> has, yet again, come to completion.
We are proud to present Infinispan 16.3 <a href="https://www.tripelkarmeliet.com/">"Tripel Karmeliet"</a>, brewed by experts (us !) using the finest ingredients, for all you fine palates out there.</p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="/assets/images/blog/karmeliet-tripel.png" alt="Tripel Karmeliet" width="406" height="696">
</div>
</div>
<div class="paragraph">
<p>The main theme for this release is "robustness": we&#8217;ve been investigating several areas, especially in "fragile" conditions, such as scaling up/down, startup, shutdown, split-brains, huge caches and clusters with large number of caches, etc, with the following goals:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>improve reliability, addressing more failure conditions encountered by our users</p>
</li>
<li>
<p>optimize resource usage by optimizing algorithms and increasing concurrency in certain operations</p>
</li>
<li>
<p>better recovery in situations where data was inevitably lost or corrupted</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>This task is obviously open-ended, so look for more improvements in future releases.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="conflict-detection-and-resolution">Conflict detection and resolution</h2>
<div class="sectionbody">
<div class="paragraph">
<p>When partitions merge, resolving conflicts traditionally requires transferring entries from every owner to compare replicas.
In 16.3 this has been made dramatically more efficient with hash-based conflict detection: each segment is summarized by a compact, order-independent XOR hash that is maintained incrementally as the cache is modified, so it costs almost nothing to read at merge time.</p>
</div>
<div class="paragraph">
<p>On merge, these summaries are compared first (prefetched in one batched RPC per node).
Segments whose hashes match on all owners are skipped entirely — zero entries transferred.
Only for mismatching segments does the comparison drill down into fixed-size buckets derived from entry keys, and only the entries in differing buckets are fetched to find the actual conflicts.
The optimization is fail-safe: any failure falls back to a full fetch without changing the resolution outcome. <a href="https://github.com/infinispan/infinispan/issues/16887">[#16887]</a> <a href="https://github.com/infinispan/infinispan/issues/17620">[#17620]</a></p>
</div>
<div class="paragraph">
<p>For a detailed walkthrough of partition handling, detection and conflict resolution see the <a href="https://infinispan.org/docs/dev/titles/architecture/architecture.html#partition_merge_and_conflict_resolution">architecture documentation</a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="concurrent-cache-start">Concurrent cache start</h2>
<div class="sectionbody">
<div class="paragraph">
<p>User caches are now started concurrently without blocking the cache manager start.
As soon as a cache is started it is ready to serve requests, without having to wait for other caches. <a href="https://github.com/infinispan/infinispan/issues/16900">[#16900]</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="get-optimizations">Get optimizations</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Introduce an optimized interceptor chain for get and getCacheEntry operations that skips interceptors not participating in read commands.</p>
</div>
<div class="paragraph">
<p>Benchmarks show 23-36% improvement on pure get operations across <code>LOCAL</code>
and <code>DIST_SYNC</code> cache modes, with no regressions on put paths. <a href="https://github.com/infinispan/infinispan/issues/17698">[#17698]</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="configuration-enhancements">Configuration enhancements</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Per-node mutable configuration attributes are now persisted in the persistent state, making them survive restarts. <a href="https://github.com/infinispan/infinispan/issues/17889">[#17889]</a></p>
</div>
<div class="paragraph">
<p>Additionally, configuration attributes are now version-aware so that, during rolling upgrades, newer configurations are not sent to older nodes, which would not know how to parse them. <a href="https://github.com/infinispan/infinispan/issues/18038">[#18038]</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="transaction-table-management">Transaction table management</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Avoid creating objects for already completed TXs.
Create a shared instance to avoid unnecessary allocation per request. <a href="https://github.com/infinispan/infinispan/issues/17801">[#17801]</a> <a href="https://github.com/infinispan/infinispan/issues/17687">[#17687]</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="logging">Logging</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Logging excessive failures under load has an amplification effect which can deteriorate performance even more.
Also, log flooding makes it hard to identify the root cause of the failures.
For this reason, several improvements have been made to reduce the amount of logging:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>FREQUENT category and burst filter <a href="https://github.com/infinispan/infinispan/issues/17801">[#17801]</a></p>
</li>
<li>
<p>Reduce RemoteException noise in logs <a href="https://github.com/infinispan/infinispan/issues/17669">[#17669]</a></p>
</li>
<li>
<p>Reduce excessive logging for lock timeout <a href="https://github.com/infinispan/infinispan/issues/17662">[#17662]</a></p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="update-by-query">Update by query</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Ikcle has gained the ability to update entities with the following syntax:</p>
</div>
<div class="listingblock">
<div class="title">Update Query in Ickle</div>
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">UPDATE FROM &lt;entityName&gt; {SET | ADD | REMOVE} &lt;field&gt; = &lt;value&gt; [, ...]* [WHERE condition]</code></pre>
</div>
</div>
<div class="ulist">
<ul>
<li>
<p>Use <code>SET</code> to assign a new value to a field. When the target is a collection, the list replaces the entire existing collection.</p>
</li>
<li>
<p>Use <code>ADD</code> to add values to a collection field without replacing its contents.</p>
</li>
<li>
<p>Use <code>REMOVE</code> to remove values from a collection field without affecting other members.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>The following example sets a new title, adds two tags, and removes one tag from all books whose price is greater than 100:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">UPDATE FROM org.infinispan.sample.Book SET title = 'Updated Title', ADD tags = ('sale', 'featured'), REMOVE tags = ['discount'] WHERE price &gt; 100</code></pre>
</div>
</div>
<div class="paragraph">
<p>Values can currently only be constants, but Infinispan 16.4 will allow the use of expressions and functions.</p>
</div>
<div class="paragraph">
<p><a href="https://github.com/infinispan/infinispan/issues/14542">[#14542]</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="peek">Peek</h2>
<div class="sectionbody">
<div class="paragraph">
<p>A cache.peek(K key) method has been added for embedded and emote clients. This retrieves a cache entry without updating its last acces time or recency, meaning it will be unaffected for max idle expiration or eviction purposes. Also REST can utilize this via the flags header (e.g."PEEK") that is automatically parsed on server-side and converted to Flag enum values.</p>
</div>
<div class="paragraph">
<p>This behavior can also be used via a flag:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">cache.withFlags(Flag.PEEK).get(key);</code></pre>
</div>
</div>
<div class="paragraph">
<p><a href="https://github.com/infinispan/infinispan/issues/17532">[#17532]</a></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="security-enhancements">Security enhancements</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="brute-force-authentication-protection">Brute-force authentication protection</h3>
<div class="paragraph">
<p>Security realms which handle username/password credentials (properties, ldap) are now automatically wrapped by a brute-force authentication protection mechanism: repeated failed attempts to authenticate a user will result in temporary lockdowns. <a href="https://github.com/infinispan/infinispan/issues/18001">[#18001]</a></p>
</div>
</div>
<div class="sect2">
<h3 id="post-quantum-cryptography-configuration">Post-Quantum Cryptography configuration</h3>
<div class="paragraph">
<p>TLS named groups, used by Post-Quantum Cryptography (PQC) algorithms, can be fine-tuned to cater for specific restrictions.
This feature requires JDK 27 or JDK 25.0.5. <a href="https://github.com/infinispan/infinispan/issues/17842">[#17842]</a></p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="console-enhancements">Console enhancements</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="update-by-query-2">Update by query</h3>
<div class="paragraph">
<p>The new update-by-query capability (see above) is also available from the console: an "Update entries" action in the cache Query tab executes an Ickle <code>UPDATE</code> statement against all matching entries, with a confirmation modal and result reporting, alongside the existing delete-by-query. <a href="https://github.com/infinispan/infinispan-console/issues/868">[#868]</a></p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="/assets/images/blog/2026-infinispan-16-3-release/update_by_query.png" alt="Update by query in the console">
</div>
</div>
</div>
<div class="sect2">
<h3 id="editor-and-i18n-improvements">Editor and i18n improvements</h3>
<div class="paragraph">
<p>The code editor used to create and download cache configurations has been improved, and translation loading was fixed so that localized bundles resolve correctly when the console is served from a custom base path. <a href="https://github.com/infinispan/infinispan-console/issues/707">[#707]</a></p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="cli-enhancements">CLI enhancements</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="zero-local-authentication">Zero local authentication</h3>
<div class="paragraph">
<p>A new <code>local</code> security realm allows the CLI to authenticate against a local Infinispan instance without prompting for a username and password, provided it is running local to the server with the same system account. Authentication grants all permissions, so this realm should always be combined with another one (e.g., <code>properties</code>) as a fallback: if the local mechanism fails, the CLI falls back to prompting for credentials of a configured user.</p>
</div>
</div>
<div class="sect2">
<h3 id="optional-transcoders">Optional transcoders</h3>
<div class="paragraph">
<p>The JSON POJO (Jackson) and XML (XStream) transcoders have been moved out of the server core into dedicated modules, joining JBoss Marshalling as an optional module. These are no longer included in the default server distribution: install them with the CLI <code>install</code> command if you need their data conversion capabilities on a running server, or add the corresponding artifact to your dependencies for embedded usage.</p>
</div>
<div class="paragraph">
<p><a href="https://github.com/infinispan/infinispan/issues/17774">[#17774]</a></p>
</div>
</div>
<div class="sect2">
<h3 id="install-with-dependencies">Install with dependencies</h3>
<div class="paragraph">
<p>The <code>install</code> command now supports installing an artifact together with its declared external dependencies via the <code>-d|--with-dependencies</code> option. Artifacts can list their required dependencies in a <code>META-INF/infinispan-dependencies.txt</code> file, and those are downloaded and installed automatically (dependencies already present in <code>server/lib</code> are skipped). For example, this makes it easy to install modules with native or third-party requirements such as the RocksDB cachestore or the optional transcoder modules.</p>
</div>
<div class="paragraph">
<p><a href="https://github.com/infinispan/infinispan/issues/17775">[#17775]</a></p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="operator-enhancements">Operator enhancements</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="schema-crd">Schema CRD</h3>
<div class="paragraph">
<p>A new <code>Schema</code> custom resource allows registering, updating and deleting Protobuf schemas on Infinispan clusters via the REST API (<code>/rest/v2/schemas</code>). It follows the same patterns as the existing Cache CRD, including bidirectional synchronization . This was highly requested, and we&#8217;re glad we could finally deliver it! <a href="https://github.com/infinispan/infinispan-operator/issues/2379">[#2379]</a></p>
</div>
</div>
<div class="sect2">
<h3 id="custom-serviceaccount">Custom ServiceAccount</h3>
<div class="paragraph">
<p>The Infinispan CR now accepts a custom <code>serviceAccountName</code> for the server (and GossipRouter, Batch, Backup and Restore) pods. The ConfigListener can also be given its own service account; when one is specified, the operator does not create RBAC resources for it, leaving that entirely in user hands. <a href="https://github.com/infinispan/infinispan-operator/issues/2404">[#2404]</a></p>
</div>
</div>
<div class="sect2">
<h3 id="pausing-reconciliation">Pausing reconciliation</h3>
<div class="paragraph">
<p>Reconciliation of any managed resource (Infinispan, Cache, Schema, Batch, Backup or Restore) can be paused by annotating it with <code>infinispan.org/paused: true</code>. While paused, spec changes are not applied to the underlying resources - useful for debugging or maintenance windows. Remove the annotation to resume normal operation. <a href="https://github.com/infinispan/infinispan-operator/pull/2585">[PR #2585]</a></p>
</div>
<div class="paragraph">
<p>Other notable improvements:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>the <code>storageClassName</code> is now immutable after creation <a href="https://github.com/infinispan/infinispan-operator/issues/2368">[#2368]</a></p>
</li>
<li>
<p>The operator itself runs as a non-root user with a restricted security context (seccomp profile, no privilege escalation, all capabilities dropped), making it compatible with restrictive Pod Security Standards. <a href="https://github.com/infinispan/infinispan-operator/issues/2573">[#2573]</a></p>
</li>
<li>
<p>The operator now talks to Infinispan 16 servers without using deprecated REST endpoints, eliminating the associated deprecation warnings. <a href="https://github.com/infinispan/infinispan-operator/issues/2474">[#2474]</a></p>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="spring-ai-integration">Spring AI integration</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Infinispan now has a dedicated <a href="https://github.com/infinispan/spring-ai-infinispan">Spring AI integration</a>, released as version 1.0.0.
It provides two capabilities for building AI-powered applications with Spring Boot:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Vector Store</strong> - store and search document embeddings using Infinispan&#8217;s vector search capabilities with Ickle query language. Supports cosine, L2 and inner-product similarity metrics, metadata filtering, and auto-creates Protobuf schemas and indexed caches on startup.</p>
</li>
<li>
<p><strong>Chat Memory Repository</strong> - persist conversation history (user, assistant, system and tool messages) in Infinispan, enabling stateful conversational AI applications.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Both modules come with Spring Boot auto-configuration starters, so getting started is as simple as adding the dependency and pointing to your Infinispan server.</p>
</div>
<div class="paragraph">
<p>For details, see the <a href="https://infinispan.org/docs/spring-ai/1.0.0/spring-ai.html">Spring AI integration documentation</a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="architecture-documentation">Architecture documentation</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The Infinispan documentation now includes a <a href="https://infinispan.org/docs/dev/titles/architecture/architecture.html">detailed architecture</a> document that describes in detail the various components and algorithms doing the dirty work behind the scenes.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="backwards-compatibility">Backwards compatibility</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Infinispan 16.3 is fully backwards compatible with 16.x deployments and can be upgraded in-place.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-next-release">The next release</h2>
<div class="sectionbody">
<div class="paragraph">
<p>16.3 is our third release using our time-boxed schedule.
Adopting predictable release dates, in combination with the backwards compatibility guarantees of our rolling upgrade test harness, has allowed us to achieve great momentum: we&#8217;re delivering features, improvements and bug fixes more quickly.
According to our <a href="/roadmap/">roadmap</a>, our next release will be 16.4, and it will happen on 2027-02-03.
And you can trust it will happen exactly as the roadmap says !</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="release-notes">Release notes</h2>
<div class="sectionbody">
<div class="paragraph">
<p>You can look at the <a href="https://github.com/infinispan/infinispan/releases/tag/16.3.0">release notes</a> to see what was changed since our previous release.</p>
</div>
<div class="paragraph">
<p>Get them from our <a href="https://infinispan.org/download/">download page</a>.</p>
</div>
</div>
</div>]]></content><author><name>Tristan Tarrant</name></author><category term="release" /><category term="final" /><summary type="html"><![CDATA["Tripel Karmeliet"]]></summary></entry><entry><title type="html">Infinispan Hot Rod JS Client 0.16.0: Transactions, Multimaps, and Flags</title><link href="https://infinispan.org/blog/2026/06/29/hotrod-js-client-0-16-0" rel="alternate" type="text/html" title="Infinispan Hot Rod JS Client 0.16.0: Transactions, Multimaps, and Flags" /><published>2026-06-29T00:00:00+00:00</published><updated>2026-06-29T00:00:00+00:00</updated><id>https://infinispan.org/blog/2026/06/29/hotrod-js-client-016</id><content type="html" xml:base="https://infinispan.org/blog/2026/06/29/hotrod-js-client-0-16-0"><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>Hot on the heels of 0.15.0, we&#8217;re happy to announce the release of the <a href="https://github.com/infinispan/js-client/releases/tag/v0.16.0">Infinispan Hot Rod JS Client 0.16.0</a>.
This release brings three new features that round out the client&#8217;s capabilities: transactions, multimap support, and operation flags.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="transactions">Transactions</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Sometimes you need multiple operations to succeed or fail as a unit. The JS client now supports Hot Rod transactions with a simple begin/commit/rollback API:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-javascript hljs" data-lang="javascript">var tm = client.getTransactionManager();

await tm.begin();
try {
  await client.put('account-A', '900');
  await client.put('account-B', '1100');
  await tm.commit();
} catch (e) {
  await tm.rollback();
  throw e;
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>During a transaction, reads go through the local write set first&#8201;&#8212;&#8201;so if you put a value and then get it within the same transaction, you&#8217;ll see your own write.
Under the hood, the client uses XA-style two-phase commit with version-based conflict detection, so concurrent modifications are caught at commit time.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="multimaps">Multimaps</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Multimaps let you associate multiple values with a single key&#8201;&#8212;&#8201;great for tags, categories, or any one-to-many relationship:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-javascript hljs" data-lang="javascript">// Add values to a key
await client.multimapPut('colors', 'red');
await client.multimapPut('colors', 'green');
await client.multimapPut('colors', 'blue');

// Get all values for a key
var values = await client.multimapGet('colors');
console.log(values); // ['red', 'green', 'blue']

// Check containment
await client.multimapContainsEntry('colors', 'red');  // true
await client.multimapContainsValue('green');           // true
await client.multimapContainsKey('colors');             // true

// Remove a specific value or the entire key
await client.multimapRemoveEntry('colors', 'red');
await client.multimapRemoveKey('colors');

// Total number of key-value pairs across all keys
var size = await client.multimapSize();</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="operation-flags">Operation flags</h2>
<div class="sectionbody">
<div class="paragraph">
<p>You can now pass Hot Rod flags to fine-tune how individual operations behave.
Flags are available as constants on the <code>infinispan</code> module and can be combined with bitwise OR:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-javascript hljs" data-lang="javascript">var infinispan = require('infinispan');

// Skip cache store loading
await client.put('key', 'value', {
  flags: infinispan.flags.SKIP_CACHE_LOAD
});

// Combine multiple flags
await client.put('key', 'value', {
  flags: infinispan.flags.SKIP_CACHE_LOAD | infinispan.flags.SKIP_INDEXING
});

// Suppress listener notifications on remove
await client.remove('key', {
  flags: infinispan.flags.SKIP_LISTENER_NOTIFICATION
});</code></pre>
</div>
</div>
<div class="paragraph">
<p>The available flags are:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>FORCE_RETURN_VALUE</code>&#8201;&#8212;&#8201;return the previous value on mutations</p>
</li>
<li>
<p><code>DEFAULT_LIFESPAN</code>&#8201;&#8212;&#8201;use the server-configured default lifespan</p>
</li>
<li>
<p><code>DEFAULT_MAXIDLE</code>&#8201;&#8212;&#8201;use the server-configured default max idle time</p>
</li>
<li>
<p><code>SKIP_CACHE_LOAD</code>&#8201;&#8212;&#8201;don&#8217;t load from the cache store</p>
</li>
<li>
<p><code>SKIP_INDEXING</code>&#8201;&#8212;&#8201;don&#8217;t index the entry</p>
</li>
<li>
<p><code>SKIP_LISTENER_NOTIFICATION</code>&#8201;&#8212;&#8201;don&#8217;t fire listener events</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="get-started">Get started</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Install or update the client via npm:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">npm install infinispan@0.16.0</code></pre>
</div>
</div>
<div class="paragraph">
<p>Check out the full <a href="https://github.com/infinispan/js-client">source code and documentation</a> on GitHub.</p>
</div>
</div>
</div>]]></content><author><name>Tristan Tarrant</name></author><category term="hotrod" /><category term="javascript" /><category term="client" /><category term="release" /><summary type="html"><![CDATA[Hot on the heels of 0.15.0, we&#8217;re happy to announce the release of the Infinispan Hot Rod JS Client 0.16.0. This release brings three new features that round out the client&#8217;s capabilities: transactions, multimap support, and operation flags.]]></summary></entry><entry><title type="html">Hello, Gophers! Introducing the Infinispan Go Client</title><link href="https://infinispan.org/blog/2026/06/22/infinispan-go-client" rel="alternate" type="text/html" title="Hello, Gophers! Introducing the Infinispan Go Client" /><published>2026-06-22T00:00:00+00:00</published><updated>2026-06-22T00:00:00+00:00</updated><id>https://infinispan.org/blog/2026/06/22/infinispan-go-client</id><content type="html" xml:base="https://infinispan.org/blog/2026/06/22/infinispan-go-client"><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>If you&#8217;ve ever thought "I wish I could talk to Infinispan from Go," today is your lucky day.
We&#8217;re thrilled to announce the <a href="https://github.com/infinispan/go-client">Infinispan Go Client</a>&#8201;&#8212;&#8201;a brand-new, pure-Go client that speaks the Hot Rod 4.1 binary protocol and gives you access to all the good stuff: caching, counters, queries, transactions, listeners, and more.</p>
</div>
<div class="paragraph">
<p>No CGo. No JNI bridges. No funny business. Just <code>go get</code> and you&#8217;re off to the races.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="fire-up-a-server">Fire up a server</h2>
<div class="sectionbody">
<div class="paragraph">
<p>First things first: you need an Infinispan server to talk to.
The fastest way is to grab one with Docker (or Podman&#8201;&#8212;&#8201;we don&#8217;t judge):</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">docker run --name infinispan \
  -p 11222:11222 \
  -e USER="admin" \
  -e PASS="password" \
  infinispan/server:16.2</code></pre>
</div>
</div>
<div class="paragraph">
<p>Give it a couple of seconds to start up, and you&#8217;ve got a fully functional Infinispan node listening on port 11222. Easy.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="your-first-put-and-get">Your first Put and Get</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Let&#8217;s jump straight into code. Create a new Go module and grab the client:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">mkdir hello-infinispan &amp;&amp; cd hello-infinispan
go mod init hello-infinispan
go get infinispan.org/go-client/hotrod</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now write a tiny program:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-go hljs" data-lang="go">package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"infinispan.org/go-client/hotrod"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Connect to the server
	client, err := hotrod.NewClient(ctx, "hotrod://admin:password@localhost:11222")
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// Grab a cache handle
	cache := client.Cache("my-cache")

	// Store something
	err = cache.Put(ctx, []byte("greeting"), []byte("Hello from Go!"))
	if err != nil {
		log.Fatal(err)
	}

	// Read it back
	val, found, err := cache.Get(ctx, []byte("greeting"))
	if err != nil {
		log.Fatal(err)
	}
	if found {
		fmt.Println(string(val)) // Hello from Go!
	}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Run it:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">go run .</code></pre>
</div>
</div>
<div class="paragraph">
<p>And there you have it&#8201;&#8212;&#8201;your Go program just stored and retrieved data from a distributed cache. That&#8217;s it. No XML. No YAML. Just a URI and you&#8217;re in.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="what-else-can-it-do">What else can it do?</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Glad you asked. This isn&#8217;t a toy client&#8201;&#8212;&#8201;it&#8217;s packed with features. Here&#8217;s the highlight reel:</p>
</div>
<div class="sect2">
<h3 id="entries-with-ttl-and-idle-timeout">Entries with TTL and idle timeout</h3>
<div class="paragraph">
<p>Don&#8217;t want stale data hanging around? Set a lifespan or a max-idle time on your entries:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-go hljs" data-lang="go">cache.Put(ctx, []byte("session"), []byte("abc123"),
	hotrod.WithLifespan(30*time.Minute),
	hotrod.WithMaxIdle(5*time.Minute),
)</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="conditional-operations">Conditional operations</h3>
<div class="paragraph">
<p>Optimistic locking fans, rejoice. You get compare-and-swap semantics:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-go hljs" data-lang="go">// Only put if the key doesn't exist yet
ok, _ := cache.PutIfAbsent(ctx, []byte("lock"), []byte("mine"))

// Replace only if the entry hasn't been modified
meta, _, _ := cache.GetWithMetadata(ctx, []byte("counter"))
ok, _ = cache.ReplaceIfUnmodified(ctx, []byte("counter"), []byte("42"), meta.Version)</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="bulk-operations">Bulk operations</h3>
<div class="paragraph">
<p>Need to move a lot of data? <code>PutAll</code> and <code>GetAll</code> are hash-distribution-aware, so they route entries directly to the right nodes:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-go hljs" data-lang="go">entries := map[string][]byte{
	"key1": []byte("val1"),
	"key2": []byte("val2"),
	"key3": []byte("val3"),
}
cache.PutAll(ctx, entries)

results, _ := cache.GetAll(ctx, []string{"key1", "key2", "key3"})</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="event-listeners">Event listeners</h3>
<div class="paragraph">
<p>Want to react when data changes? Register a listener and get notified through a channel:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-go hljs" data-lang="go">listener, _ := cache.AddListener(ctx,
	hotrod.WithListenerInterests(hotrod.EventCreated, hotrod.EventModified),
)
defer cache.RemoveListener(ctx, listener)

for event := range listener.Events {
	fmt.Printf("Event: %s on key %s\n", event.Type, event.Key)
}</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="queries-with-ickle">Queries with Ickle</h3>
<div class="paragraph">
<p>If you&#8217;re using Protocol Buffers for your values, you can query them with the Ickle query language:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-go hljs" data-lang="go">result, _ := cache.Query(ctx, "FROM example.Person WHERE age &gt; :minAge",
	hotrod.WithQueryParam("minAge", int32(21)),
	hotrod.WithQueryMaxResults(10),
)</code></pre>
</div>
</div>
<div class="paragraph">
<p>And yes, we support <strong>continuous queries</strong> too&#8201;&#8212;&#8201;subscribe to a query and get notified as entries join, update, or leave the result set. Think of it as a live view of your data.</p>
</div>
</div>
<div class="sect2">
<h3 id="distributed-counters">Distributed counters</h3>
<div class="paragraph">
<p>Need a cluster-wide atomic counter? We&#8217;ve got strong counters (reliable, optionally bounded) and weak counters (high-throughput, eventually consistent):</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-go hljs" data-lang="go">counters := client.Counters()
counters.Define(ctx, "page-views", &amp;hotrod.CounterConfiguration{
	Type:         hotrod.CounterStrong,
	InitialValue: 0,
	Storage:      hotrod.StoragePersistent,
})

counter := counters.Counter("page-views")
newVal, _ := counter.AddAndGet(ctx, 1)</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="near-caching">Near caching</h3>
<div class="paragraph">
<p>For read-heavy workloads, the near cache keeps recently accessed entries on the client side with LRU eviction and automatic server-side invalidation via Bloom filters:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-go hljs" data-lang="go">nc, _ := hotrod.NewNearCache(ctx, client, "my-cache",
	hotrod.WithMaxNearCacheEntries(500),
)
// First Get goes to the server; subsequent ones are served locally
val, found, _ := nc.Get(ctx, []byte("hot-key"))</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="transactions">Transactions</h3>
<div class="paragraph">
<p>When you need atomicity across multiple operations:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-go hljs" data-lang="go">err := client.WithTransaction(ctx, "my-cache", func(tc *hotrod.TxCache) error {
	tc.Put(ctx, []byte("account-A"), []byte("900"))
	tc.Put(ctx, []byte("account-B"), []byte("1100"))
	return nil // commit; return an error to rollback
})</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="type-safe-caches-with-protocol-buffers">Type-safe caches with Protocol Buffers</h3>
<div class="paragraph">
<p>Tired of juggling <code>[]byte</code>? The typed cache gives you generics-powered, type-safe access with automatic marshalling:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-go hljs" data-lang="go">cache := hotrod.NewTypedCache[string, *pb.Person](
	client, "people",
	hotrod.ProtoStreamMarshaller(),
	func() *pb.Person { return &amp;pb.Person{} },
)

cache.Put(ctx, "john", &amp;pb.Person{Name: "John", Age: 30})
person, found, _ := cache.Get(ctx, "john")</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="and-theres-more">And there&#8217;s more</h3>
<div class="ulist">
<ul>
<li>
<p><strong>Iterators</strong>&#8201;&#8212;&#8201;scan through all entries in batches without blowing up your memory</p>
</li>
<li>
<p><strong>Multimap caches</strong>&#8201;&#8212;&#8201;map a single key to multiple values</p>
</li>
<li>
<p><strong>Cache administration</strong>&#8201;&#8212;&#8201;create, configure, and remove caches programmatically</p>
</li>
<li>
<p><strong>Schema management</strong>&#8201;&#8212;&#8201;register and manage Protobuf schemas</p>
</li>
<li>
<p><strong>Pipelined connections</strong>&#8201;&#8212;&#8201;multiple operations fly over a single TCP connection concurrently, so you don&#8217;t pay a round-trip per request</p>
</li>
<li>
<p><strong>Smart routing</strong>&#8201;&#8212;&#8201;topology-aware and hash-distribution-aware client intelligence, so your requests go straight to the node that owns the data</p>
</li>
<li>
<p><strong>TLS and mTLS</strong>&#8201;&#8212;&#8201;because security isn&#8217;t optional</p>
</li>
<li>
<p><strong>SCRAM-SHA-256, PLAIN, OAUTHBEARER, and EXTERNAL auth</strong>&#8201;&#8212;&#8201;pick your SASL flavor</p>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-connection-uri">The connection URI</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Connecting is as simple as a URI:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-none hljs">hotrod://admin:password@server1:11222,server2:11222
hotrods://admin:password@server:11222?trust_store_file_name=/path/to/ca.pem</code></pre>
</div>
</div>
<div class="paragraph">
<p>Use <code>hotrod://</code> for plain connections, <code>hotrods://</code> for TLS, and list as many servers as you like&#8201;&#8212;&#8201;the client discovers the rest through topology updates.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="get-started">Get started</h2>
<div class="sectionbody">
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">go get infinispan.org/go-client/hotrod</code></pre>
</div>
</div>
<div class="paragraph">
<p>Head over to the <a href="https://github.com/infinispan/go-client">GitHub repository</a> for full documentation and examples.
Our <a href="https://infinispan.org/tutorials/?language=go">tutorials</a> are also packed with Go goodies now, so you can hit the ground running with hands-on, step-by-step guides.
We&#8217;d love to hear your feedback&#8201;&#8212;&#8201;open an issue, send a PR, or just say hi. Happy caching, Gophers!</p>
</div>
</div>
</div>]]></content><author><name>Tristan Tarrant</name></author><category term="hotrod" /><category term="go" /><category term="golang" /><category term="client" /><category term="release" /><summary type="html"><![CDATA[If you&#8217;ve ever thought "I wish I could talk to Infinispan from Go," today is your lucky day. We&#8217;re thrilled to announce the Infinispan Go Client&#8201;&#8212;&#8201;a brand-new, pure-Go client that speaks the Hot Rod 4.1 binary protocol and gives you access to all the good stuff: caching, counters, queries, transactions, listeners, and more.]]></summary></entry><entry><title type="html">Hot Rod Dissector for Wireshark</title><link href="https://infinispan.org/blog/2026/06/17/hot-rod-dissector-wireshark" rel="alternate" type="text/html" title="Hot Rod Dissector for Wireshark" /><published>2026-06-17T00:00:00+00:00</published><updated>2026-06-17T00:00:00+00:00</updated><id>https://infinispan.org/blog/2026/06/17/hot-rod-dissector-wireshark</id><content type="html" xml:base="https://infinispan.org/blog/2026/06/17/hot-rod-dissector-wireshark"><![CDATA[<div class="paragraph">
<p>Need to debug a Hot Rod conversation? Don&#8217;t do it the hard way anymore—
grab the right tool and be like Neo: able to decode the Matrix.</p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="/assets/images/blog/2026-06-17-morpheus.png" alt="Alt text" width="width" height="height">
</div>
</div>
<div class="paragraph">
<p>No need for colorful pills—just install the Hot Rod dissector plugin in Wireshark and let it do the hard work. Hot Rod
conversations will appear in a clear, readable format. Now just relax, grab a coffee, and focus
on debugging your application instead of spending time decoding bits!</p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="/assets/images/blog/2026-06-17-dissector.gif" alt="Alt text" width="width" height="height">
</div>
</div>
<div class="paragraph">
<p>The Hot Rod dissector is available at: <a href="https://github.com/rigazilla/hotrod-dissector" class="bare">https://github.com/rigazilla/hotrod-dissector</a></p>
</div>]]></content><author><name>Vittorio Rigamonti</name></author><category term="hot rod" /><category term="dissector" /><category term="wireshark" /><summary type="html"><![CDATA[Need to debug a Hot Rod conversation? Don&#8217;t do it the hard way anymore— grab the right tool and be like Neo: able to decode the Matrix.]]></summary></entry><entry><title type="html">Beyond Per-Cache Eviction</title><link href="https://infinispan.org/blog/2026/06/16/beyond-per-cache-eviction" rel="alternate" type="text/html" title="Beyond Per-Cache Eviction" /><published>2026-06-16T00:00:00+00:00</published><updated>2026-06-16T00:00:00+00:00</updated><id>https://infinispan.org/blog/2026/06/16/beyond-per-cache-eviction</id><content type="html" xml:base="https://infinispan.org/blog/2026/06/16/beyond-per-cache-eviction"><![CDATA[<div class="sect1">
<h2 id="per-cache-memory-limits">Per-cache memory limits</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Up until now, Infinispan has required you to configure memory bounds for each cache individually.
You decide how much memory each cache is allowed to consume, and the eviction machinery enforces those limits independently.</p>
</div>
<div class="paragraph">
<p>Suppose you have a node with 4GB of heap available for caching and five caches to fill it.
You assign each cache its own slice of that budget — say 200MB for some and 300MB for others.
This works, but it means you need to know upfront how much each cache will need.
Caches that don&#8217;t use their full allocation leave memory on the table, while others may be constrained.
Every time you resize a cache you have to revisit the arithmetic.</p>
</div>
<div class="paragraph">
<p>This also means that creating a new cache at runtime requires you to carve out a separate memory allocation for it — memory that has to come from somewhere, potentially requiring you to shrink existing caches.
With shared containers, a new cache can simply join an existing container and immediately share its budget with no reconfiguration needed.</p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="/assets/images/blog/per-cache-eviction.svg" alt="Per-cache eviction">
</div>
</div>
<div class="paragraph">
<p>Infinispan 16.1 introduced <strong>container eviction</strong> as an alternative approach.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="container-eviction">Container eviction</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Container eviction lets you define a single memory boundary that is shared across multiple caches.
Instead of configuring limits per cache, you create a named eviction container at the cache-container level and reference it from any caches that should share that budget.</p>
</div>
<div class="paragraph">
<p>Infinispan supports two types of eviction containers:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>max-size-container</code> — bounds by total memory (e.g., <code>100MB</code>, <code>1GB</code>)</p>
</li>
<li>
<p><code>max-count-container</code> — bounds by total entry count (e.g., <code>1000</code> entries)</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>You can define multiple containers if you need different budgets for different groups of caches.</p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="/assets/images/blog/container-eviction.svg" alt="Container eviction">
</div>
</div>
<div class="paragraph">
<p>In the example below, caches A and B share a <code>hot</code> container (400MB) while caches C, D, and E share a <code>warm</code> container (900MB):</p>
</div>
<details open>
<summary><strong>XML</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-xml hljs" data-lang="xml">&lt;cache-container&gt;
   &lt;eviction-containers&gt;
      &lt;max-size-container name="hot" size="400MB"/&gt;
      &lt;max-size-container name="warm" size="900MB"/&gt;
   &lt;/eviction-containers&gt;

   &lt;distributed-cache name="cache-a"&gt;
      &lt;memory eviction-container="hot"/&gt;
   &lt;/distributed-cache&gt;

   &lt;distributed-cache name="cache-b"&gt;
      &lt;memory eviction-container="hot"/&gt;
   &lt;/distributed-cache&gt;

   &lt;distributed-cache name="cache-c"&gt;
      &lt;memory eviction-container="warm"/&gt;
   &lt;/distributed-cache&gt;

   &lt;distributed-cache name="cache-d"&gt;
      &lt;memory eviction-container="warm"/&gt;
   &lt;/distributed-cache&gt;

   &lt;distributed-cache name="cache-e"&gt;
      &lt;memory eviction-container="warm"/&gt;
   &lt;/distributed-cache&gt;
&lt;/cache-container&gt;</code></pre>
</div>
</div>
</details>
<details>
<summary><strong>JSON</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-json hljs" data-lang="json">{
  "infinispan": {
    "cacheContainer": {
      "evictionContainers": {
        "maxSizeContainer": [
          {
            "name": "hot",
            "size": "400MB"
          },
          {
            "name": "warm",
            "size": "900MB"
          }
        ]
      },
      "distributedCache": {
        "cache-a": {
          "memory": {
            "evictionContainer": "hot"
          }
        },
        "cache-b": {
          "memory": {
            "evictionContainer": "hot"
          }
        },
        "cache-c": {
          "memory": {
            "evictionContainer": "warm"
          }
        },
        "cache-d": {
          "memory": {
            "evictionContainer": "warm"
          }
        },
        "cache-e": {
          "memory": {
            "evictionContainer": "warm"
          }
        }
      }
    }
  }
}</code></pre>
</div>
</div>
</details>
<details>
<summary><strong>YAML</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-yaml hljs" data-lang="yaml">infinispan:
  cacheContainer:
    evictionContainers:
      maxSizeContainer:
        - name: "hot"
          size: "400MB"
        - name: "warm"
          size: "900MB"
    distributedCache:
      "cache-a":
        memory:
          evictionContainer: "hot"
      "cache-b":
        memory:
          evictionContainer: "hot"
      "cache-c":
        memory:
          evictionContainer: "warm"
      "cache-d":
        memory:
          evictionContainer: "warm"
      "cache-e":
        memory:
          evictionContainer: "warm"</code></pre>
</div>
</div>
</details>
<details>
<summary><strong>Java</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-java hljs" data-lang="java">GlobalConfigurationBuilder global = new GlobalConfigurationBuilder();
global.containerMemoryConfiguration("hot")
      .maxSize("400MB");
global.containerMemoryConfiguration("warm")
      .maxSize("900MB");

ConfigurationBuilder cacheA = new ConfigurationBuilder();
cacheA.memory().containerMemory("hot");

ConfigurationBuilder cacheB = new ConfigurationBuilder();
cacheB.memory().containerMemory("hot");

ConfigurationBuilder cacheC = new ConfigurationBuilder();
cacheC.memory().containerMemory("warm");

ConfigurationBuilder cacheD = new ConfigurationBuilder();
cacheD.memory().containerMemory("warm");

ConfigurationBuilder cacheE = new ConfigurationBuilder();
cacheE.memory().containerMemory("warm");</code></pre>
</div>
</div>
</details>
<div class="sect2">
<h3 id="full-feature-compatibility">Full feature compatibility</h3>
<div class="paragraph">
<p>Shared containers are not a stripped-down mode — all of Infinispan&#8217;s regularly supported features continue to work with container eviction.
Indexing, persistence, transactions, expiration, and listeners all function exactly as they do with per-cache eviction.</p>
</div>
<div class="paragraph">
<p>This matters most when it comes to persistence.
We recommend pairing shared containers with a persistence store as a way to automatically alleviate memory pressure while still retaining access to all entries.
With a store configured, every entry is written through to disk, so when the container evicts an entry to stay within its memory budget the data is still available and can be loaded back on demand.</p>
</div>
<div class="paragraph">
<p>The Soft-Index File Store (SIFS) is a natural fit here.
SIFS is Infinispan&#8217;s default file-based store, and its index is designed to adapt to memory conditions.
Index nodes are held via soft references, so when memory is plentiful they stay cached in the heap for fast lookups.
When the JVM comes under memory pressure, the garbage collector can reclaim those soft references and SIFS transparently reloads the index nodes from disk on the next access.
This makes SIFS an ideal companion to shared container eviction — the store index itself cooperates with the JVM to balance performance and memory usage without any manual tuning.</p>
</div>
<div class="paragraph">
<p>Here is an example of a shared container with a SIFS store configured for the <code>my-cache</code>:</p>
</div>
<details open>
<summary><strong>XML</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-xml hljs" data-lang="xml">&lt;cache-container&gt;
   &lt;eviction-containers&gt;
      &lt;max-size-container name="shared" size="1GB"/&gt;
   &lt;/eviction-containers&gt;

   &lt;distributed-cache name="my-cache"&gt;
      &lt;memory eviction-container="shared"/&gt;
      &lt;persistence&gt;
         &lt;file-store/&gt;
      &lt;/persistence&gt;
   &lt;/distributed-cache&gt;

   &lt;distributed-cache name="my-other-cache"&gt;
      &lt;memory eviction-container="shared"/&gt;
   &lt;/distributed-cache&gt;
&lt;/cache-container&gt;</code></pre>
</div>
</div>
</details>
<details>
<summary><strong>JSON</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-json hljs" data-lang="json">{
  "infinispan": {
    "cacheContainer": {
      "evictionContainers": {
        "maxSizeContainer": [
          {
            "name": "shared",
            "size": "1GB"
          }
        ]
      },
      "distributedCache": {
        "my-cache": {
          "memory": {
            "evictionContainer": "shared"
          },
          "persistence": {
            "fileStore": {}
          }
        },
        "my-other-cache": {
          "memory": {
            "evictionContainer": "shared"
          }
        }
      }
    }
  }
}</code></pre>
</div>
</div>
</details>
<details>
<summary><strong>YAML</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-yaml hljs" data-lang="yaml">infinispan:
  cacheContainer:
    evictionContainers:
      maxSizeContainer:
        - name: "shared"
          size: "1GB"
    distributedCache:
      "my-cache":
        memory:
          evictionContainer: "shared"
        persistence:
          fileStore: ~
      "my-other-cache":
        memory:
          evictionContainer: "shared"</code></pre>
</div>
</div>
</details>
<details>
<summary><strong>Java</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-java hljs" data-lang="java">GlobalConfigurationBuilder global = new GlobalConfigurationBuilder();
global.containerMemoryConfiguration("shared")
      .maxSize("1GB");

ConfigurationBuilder cache = new ConfigurationBuilder();
cache.memory()
     .containerMemory("shared");
cache.persistence()
     .addSoftIndexFileStore();

ConfigurationBuilder otherCache = new ConfigurationBuilder();
otherCache.memory()
          .containerMemory("shared");</code></pre>
</div>
</div>
</details>
</div>
<div class="sect2">
<h3 id="tradeoffs-to-keep-in-mind">Tradeoffs to keep in mind</h3>
<div class="paragraph">
<p>All caches sharing a container also share the same eviction policy.
This means that a burst of writes to one cache can cause entries from another cache to be evicted.
If you need strict control over a specific cache&#8217;s memory, per-cache eviction is still the right choice.</p>
</div>
<div class="paragraph">
<p>There is a minor memory overhead per entry.
Each entry in a shared container is stored with a wrapper that holds the cache name and value, adding two pointers and an object header.</p>
</div>
<div class="paragraph">
<p>Clearing a cache or removing segments is slower than with per-cache eviction, because Infinispan must iterate through the shared container to find entries belonging to a specific cache.
In practice, this overhead is typically negligible compared to the network cost of state transfer or rebalancing.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Container eviction is configured per node, not cluster-wide.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Infinispan 16.2 takes this a step further with <strong>dynamic eviction</strong> that automatically adapts to JVM memory pressure at runtime.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="dynamic-eviction">Dynamic eviction</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Container eviction gives you a fixed memory budget, but what happens when the unexpected hits?
A sudden spike of large entries, a burst of temporary computation, or a background task that eats into heap — your fixed budget doesn&#8217;t know about any of it.</p>
</div>
<div class="paragraph">
<p>Starting in Infinispan 16.2, <strong>dynamic eviction</strong> automatically adjusts your container&#8217;s capacity in response to real-time JVM memory pressure.
When the memory monitor detects that the JVM is running low on heap or garbage collection is working too hard, Infinispan shrinks the container to free up space.
When pressure subsides, it gradually grows the container back to its original configured capacity.</p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="/assets/images/blog/dynamic-eviction-states.svg" alt="Dynamic eviction state machine">
</div>
</div>
<div class="paragraph">
<p>Dynamic eviction operates in three states:</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">Stable</dt>
<dd>
<p>Normal operation. The container uses its full configured capacity.</p>
</dd>
<dt class="hdlist1">Shrinking</dt>
<dd>
<p>Memory pressure detected. The container capacity is progressively reduced to free heap space. Infinispan shrinks aggressively to respond quickly to pressure.</p>
</dd>
<dt class="hdlist1">Growing</dt>
<dd>
<p>Pressure has subsided. The container capacity is cautiously restored, with increasing delays between growth steps. If pressure returns during growth, Infinispan immediately switches back to shrinking.</p>
</dd>
</dl>
</div>
<div class="paragraph">
<p>To enable dynamic eviction, you need two things: a memory monitor on the cache container and the <code>dynamic-resize</code> attribute on your eviction container.</p>
</div>
<details open>
<summary><strong>XML</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-xml hljs" data-lang="xml">&lt;cache-container&gt;
   &lt;memory-monitor enabled="true"
                   memory-threshold="0.85"
                   gc-pressure-threshold="0.20" /&gt;
   &lt;eviction-containers&gt;
      &lt;max-size-container name="my-container" size="1GB" dynamic-resize="true"/&gt;
   &lt;/eviction-containers&gt;

   &lt;distributed-cache name="my-cache"&gt;
      &lt;memory eviction-container="my-container"/&gt;
   &lt;/distributed-cache&gt;
&lt;/cache-container&gt;</code></pre>
</div>
</div>
</details>
<details>
<summary><strong>JSON</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-json hljs" data-lang="json">{
  "infinispan": {
    "cacheContainer": {
      "memoryMonitor": {
        "enabled": true,
        "memoryThreshold": 0.85,
        "gcPressureThreshold": 0.20
      },
      "evictionContainers": {
        "maxSizeContainer": [
          {
            "name": "my-container",
            "size": "1GB",
            "dynamicResize": true
          }
        ]
      },
      "distributedCache": {
        "my-cache": {
          "memory": {
            "evictionContainer": "my-container"
          }
        }
      }
    }
  }
}</code></pre>
</div>
</div>
</details>
<details>
<summary><strong>YAML</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-yaml hljs" data-lang="yaml">infinispan:
  cacheContainer:
    memoryMonitor:
      enabled: true
      memoryThreshold: 0.85
      gcPressureThreshold: 0.20
    evictionContainers:
      maxSizeContainer:
        - name: "my-container"
          size: "1GB"
          dynamicResize: true
    distributedCache:
      "my-cache":
        memory:
          evictionContainer: "my-container"</code></pre>
</div>
</div>
</details>
<details>
<summary><strong>Java</strong></summary>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-java hljs" data-lang="java">GlobalConfigurationBuilder global = new GlobalConfigurationBuilder();
global.memoryMonitor()
      .enabled(true);
global.containerMemoryConfiguration("my-container")
      .maxSize("1GB")
      .dynamicResize(true);

ConfigurationBuilder cache = new ConfigurationBuilder();
cache.memory()
     .containerMemory("my-container");</code></pre>
</div>
</div>
</details>
<div class="sect2">
<h3 id="caveats">Caveats</h3>
<div class="paragraph">
<p>Dynamic eviction is designed to ease memory pressure, not guarantee prevention of <code>OutOfMemoryError</code>.
It works best for situations like temporary computation spikes or bursts of incoming data.</p>
</div>
<div class="paragraph">
<p>When using a shared container, prefer <code>max-size-container</code> over <code>max-count-container</code>.
A shared container may hold entries of varying sizes across different caches, so a count-based limit may not accurately reflect actual memory consumption.</p>
</div>
<div class="paragraph">
<p>If you use passivation with a dynamically resized container, be aware that evicted entries must be written to the persistence store.
Under memory pressure, this creates additional memory churn that can reduce the effectiveness of the resize.
Infinispan logs a warning at startup when this combination is detected.</p>
</div>
</div>
<div class="sect2">
<h3 id="the-memory-monitor">The memory monitor</h3>
<div class="paragraph">
<p>At the heart of dynamic eviction is the <strong>memory monitor</strong>, a cache-container-level component that continuously tracks JVM memory health.
It watches two signals:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Memory threshold</strong> — the fraction of old-generation heap in use. When usage exceeds this threshold (default: 85%), the monitor raises a low-memory alert.</p>
</li>
<li>
<p><strong>GC pressure threshold</strong> — the fraction of time spent in garbage collection over a rolling window (default: 60 seconds). When GC overhead exceeds this threshold (default: 20%), the monitor raises a GC pressure alert.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Dynamic eviction registers as a listener on the memory monitor and reacts to these alerts by shrinking or growing containers.
But the memory monitor is not exclusive to eviction — it is a general-purpose component available to any Infinispan subsystem.</p>
</div>
<div class="paragraph">
<p>For example, the query engine already uses the memory monitor.
Sorted non-indexed queries need to load all matching results into memory before sorting.
When the memory monitor reports pressure, Infinispan rejects these queries outright rather than risking an <code>OutOfMemoryError</code>.</p>
</div>
<div class="paragraph">
<p>Future Infinispan releases will extend this further, using the memory monitor to adapt additional subsystems under memory pressure.
The monitor provides a single, consistent view of JVM memory health that any component can subscribe to.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="getting-started">Getting started</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Container eviction is available in Infinispan 16.1 and later.
Dynamic eviction requires Infinispan 16.2 and later.</p>
</div>
<div class="paragraph">
<p>For full configuration reference and additional details, see the
<a href="https://infinispan.org/docs/stable/titles/configuring/configuring.html">Infinispan documentation</a>.</p>
</div>
</div>
</div>]]></content><author><name>William Burns</name></author><category term="eviction" /><category term="memory" /><category term="configuration" /><summary type="html"><![CDATA[Per-cache memory limits]]></summary></entry><entry><title type="html">Infinispan 16.2</title><link href="https://infinispan.org/blog/2026/06/03/infinispan-16-2" rel="alternate" type="text/html" title="Infinispan 16.2" /><published>2026-06-03T00:00:00+00:00</published><updated>2026-06-03T00:00:00+00:00</updated><id>https://infinispan.org/blog/2026/06/03/infinispan-16.2</id><content type="html" xml:base="https://infinispan.org/blog/2026/06/03/infinispan-16-2"><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p><strong><em>"Arctic Panzer Wolf"</em></strong></p>
</div>
<div class="paragraph">
<p>Infinispan 16.2 is here, and it is codenamed <a href="https://untappd.com/b/3-floyds-brewing-arctic-panzer-wolf/5776">"Arctic Panzer Wolf"</a>.
Like the beer, this release packs a punch: it&#8217;s bold, it&#8217;s fierce, and there&#8217;s a lot of it.</p>
</div>
<div class="imageblock text-center">
<div class="content">
<img src="/assets/images/blog/arcticpanzerwolf.png" alt="Arctic Panzer Wolf" width="100" height="300">
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="resp-endpoint">RESP endpoint</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This release is a huge step forward for our Redis-compatible RESP endpoint. So many new things that it deserves its
own section!</p>
</div>
<div class="sect2">
<h3 id="probabilistic-data-structures">Probabilistic data structures</h3>
<div class="paragraph">
<p>We&#8217;ve implemented a whole family of probabilistic data structures:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Bloom Filters</strong> (<code>BF.*</code>): space-efficient probabilistic membership testing. Add items and check whether they <em>might</em> be
in the set — with a configurable false positive rate.</p>
</li>
<li>
<p><strong>Cuckoo Filters</strong> (<code>CF.*</code>): like Bloom Filters, but they also support deletion. Handy when your data is more dynamic.</p>
</li>
<li>
<p><strong>Count-Min Sketch</strong> (<code>CMS.*</code>): estimate the frequency of items in a stream without storing all the data. Perfect for
anomaly detection and traffic analysis.</p>
</li>
<li>
<p><strong>Top-K</strong> (<code>TOPK.*</code>): maintain a list of the <em>K</em> most frequent items in a stream. While CMS can tell you <em>how many times</em>
an item has appeared, Top-K tells you <em>which items</em> appear most often — it tracks the heavy hitters so you don&#8217;t have to
query every item individually.</p>
</li>
<li>
<p><strong>HyperLogLog</strong> (<code>PFCOUNT</code>, <code>PFMERGE</code>): estimate the cardinality of large datasets using very little memory. Count unique
visitors, unique events, unique everything.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>All of these work in clustered mode too!</p>
</div>
</div>
<div class="sect2">
<h3 id="new-bitop-operations-from-redis-8-2">New BITOP operations from Redis 8.2</h3>
<div class="paragraph">
<p>Keeping up with the latest from Redis, we&#8217;ve added the four new bitwise operations introduced in Redis 8.2:
 These give you finer-grained control over bitwise set operations across keys.</p>
</div>
</div>
<div class="sect2">
<h3 id="geosearch-commands">GEOSEARCH commands</h3>
<div class="paragraph">
<p>The <code>GEOSEARCH</code> family of commands is now fully implemented, letting you perform radius and bounding-box queries on
geospatial data.</p>
</div>
</div>
<div class="sect2">
<h3 id="copy-delex-digest-and-more">COPY, DELEX, DIGEST, and more</h3>
<div class="ulist">
<ul>
<li>
<p><code>COPY</code>: copy a key to another key, optionally replacing the destination.</p>
</li>
<li>
<p><code>DELEX</code>: delete a key only if it exists (returning whether it was actually deleted).</p>
</li>
<li>
<p><code>DIGEST</code>: return a hash digest of a key&#8217;s value.</p>
</li>
<li>
<p><code>SET</code> conditional options: additional flags for conditional set operations.</p>
</li>
<li>
<p><code>PSUBSCRIBE</code> / <code>PUNSUBSCRIBE</code>: pattern-based Pub/Sub subscriptions.</p>
</li>
<li>
<p><code>AGGREGATE COUNT</code> option for <code>ZUNION</code>, <code>ZINTER</code>, <code>ZUNIONSTORE</code>, and <code>ZINTERSTORE</code>.</p>
</li>
<li>
<p><code>BITFIELD</code> / <code>BITOP</code>: implemented all operations.</p>
</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="local-resp-caches-in-clustered-mode">Local RESP caches in clustered mode</h3>
<div class="paragraph">
<p>You can now use local RESP caches in a clustered server. Previously, a local RESP cache created on an unclustered server
would prevent the server from starting in clustered mode. No more!</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="infinispan-server">Infinispan Server</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="granular-jvm-options">Granular JVM options</h3>
<div class="paragraph">
<p>The server startup scripts now split <code>JAVA_OPTS</code> into independently overridable categories:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>JAVA_OPTS_BASE</code> — essential JVM flags (headless, ExitOnOutOfMemoryError, incubator modules)</p>
</li>
<li>
<p><code>JAVA_OPTS_NETWORK</code> — network settings (IPv4/IPv6 stack preference)</p>
</li>
<li>
<p><code>JAVA_OPTS_MEMORY</code> — heap, metaspace, and RAM percentage configuration</p>
</li>
<li>
<p><code>JAVA_OPTS_DEBUG</code> — JPDA debug settings</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Want to tweak just the memory settings? Set <code>JAVA_OPTS_MEMORY</code> and leave the rest alone. Setting <code>JAVA_OPTS</code> directly
still overrides everything for backward compatibility.</p>
</div>
</div>
<div class="sect2">
<h3 id="ecs-logging-support">ECS logging support</h3>
<div class="paragraph">
<p>The server now bundles <code>log4j-layout-template-json</code>, enabling native
<a href="https://www.elastic.co/guide/en/ecs/current/index.html">Elastic Common Schema (ECS)</a> formatted logging out of the box.
Just point your Log4j2 config at the built-in ECS template:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-xml hljs" data-lang="xml">&lt;JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json"/&gt;</code></pre>
</div>
</div>
<div class="paragraph">
<p>No more custom images or runtime library hacking to get structured JSON logs into your Elasticsearch/Kibana stack.</p>
</div>
</div>
<div class="sect2">
<h3 id="simplified-pem-certificate-configuration">Simplified PEM certificate configuration</h3>
<div class="paragraph">
<p>TLS configuration with PEM certificates has been unified and simplified. The server now auto-detects keystore types,
so you no longer need to juggle different property names for PEM vs PKCS#12 certificates.</p>
</div>
</div>
<div class="sect2">
<h3 id="minimal-boot-logging">Minimal boot logging</h3>
<div class="paragraph">
<p>The server now uses minimal logging until the classpath is fully configured, preventing noisy or misleading log
messages during early startup.</p>
</div>
</div>
<div class="sect2">
<h3 id="configurable-backpressure-for-hot-rod-client-listeners">Configurable backpressure for Hot Rod client listeners</h3>
<div class="paragraph">
<p>The server now supports configurable backpressure for Hot Rod client event listeners. When a client cannot consume
events fast enough, the server buffers them up to a configurable limit instead of dropping events or blocking the
originating cache operation. This gives operators control over the trade-off between memory usage and event delivery
reliability for slow consumers.</p>
</div>
</div>
<div class="sect2">
<h3 id="backup-support-for-multimaps-resp-and-memcached-caches">Backup support for multimaps, RESP, and Memcached caches</h3>
<div class="paragraph">
<p>Server backups now include multimap caches as well as caches created via the RESP and Memcached endpoints. Previously,
these were silently skipped during backup and restore operations.</p>
</div>
</div>
<div class="sect2">
<h3 id="rocksdb-cache-store-removed-from-the-server-distribution">RocksDB cache store removed from the server distribution</h3>
<div class="paragraph">
<p>The RocksDB cache store has been moved out of the server distribution. It is still available as a separate module if
you need it, but the server image is now lighter. The bundled SIFS store is the recommended persistence option.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="cli">CLI</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="suggestions">Suggestions</h3>
<div class="paragraph">
<p>The CLI now provides suggestions based on your command history and command syntax.</p>
</div>
</div>
<div class="sect2">
<h3 id="connection-bookmarks">Connection bookmarks</h3>
<div class="paragraph">
<p>Tired of typing long connection URLs and credentials every time? The new <code>bookmark</code> command lets you save named
connection bookmarks:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-shell hljs" data-lang="shell">bookmark set prod -u https://prod-server:11222 --username admin --password secret
connect prod</code></pre>
</div>
</div>
<div class="paragraph">
<p>Passwords are stored encrypted in a PKCS12 credential store. Bookmarks are also used by the new <code>mcp</code> CLI command for
a seamless AI integration experience.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="core">Core</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="json-configuration-schemas">JSON configuration schemas</h3>
<div class="paragraph">
<p>Infinispan now provides JSON schemas that mirror the existing XSD schemas, making it easier to author and validate
configuration in JSON and YAML formats with full editor support and autocompletion.</p>
</div>
</div>
<div class="sect2">
<h3 id="pull-based-state-transfer">Pull-based state transfer</h3>
<div class="paragraph">
<p>State transfer has been reworked to use a pull-based approach instead of the previous push model. This gives the
receiving node much better control over backpressure and memory consumption during rebalancing. Previously, all existing
owners would push data simultaneously, making memory pressure scale linearly with cluster size. The new approach is
smarter and more efficient.</p>
</div>
</div>
<div class="sect2">
<h3 id="memory-monitor">Memory monitor</h3>
<div class="paragraph">
<p>A new <code>MemoryMonitor</code> component tracks JVM memory and GC health with configurable thresholds:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Memory threshold alerts when old generation heap usage exceeds a percentage (default 85%)</p>
</li>
<li>
<p>GC duration alerts when a single GC pause is too long (default 5 seconds)</p>
</li>
<li>
<p>GC pressure tracking alerts when too much time is spent in GC over a rolling window</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>All thresholds are tunable at runtime and configurable via a new <code>&lt;memory-monitor&gt;</code> element.</p>
</div>
</div>
<div class="sect2">
<h3 id="dynamic-eviction-based-on-memory-pressure">Dynamic eviction based on memory pressure</h3>
<div class="paragraph">
<p>Eviction can now be driven by actual JVM memory pressure instead of relying solely on static entry counts or fixed
memory sizes. When enabled, Infinispan monitors old generation heap usage and dynamically adjusts eviction thresholds
to keep the JVM healthy. This means caches can make the most of available memory without the guesswork of manually
sizing <code>max-count</code> or <code>max-size</code> — the system responds to real conditions, evicting more aggressively when memory
is tight and relaxing when there is headroom.</p>
</div>
</div>
<div class="sect2">
<h3 id="serialized-cache-entries-on-heap">Serialized cache entries on heap</h3>
<div class="paragraph">
<p>You can now store cache entries in their serialized form even when using heap storage. This is particularly useful for
entries with many fields where the serialized byte representation is more memory-efficient than the full Java object
graph.</p>
</div>
</div>
<div class="sect2">
<h3 id="graceful-shutdown-and-network-partitions">Graceful shutdown and network partitions</h3>
<div class="paragraph">
<p>The graceful shutdown procedure has been hardened to handle network partitions correctly. Previously, star-shaped
partitions (where the coordinator could see all nodes, but nodes couldn&#8217;t see each other) during restart could lead to
data loss. This has been fixed.</p>
</div>
</div>
<div class="sect2">
<h3 id="orderly-cache-scale-down">Orderly cache scale-down</h3>
<div class="paragraph">
<p>Stopping a cache or (concurrently) scaling down a cluster node while state transfer is in progress could previously
result in data loss: the departing node would leave before its data had been fully redistributed to the remaining members.
New overloads on <code>Cache.stop(timeout, TimeUnit)</code>, <code>EmbeddedCacheManager.stop(timeout, TimeUnit)</code>, and
<code>EmbeddedCacheManager.stopCache(cacheName, timeout, TimeUnit)</code> let you specify how long to wait for any in-flight state
transfer to complete before leaving. If the timeout elapses, the method returns <code>false</code> so you can decide what to do next.
The existing no-arg <code>stop()</code> methods continue to work as before.</p>
</div>
</div>
<div class="sect2">
<h3 id="generic-micrometer-meterregistry-support">Generic Micrometer MeterRegistry support</h3>
<div class="paragraph">
<p>Infinispan now supports any Micrometer <code>MeterRegistry</code>, not just Prometheus. This means you can use OTLP, Simple, or any
other Micrometer registry implementation without needing a Prometheus dependency on the classpath.</p>
</div>
</div>
<div class="sect2">
<h3 id="counter-configuration-events">Counter configuration events</h3>
<div class="paragraph">
<p>Counter configuration changes (add/modify/remove) now generate container events and are streamed via the REST SSE
listeners. This enables the Infinispan Operator to implement Counter custom resources.</p>
</div>
</div>
<div class="sect2">
<h3 id="sifs-persistence-store-improvements">SIFS persistence store improvements</h3>
<div class="paragraph">
<p>The Soft-Index File Store (SIFS) has received several reliability and performance improvements:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Buffered index updates</strong>: index writes are now batched in memory before being flushed to disk, reducing the number of
I/O operations and improving write throughput.</p>
</li>
<li>
<p><strong>B+ tree refactoring</strong>: the internal B+ tree used for the SIFS index has been extracted into a standalone, independently
testable class, resolving a number of hard-to-reproduce corruption issues related to soft reference reclamation and
concurrent access.</p>
</li>
<li>
<p><strong>Segment lifecycle fixes</strong>: fixed a race condition where rapid segment removal and re-addition could corrupt the index,
and ensured that segment removal completes fully before the segment can be reused.</p>
</li>
<li>
<p><strong>Persistence timeout support</strong>: persistence operations now have configurable timeouts, preventing the system from
hanging indefinitely when a store operation stalls or a lock is never released.</p>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="protostream">ProtoStream</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The ProtoStream serialization library has seen major improvements across performance, usability, and JSON support:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>ASCII string optimizations</strong>: writing ASCII strings now takes advantage of the JVM&#8217;s internal <code>String</code> coder and value
fields, and uses <code>VarHandle</code> for fixed-width writes, reducing encoding overhead.</p>
</li>
<li>
<p><strong>Kotlin support</strong>: the annotation processor now supports Kotlin classes and <code>data class</code> types.</p>
</li>
<li>
<p><strong>Copy-on-write serialization context</strong>: the serialization context state is now copy-on-write, improving thread safety
when schemas are registered concurrently as well as improving performance.</p>
</li>
<li>
<p><strong>JPMS support</strong>: ProtoStream is now fully modularized with proper <code>module-info</code> descriptors.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="query">Query</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="antlr-4">ANTLR 4</h3>
<div class="paragraph">
<p>The Ickle query parser has been migrated from the unmaintained ANTLR 3 to ANTLR 4, improving reproducible builds and
eliminating a stale runtime dependency.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="spring-boot">Spring Boot</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="automatic-schema-registration">Automatic schema registration</h3>
<div class="paragraph">
<p>The Spring Boot integration now automatically registers Protobuf schemas, matching the behavior that was already
available in the Quarkus integration.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="console">Console</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The web console has received a batch of usability improvements:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>User permissions page</strong>: a new page to display current user permissions, making it easier to understand who can
do what.</p>
</li>
</ul>
</div>
<div class="imageblock text-center">
<div class="content">
<a class="image" href="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_My_Permissions_Menu.png"><img src="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_My_Permissions_Menu.png" alt="My Permissions menu" width="300"></a>
</div>
</div>
<div class="imageblock text-center">
<div class="content">
<a class="image" href="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_My_Permissions.png"><img src="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_My_Permissions.png" alt="My Permissions page"></a>
</div>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Schema editor with syntax highlighting</strong>: the schema editor now uses the Monaco code editor with Protobuf syntax
highlighting and code assistance.</p>
</li>
</ul>
</div>
<div class="imageblock text-center">
<div class="content">
<a class="image" href="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_Edit_Schemas.png"><img src="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_Edit_Schemas.png" alt="Schema editor with syntax highlighting"></a>
</div>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Manage columns in cache detail</strong>: customize which columns are displayed in the cache entries view.</p>
</li>
</ul>
</div>
<div class="imageblock text-center">
<div class="content">
<a class="image" href="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_Columns.png"><img src="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_Columns.png" alt="Manage columns dialog"></a>
</div>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Query history</strong>: your recent queries are now remembered, so you can quickly re-run them.</p>
</li>
</ul>
</div>
<div class="imageblock text-center">
<div class="content">
<a class="image" href="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_Query.png"><img src="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_Query.png" alt="Query values view"></a>
</div>
</div>
<div class="imageblock text-center">
<div class="content">
<a class="image" href="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_Query_History.png"><img src="/assets/images/blog/2026-infinispan-16-2-release/16.2.Console_Query_History.png" alt="Query history view"></a>
</div>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Swagger UI and metrics links</strong>: direct links to the Swagger UI and metrics endpoints from the console.</p>
</li>
<li>
<p><strong>Truncate result values</strong>: a checkbox to truncate long values in query results for better readability.</p>
</li>
<li>
<p><strong>Persistent pagination settings</strong>: pagination preferences are now remembered across sessions.</p>
</li>
<li>
<p><strong>Cache deletion from detail view</strong>: delete a cache directly from its detail page.</p>
</li>
<li>
<p><strong>Internationalization</strong>: updated translations for French, Spanish, Italian, and Brazilian Portuguese.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="mcp">MCP</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="cli-stdio-transport">CLI stdio transport</h3>
<div class="paragraph">
<p>Connecting your AI tools to Infinispan just got a whole lot easier. We&#8217;ve added an <code>mcp</code> command to the CLI that acts as
a stdio transport bridge for the Model Context Protocol.</p>
</div>
<div class="paragraph">
<p>This means you can now use Infinispan as an MCP server from Claude Desktop, Claude Code, VS Code, or any other MCP
client:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-json hljs" data-lang="json">{
  "mcpServers": {
    "infinispan": {
      "command": "infinispan",
      "args": ["mcp", "--bookmark", "prod-cluster"]
    }
  }
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>The CLI command connects to the server&#8217;s <code>/v3/mcp</code> HTTP endpoint and proxies JSON-RPC messages via stdin/stdout. Use it
with CLI bookmarks (we mentioned those above!) for a seamless, secure connection.</p>
</div>
</div>
<div class="sect2">
<h3 id="gc-log-as-mcp-resource">GC log as MCP resource</h3>
<div class="paragraph">
<p>The server&#8217;s GC log is now exposed as an MCP resource, so your AI tools can inspect garbage collection behavior directly.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="backwards-compatibility">Backwards compatibility</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Infinispan 16.2 is fully backwards compatible with 16.1 deployments.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-next-release">The next release</h2>
<div class="sectionbody">
<div class="paragraph">
<p>According to our <a href="/roadmap/">roadmap</a>, our next release will be 16.3, and it will happen on 2026-10-07.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="release-notes">Release notes</h2>
<div class="sectionbody">
<div class="paragraph">
<p>You can look at the <a href="https://github.com/infinispan/infinispan/releases/tag/16.2.0">release notes</a> to see what was changed
since our previous release.</p>
</div>
<div class="paragraph">
<p>Get them from our <a href="https://infinispan.org/download/">download page</a>.</p>
</div>
</div>
</div>]]></content><author><name>Tristan Tarrant</name></author><category term="release" /><category term="final" /><summary type="html"><![CDATA["Arctic Panzer Wolf"]]></summary></entry><entry><title type="html">Infinispan Hot Rod JS Client 0.14.0: Near Feature Parity with Java</title><link href="https://infinispan.org/blog/2026/05/06/hotrod-js-client-0-14-0" rel="alternate" type="text/html" title="Infinispan Hot Rod JS Client 0.14.0: Near Feature Parity with Java" /><published>2026-05-06T00:00:00+00:00</published><updated>2026-05-06T00:00:00+00:00</updated><id>https://infinispan.org/blog/2026/05/06/hotrod-js-client-014</id><content type="html" xml:base="https://infinispan.org/blog/2026/05/06/hotrod-js-client-0-14-0"><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>We are happy to announce the release of the <a href="https://github.com/infinispan/js-client/releases/tag/v0.14.0">Infinispan Hot Rod JS Client 0.14.0</a>.
This release brings the JavaScript client very close to feature parity with the Java Hot Rod client, with support for near caching, distributed counters, admin operations, and protocol auto-negotiation.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="protocol-auto-negotiation">Protocol auto-negotiation</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The client can now automatically negotiate the highest mutually-supported protocol version with the server.
No more guessing which version to use: just set <code>version: 'auto'</code> and the client will pick the best available.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-javascript hljs" data-lang="javascript">var infinispan = require('infinispan');

var client = await infinispan.client(
  {port: 11222, host: 'localhost'},
  {
    version: 'auto',
    authentication: {
      enabled: true,
      saslMechanism: 'DIGEST-MD5',
      userName: 'admin',
      password: 'changeit'
    }
  }
);

// Check which version was negotiated
console.log('Protocol:', client.getProtocolVersion());  // e.g. '4.1'</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="near-caching">Near caching</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Near caching stores recently accessed entries on the client side, dramatically reducing latency for read-heavy workloads.
The cache uses LRU eviction and is automatically invalidated when entries are modified.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-javascript hljs" data-lang="javascript">var client = await infinispan.client(
  {port: 11222, host: 'localhost'},
  {
    version: 'auto',
    nearCache: { maxEntries: 16 },
    authentication: { /* ... */ }
  }
);

await client.put('key', 'value');

// First get: fetches from server and populates the near cache
var v1 = await client.get('key');

// Second get: served from the local near cache, no server roundtrip
var v2 = await client.get('key');

// Writes automatically invalidate the near cache
await client.put('key', 'new-value');</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="distributed-counters">Distributed counters</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Distributed counters are cluster-wide atomic counters, available in two flavors: <strong>strong</strong> (linearizable, optionally bounded) and <strong>weak</strong> (higher concurrency, eventually consistent).</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-javascript hljs" data-lang="javascript">// Strong unbounded counter
await client.counterCreate('page-views', {
  type: 'strong',
  initialValue: 0
});

// Strong bounded counter
await client.counterCreate('stock', {
  type: 'strong',
  initialValue: 50,
  lowerBound: 0,
  upperBound: 1000,
  storage: 'persistent'
});

// Weak counter for high-throughput scenarios
await client.counterCreate('impressions', {
  type: 'weak',
  initialValue: 0,
  concurrencyLevel: 4
});

// Atomic operations
var current = await client.counterGet('page-views');
var updated = await client.counterAddAndGet('page-views', 1);
var swapped = await client.counterCompareAndSwap('stock', 50, 49);
await client.counterReset('page-views');</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="admin-operations">Admin operations</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The <code>client.admin</code> namespace provides cache lifecycle management and Protobuf schema administration, enabling full cluster management directly from JavaScript.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-javascript hljs" data-lang="javascript">// Create a cache from XML configuration
await client.admin.createCache('sessions',
  '&lt;local-cache&gt;&lt;encoding media-type="text/plain"/&gt;&lt;/local-cache&gt;'
);

// Idempotent create: no error if it already exists
await client.admin.getOrCreateCache('sessions',
  '&lt;local-cache&gt;&lt;encoding media-type="text/plain"/&gt;&lt;/local-cache&gt;'
);

// List all caches
var names = await client.admin.cacheNames();

// Remove a cache
await client.admin.removeCache('sessions');

// Register a Protobuf schema
await client.admin.registerSchema('person.proto', `
  package example;
  message Person {
    required string name = 1;
    optional int32 age = 2;
  }
`);</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="protocol-3-1-4-0-and-4-1-support">Protocol 3.1, 4.0, and 4.1 support</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This release adds support for Hot Rod protocol versions 3.1, 4.0, and 4.1, each bringing new capabilities:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Protocol 3.1</strong>&#8201;&#8212;&#8201;Distributed counter operations</p>
</li>
<li>
<p><strong>Protocol 4.0</strong>&#8201;&#8212;&#8201;Previous value with metadata in mutation responses</p>
</li>
<li>
<p><strong>Protocol 4.1</strong>&#8201;&#8212;&#8201;JSON data format support</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-javascript hljs" data-lang="javascript">// Protocol 4.0+: mutation operations can return previous values
var client = await infinispan.client(
  {port: 11222, host: 'localhost'},
  { version: '4.0', authentication: { /* ... */ } }
);

await client.put('key', 'original');
var prev = await client.put('key', 'updated', { previous: true });
console.log(prev);  // 'original'</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="get-started">Get started</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Install the client via npm:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">npm install infinispan</code></pre>
</div>
</div>
<div class="paragraph">
<p>Check out the full <a href="https://github.com/infinispan/js-client">source code and documentation</a> on GitHub.</p>
</div>
</div>
</div>]]></content><author><name>Tristan Tarrant</name></author><category term="hotrod" /><category term="javascript" /><category term="client" /><category term="release" /><summary type="html"><![CDATA[We are happy to announce the release of the Infinispan Hot Rod JS Client 0.14.0. This release brings the JavaScript client very close to feature parity with the Java Hot Rod client, with support for near caching, distributed counters, admin operations, and protocol auto-negotiation.]]></summary></entry></feed>