<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Blogs by Harrish]]></title><description><![CDATA[Blogs by Harrish]]></description><link>https://harrishdhaithya.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Blogs by Harrish</title><link>https://harrishdhaithya.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 19 Sep 2026 13:28:02 GMT</lastBuildDate><atom:link href="https://harrishdhaithya.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why Your `counter++` Is Lying to You: A Practical Guide to Race Conditions in Java]]></title><description><![CDATA[You wrote a counter. It increments. You ran it across a few threads, summed the results, and got a number that was almost right — off by a handful, in a way that changes every run and never reproduces]]></description><link>https://harrishdhaithya.hashnode.dev/why-your-counter-is-lying-to-you-a-practical-guide-to-race-conditions-in-java</link><guid isPermaLink="true">https://harrishdhaithya.hashnode.dev/why-your-counter-is-lying-to-you-a-practical-guide-to-race-conditions-in-java</guid><dc:creator><![CDATA[HARRISH DHAITHYA K A]]></dc:creator><pubDate>Wed, 24 Jun 2026 14:38:03 GMT</pubDate><content:encoded><![CDATA[<p>You wrote a counter. It increments. You ran it across a few threads, summed the results, and got a number that was <em>almost</em> right — off by a handful, in a way that changes every run and never reproduces in the debugger. That's a race condition, and no amount of staring will fix it.</p>
<p>A race condition isn't a rare timing gremlin you exorcise by sprinkling <code>synchronized</code> over everything that moves. It's a <strong>missing happens-before relationship</strong> — a spot where one thread writes and another reads, and nothing in your code guarantees the reader sees the write. Once you think in those terms, the fix stops being "add a lock" and becomes "pick the cheapest tool that creates the ordering I need." Sometimes that's a lock. Often it's something smaller.</p>
<p>This post is the practical version: just enough memory-model theory to make the fixes make sense, then the actual tools — atomics, locks, concurrent collections, immutability — and when to reach for each.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a2de18651c2cbdb8fc95f39/da3dcd2a-ef18-4237-9cc5-6da41dc203a6.jpg" alt="A multithreaded counter producing the wrong total, with question marks over the discrepancy" style="display:block;margin:0 auto" />

<h2>The short version (TL;DR)</h2>
<ul>
<li><p><code>c++</code> is not one operation. It's read, add, write — and two threads can interleave those steps so one update vanishes.</p>
</li>
<li><p>A <strong>race condition</strong> means correctness depends on timing. A <strong>data race</strong> is the memory-model version: two conflicting accesses with no happens-before edge between them. They overlap but aren't the same thing, and fixing one doesn't guarantee the other.</p>
</li>
<li><p>Three separate problems hide under "race condition": <strong>atomicity</strong>, <strong>visibility</strong>, and <strong>ordering</strong>. <code>volatile</code> fixes two of them and <em>not</em> atomicity. This trips up everyone.</p>
</li>
<li><p>The fix is the narrowest correct tool: immutability/confinement → atomics → locks → concurrent collections. Blanket <code>synchronized</code> is correct but usually the wrong default.</p>
</li>
</ul>
<h2>The <code>counter++</code> that loses count</h2>
<p>Here's the classic broken counter:</p>
<pre><code class="language-java">class Counter {
    private int c = 0;

    public void increment() { c++; }
    public void decrement() { c--; }
    public int  value()     { return c; }
}
</code></pre>
<p>It looks innocent. The problem is that <code>c++</code> isn't atomic — that single expression decomposes into three steps: read the current value of <code>c</code>, add 1 to the value you read, and store the result back into <code>c</code>.</p>
<p>Three steps means two threads can interleave them. Thread A reads <code>0</code>. Before A stores, Thread B also reads <code>0</code>. Both increment their local copy to <code>1</code>. Both store <code>1</code>. Two increments happened; the counter went up by one. One update was silently overwritten.</p>
<p><a href="https://drive.google.com/file/d/18vw9IrUQde7eo18Irrfsu4B0p1KFV6hw/view?usp=sharing"><img src="https://cdn.hashnode.com/uploads/covers/6a2de18651c2cbdb8fc95f39/3d8223b2-2c85-4fe9-b8fb-0253ae1747e4.png" alt="Timeline showing two threads each doing read, increment, write on a shared counter — one update lost" style="display:block;margin:0 auto" /></a></p>
<p>Run a million increments split across threads and you won't get a million. You'll get something smaller, something different each run. That non-determinism is the signature of the whole problem.</p>
<h2>What a race condition actually is (and what a data race is)</h2>
<p>"Race condition" and "data race" get used interchangeably. They're related, but the distinction pays off.</p>
<p>A <strong>race condition</strong> is a flaw where the timing or ordering of events affects whether the program produces the right answer. Your counter qualifies — it only gives the right total if the threads happen not to step on each other.</p>
<p>A <strong>data race</strong> is the memory-model definition, and it's narrower: two accesses to the same variable, at least one of them a write, that are not ordered by a happens-before relationship. That's it — same location, conflicting, unordered.</p>
<p>Here's the part worth tattooing somewhere: <strong>these are distinct, and neither implies the other.</strong> You can have a race condition with no data race — for instance, two threads each calling thread-safe methods on a shared object in an order that produces a wrong result. And you can eliminate every data race and <em>still</em> be incorrect, because data-race-freedom says nothing about whether your higher-level invariants hold.</p>
<p>So "I added <code>volatile</code>, no more data race" is not the same sentence as "my program is correct." Keep them separate in your head.</p>
<p>The everyday symptoms fall into two buckets: <strong>thread interference</strong> (the interleaving we just saw) and <strong>memory consistency errors</strong>, where different threads have inconsistent views of what should be the same data. Both trace back to the same root cause, which is the next section.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a2de18651c2cbdb8fc95f39/31edb9f0-f873-4e4d-9470-886f70cbf30f.png" alt="Two threads, a shared variable, and a missing arrow between a write and a read labeled &quot;no happens-before&quot;" style="display:block;margin:0 auto" />

<h2>Why it happens: atomicity, visibility, ordering</h2>
<p>Three different problems wear the same "race condition" costume. Telling them apart is the single most useful skill here, because each tool fixes a different subset.</p>
<p><strong>Atomicity.</strong> A compound action that has to happen all-at-once-or-not-at-all. <code>c++</code> is read-modify-write. <code>if (x == null) x = new T()</code> is check-then-act. If another thread can wedge itself into the middle, you lose. These compound actions must execute atomically to stay thread-safe.</p>
<p><strong>Visibility.</strong> One thread writes a field; another thread never sees the new value — or sees it much later. Without synchronization there is no guarantee that a write in one thread becomes visible to a read in another. A thread that prints an unsynchronized counter another thread just set to <code>1</code> may legitimately print <code>0</code>.</p>
<p><strong>Ordering.</strong> The compiler and CPU are allowed to reorder operations as long as a single thread can't tell. Across threads, that freedom can expose half-finished state. The happens-before relationship is what constrains it: a guarantee that the memory writes of one statement are visible to another specific statement.</p>
<p>Now the misconception that causes the most bad code:</p>
<p><code>volatile</code> <strong>fixes visibility and ordering. It does <em>not</em> fix atomicity.</strong></p>
<p>A write to a <code>volatile</code> field happens-before every subsequent read of that field, so every read sees the most recent write — a volatile write behaves like a lock release and a volatile read like a lock acquire. That handles visibility and ordering completely.</p>
<p>But a volatile <code>int</code> does nothing for <code>c++</code>, because the read and the write are still two separate operations with a gap in between. Marking the counter <code>volatile</code> makes the staleness go away and leaves the lost-update bug intact. People declare a field <code>volatile</code>, watch one symptom vanish, and ship the other one to production.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a2de18651c2cbdb8fc95f39/23605547-4271-485c-94fb-a5499d5ec0c2.png" alt="Comparison grid: atomicity, visibility, ordering across volatile, synchronized, atomics" style="display:block;margin:0 auto" />

<h2>The other classic: check-then-act and lazy init</h2>
<p>The counter is read-modify-write. Its evil twin is <strong>check-then-act</strong>, and the textbook example is lazy initialization:</p>
<pre><code class="language-java">// Broken: two threads can both pass the null check
public Singleton getInstance() {
    if (instance == null) {            // check
        instance = new Singleton();    // act
    }
    return instance;
}
</code></pre>
<p>Two threads hit <code>getInstance()</code> at once, both see <code>null</code>, both construct a <code>Singleton</code>. The "single line" logical operation raced.</p>
<p>The infamous attempted fix is <strong>double-checked locking</strong> — check, lock, check again. Don't write it from memory, because the obvious version is broken. Without a <code>volatile</code> field, a second thread can observe a <em>partially constructed</em> object: the reference is published before the constructor's writes are visible, so the reader gets a non-null pointer to an object whose fields aren't there yet. Double-checked locking was broken before Java 5, and it's only correct now if the field is <code>volatile</code>.</p>
<p>The cleaner answer is to not write double-checked locking at all. For lazy initialization, use the <strong>initialization-on-demand holder idiom</strong>:</p>
<pre><code class="language-java">public class Singleton {
    private Singleton() {}

    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;   // class init is thread-safe, lazy, lock-free on the hot path
    }
}
</code></pre>
<p>The JVM guarantees a class is initialized exactly once, lazily, the first time it's used — so the language hands you the correctness for free. No <code>volatile</code> to forget, no second check to get subtly wrong.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a2de18651c2cbdb8fc95f39/dac11073-a940-441d-a707-522d9d215c08.png" alt="Two threads observing a half-built object through a non-volatile reference" style="display:block;margin:0 auto" />

<h2>How do you even catch these?</h2>
<p>Race conditions are miserable to detect because they're non-deterministic. They pass your unit tests. They pass them a thousand times. Then they show up under production load on a Tuesday. <strong>Ordinary tests can't prove the absence of a race</strong> — a green run just means <em>this particular interleaving</em> happened to work.</p>
<p>So lean on tooling that actually understands concurrency, in roughly three tiers.</p>
<p><strong>Static analysis.</strong> <a href="https://spotbugs.github.io/">SpotBugs</a> (successor to FindBugs) inspects bytecode without running your code and ships a <em>Multithreaded correctness</em> category of bug patterns — inconsistent synchronization of a field, non-atomic sequences of calls on a concurrent collection, waiting without a guard, and similar. It's cheap to wire into CI and catches real mistakes, but like all static analysis it produces false positives, so read its findings rather than worship them.</p>
<p><strong>Stress testing.</strong> When you're verifying an actual concurrency primitive — a lock-free queue, a tricky publish — use <a href="https://github.com/openjdk/jcstress">jcstress</a>, the OpenJDK concurrency stress harness. It runs many threads that rendezvous on shared state, then tabulates which outcomes actually occurred, surfacing the rare interleavings ordinary tests never hit. This is the serious tool.</p>
<p><strong>Dynamic detection.</strong> There's an OpenJDK <a href="https://openjdk.org/jeps/8208520">JEP draft for a Java Thread Sanitizer</a> — a runtime data-race detector for Java and JNI, in the spirit of C++'s ThreadSanitizer. Worth knowing it exists, but it's a <em>draft proposal</em>, not something the JDK ships today. Don't plan around it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a2de18651c2cbdb8fc95f39/6ac64ca3-7d0f-48fa-93cc-26e078388b54.png" alt="Funnel diagram: many flaky passing tests narrowing down to real bugs via stress + static analysis" style="display:block;margin:0 auto" />

<h2>Fix #1 — <code>synchronized</code>, the blunt-but-honest instrument</h2>
<p><code>synchronized</code> does two things at once: it gives you <strong>mutual exclusion</strong> (only one thread in the guarded region at a time) and it establishes <strong>happens-before</strong> — an unlock on a monitor happens-before every subsequent lock on that same monitor. That's the whole package: atomicity, visibility, and ordering for the guarded region.</p>
<pre><code class="language-java">class Counter {
    private int c = 0;
    public synchronized void increment() { c++; }
    public synchronized void decrement() { c--; }
    public synchronized int  value()     { return c; }
}
</code></pre>
<p>This counter is correct. Two caveats, both load-bearing.</p>
<p>First, <strong>both threads must lock on the same monitor.</strong> A write under lock A and a read under lock B share no happens-before edge — you've paid for synchronization and gotten none of its guarantees. This is a depressingly common bug.</p>
<p>Second, synchronization introduces <strong>contention</strong>: threads pile up waiting on the same resource. Correct, but it serializes access, and if you wrap everything in one fat lock you've built a concurrent program that runs like a single-threaded one.</p>
<p>One forward-looking note for Java 21: a virtual thread that blocks inside a <code>synchronized</code> block gets <em>pinned</em> to its carrier thread. Pinning doesn't make code incorrect, but it can hinder scalability, so the guidance is to guard frequently-contended, long-running I/O with <code>ReentrantLock</code> instead of <code>synchronized</code>. (Short, in-memory critical sections are fine.)</p>
<p><code>synchronized</code> is the right answer when you genuinely need a compound action to be atomic and the region is short. It's the wrong answer as a reflex applied to everything.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a2de18651c2cbdb8fc95f39/c7ec8454-85e5-452b-bc06-092825244946.png" alt="Monitor ownership diagram showing happens-before edge from unlock to next lock on same monitor" style="display:block;margin:0 auto" />

<h2>Fix #2 — Explicit locks when <code>synchronized</code> isn't enough</h2>
<p><code>ReentrantLock</code> gives you the same memory semantics as <code>synchronized</code> plus capabilities the keyword can't express: timed acquisition (<code>tryLock(timeout)</code>), interruptible locking, non-block-structured locking, and optional fairness. The price is that you own the unlock — and you must release it in a <code>finally</code>, or one exception leaks the lock forever:</p>
<pre><code class="language-java">private final ReentrantLock lock = new ReentrantLock();

public void update() {
    lock.lock();
    try {
        // critical section
    } finally {
        lock.unlock();   // non-negotiable
    }
}
</code></pre>
<p>For read-heavy data where reads vastly outnumber writes, a <code>ReadWriteLock</code> lets many readers proceed concurrently while writers get exclusive access. And <code>StampedLock</code> (Java 8+) adds an <em>optimistic read</em> mode: grab a stamp, read, then validate that no write happened meanwhile — fast when writes are rare, at the cost of more careful code.</p>
<p>Default to <code>synchronized</code> for simple, short critical sections — it's harder to misuse and the JVM optimizes it well. Reach for explicit locks when you specifically need one of their features: a timeout, interruptibility, or read/write separation.</p>
<h2>Fix #3 — Atomics, for single-variable updates</h2>
<p>For a lone counter or flag, you don't need a lock at all. The <code>java.util.concurrent.atomic</code> classes give you lock-free read-modify-write:</p>
<pre><code class="language-java">class AtomicCounter {
    private final AtomicInteger c = new AtomicInteger(0);
    public void increment() { c.incrementAndGet(); }
    public void decrement() { c.decrementAndGet(); }
    public int  value()     { return c.get(); }
}
</code></pre>
<p><code>incrementAndGet()</code> performs the whole read-modify-write as one indivisible operation, so the lost-update bug is gone. Under the hood these use <strong>compare-and-set (CAS)</strong>: read the current value, compute the new one, and swap it in <em>only if</em> the value hasn't changed — retrying if it has. No mutual exclusion, no blocking.</p>
<p>The memory semantics are first-class too: an atomic's <code>get</code>/<code>set</code> behave like reads and writes of a <code>volatile</code> field, so a <code>set</code> happens-before any subsequent <code>get</code> on the same variable. You get visibility without synchronization.</p>
<p>On performance, stay honest. Atomic-variable access is generally more efficient than synchronized access on modern hardware, and the advantage is strongest under low-to-moderate contention. Under heavy contention the CAS retry loop can thrash. So atomics are an excellent default for single-variable state, not a guaranteed speedup you can quote a multiplier for.</p>
<p>The catch is in the name — <em>single variable</em>. The moment two fields must change together atomically, a single atomic won't do it, and you're back to a lock (or a single atomic reference to an immutable holder object).</p>
<h2>Fix #4 — Concurrent collections instead of wrapped ones</h2>
<p>If your shared state is a map or a list, the common mistake is wrapping a plain collection (<code>Collections.synchronizedMap</code>) and then doing check-then-act on it:</p>
<pre><code class="language-java">// Still racy: two threads can both see the key missing, both put
if (!map.containsKey(key)) {
    map.put(key, compute(key));
}
</code></pre>
<p>Each individual call is synchronized, yet the <em>sequence</em> races — exactly the "thread-safe building blocks, unsafe combination" trap from earlier. A thread-safe collection doesn't make your compound action thread-safe.</p>
<p>Purpose-built concurrent collections fix this by giving you the compound operation as a single atomic method:</p>
<pre><code class="language-java">map.putIfAbsent(key, compute(key));      // atomic check-then-act
map.computeIfAbsent(key, this::compute); // atomic, computes only if absent
</code></pre>
<p><code>ConcurrentHashMap</code>, <code>CopyOnWriteArrayList</code>, and the <code>BlockingQueue</code> implementations are built for concurrent access and expose atomic compound operations like <code>putIfAbsent</code>, <code>compute</code>, and <code>merge</code>. A <code>ConcurrentHashMap</code> also scales better under concurrent writes than a fully synchronized wrapper, because it doesn't hold one lock over the entire map — though the real speedup depends entirely on your access pattern, so don't reach for a fixed number.</p>
<p>The rule: if multiple threads touch a collection, use a collection designed for it, and use its atomic methods rather than rolling your own check-then-act on top.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a2de18651c2cbdb8fc95f39/79e57685-9757-4659-88c1-e33605309893.png" alt="Coarse single-lock map versus a finer-grained concurrent map" style="display:block;margin:0 auto" />

<h2>Fix #5 — The cheapest fix: no shared mutable state</h2>
<p>Every fix so far manages access to shared mutable state. The best move is often to not have any.</p>
<p><strong>Immutable objects are inherently thread-safe.</strong> There are no writes to race on — once constructed, the object never changes, so every thread sees the same consistent value. Java's <code>record</code> types make this nearly free for data carriers.</p>
<p>The memory model even gives immutable objects a special guarantee. When an object is properly constructed — meaning its <code>this</code> reference doesn't escape during the constructor — the values assigned to its <code>final</code> fields are visible to all other threads without synchronization. Build it once with <code>final</code> fields, publish it safely, and readers see it correctly with zero locking.</p>
<p>One sharp edge: <code>final</code> guarantees the <em>reference</em> won't change, not that what it points to is immutable. A <code>final List&lt;String&gt;</code> whose contents you keep mutating is still shared mutable state and still races. Deep immutability means immutable all the way down (or an unmodifiable copy).</p>
<p>When the data has to be mutable, the other escape hatch is <strong>confinement</strong> — don't share it at all. <code>ThreadLocal</code> gives each thread its own copy; stack confinement (a local variable never handed to another thread) is even simpler and race-proof by construction. No sharing, no race.</p>
<h2>Which tool, when — the decision guide</h2>
<p>Top to bottom, stop at the first match.</p>
<ol>
<li><p><strong>Can you avoid sharing mutable state?</strong> Make it immutable, or confine it to one thread. Cheapest correct fix; no synchronization to get wrong.</p>
</li>
<li><p><strong>Is it a single variable (counter, flag, reference)?</strong> Use an <code>Atomic*</code> class. Lock-free, simple, fast under normal contention.</p>
</li>
<li><p><strong>Is it a shared collection?</strong> Use a concurrent collection and its atomic compound methods (<code>computeIfAbsent</code>, <code>putIfAbsent</code>, <code>merge</code>).</p>
</li>
<li><p><strong>Do you need a compound action across multiple fields to be atomic?</strong> Use <code>synchronized</code> for short, simple regions; <code>ReentrantLock</code>/<code>ReadWriteLock</code>/<code>StampedLock</code> when you need timeouts, interruptibility, or read/write separation.</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/6a2de18651c2cbdb8fc95f39/1b4c2c20-ee1d-438c-8978-2e7712181ae8.png" alt="Decision flowchart for choosing the right concurrency tool" style="display:block;margin:0 auto" />

<p>The principle underneath all four: <strong>reach for the narrowest correct tool.</strong> Wrapping everything in <code>synchronized</code> is correct, and it's also how you build a program that's slow <em>and</em> hides its design problems behind a lock. The narrow tool both performs better and forces you to understand what state is actually shared — which is the real win.</p>
<h2>A race-condition review checklist</h2>
<p>Steal this for code review:</p>
<ul>
<li><p>Is there a field written by one thread and read by another? If so, what creates the happens-before edge between them? If you can't name it, it's a data race.</p>
</li>
<li><p>Does a "single" operation actually decompose into read-modify-write or check-then-act? Those need to be atomic.</p>
</li>
<li><p>Are all accesses to a guarded field using the <strong>same</strong> lock/monitor?</p>
</li>
<li><p>Did you reach for <code>volatile</code> to fix something that's actually an atomicity problem? (It won't.)</p>
</li>
<li><p>Is there a <code>final</code> reference to a <em>mutable</em> object you keep mutating? That's still shared mutable state.</p>
</li>
<li><p>For a shared collection: are you doing check-then-act on it instead of using an atomic method like <code>computeIfAbsent</code>?</p>
</li>
<li><p>Could this just be immutable or thread-confined and dodge the whole question?</p>
</li>
</ul>
<p>The mental model that ties it together: a race condition is a missing happens-before relationship. Every tool here — atomics, locks, concurrent collections, safe publication of immutable objects — is just a different way to <em>create</em> that relationship. Pick the cheapest one that does the job, name why it works, and move on.</p>
<hr />
<h2>Sources</h2>
<ul>
<li><p>Oracle — The Java Tutorials, Concurrency lesson (thread interference, memory consistency, synchronization, atomic variables): <a href="https://docs.oracle.com/javase/tutorial/essential/concurrency/">https://docs.oracle.com/javase/tutorial/essential/concurrency/</a></p>
</li>
<li><p>Oracle — <em>The Java Language Specification, Java SE 21</em>, Chapter 17 (Threads and Locks): <a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-17.html">https://docs.oracle.com/javase/specs/jls/se21/html/jls-17.html</a></p>
</li>
<li><p>Oracle — Java SE 21 Core Libraries: Concurrency &amp; Virtual Threads: <a href="https://docs.oracle.com/en/java/javase/21/core/concurrency.html">https://docs.oracle.com/en/java/javase/21/core/concurrency.html</a></p>
</li>
<li><p>JSR-133 (Java Memory Model) FAQ — Manson &amp; Goetz: <a href="https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html">https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html</a></p>
</li>
<li><p><em>Java Concurrency in Practice</em> — Goetz et al.: <a href="https://jcip.net/">https://jcip.net/</a></p>
</li>
<li><p>John Regehr — "Race Condition vs. Data Race": <a href="https://blog.regehr.org/archives/490">https://blog.regehr.org/archives/490</a></p>
</li>
<li><p>OpenJDK jcstress (concurrency stress harness): <a href="https://github.com/openjdk/jcstress">https://github.com/openjdk/jcstress</a></p>
</li>
<li><p>SpotBugs — bug descriptions (Multithreaded correctness): <a href="https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html">https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html</a></p>
</li>
<li><p>OpenJDK — JEP draft: Java Thread Sanitizer: <a href="https://openjdk.org/jeps/8208520">https://openjdk.org/jeps/8208520</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>