<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Pasha</title>
    <description>The latest articles on Forem by Pasha (@asm0dey).</description>
    <link>https://forem.com/asm0dey</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F19726%2F09923b5e-fafd-473d-ab9c-6e35b6f72c62.jpg</url>
      <title>Forem: Pasha</title>
      <link>https://forem.com/asm0dey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/asm0dey"/>
    <language>en</language>
    <item>
      <title>I wrote my third XML parser. Here's why this one was different.</title>
      <dc:creator>Pasha</dc:creator>
      <pubDate>Sun, 26 Apr 2026 19:18:28 +0000</pubDate>
      <link>https://forem.com/asm0dey/i-wrote-my-third-xml-parser-heres-why-this-one-was-different-445i</link>
      <guid>https://forem.com/asm0dey/i-wrote-my-third-xml-parser-heres-why-this-one-was-different-445i</guid>
      <description>&lt;p&gt;Hi, I'm Pasha, and I write XML parsers.&lt;/p&gt;

&lt;p&gt;Not because the world needs another one. The world has &lt;a href="https://github.com/pdvrieze/xmlutil" rel="noopener noreferrer"&gt;xmlutil&lt;/a&gt; by Paul de Vrieze, which I will say specific nice things about further down. The world has JAXB. The world has, depending on how you count, several hundred XML libraries on Maven Central. Adding to the pile is not on anyone's wishlist.&lt;/p&gt;

&lt;p&gt;And yet, here I am.&lt;/p&gt;

&lt;p&gt;The first one I wrote years ago for a previous employer, behind a closed-source repo I no longer have access to. The second one, &lt;a href="https://github.com/asm0dey/staks" rel="noopener noreferrer"&gt;staks&lt;/a&gt;, is mine and works very well — if you write Kotlin and only Kotlin, and you are happy hand-rolling a small DSL per record. The third one is xml-fluss, which I just released, and the rest of this post is about why it exists.&lt;/p&gt;

&lt;p&gt;So, let's get going.&lt;/p&gt;

&lt;h4&gt;
  
  
  The feed that started it
&lt;/h4&gt;

&lt;p&gt;I have a soft spot for OPDS catalogs — Atom-flavored XML feeds for ebook libraries. They are exactly the kind of thing XML was invented for and exactly the kind of thing modern tooling makes you suffer to read.&lt;/p&gt;

&lt;p&gt;Here is a fragment from an OPDS feed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;feed&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2005/Atom"&lt;/span&gt;
      &lt;span class="na"&gt;xmlns:opds=&lt;/span&gt;&lt;span class="s"&gt;"http://opds-spec.org/2010/catalog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My Books&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;updated&amp;gt;&lt;/span&gt;2026-04-26T10:00:00Z&lt;span class="nt"&gt;&amp;lt;/updated&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;entry&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;urn:isbn:9780000000001&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;The Master and Margarita&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;author&amp;gt;&amp;lt;name&amp;gt;&lt;/span&gt;Mikhail Bulgakov&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&amp;lt;/author&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"http://opds-spec.org/acquisition"&lt;/span&gt;
          &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/epub+zip"&lt;/span&gt;
          &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/get/1.epub"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/entry&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;entry&amp;gt;&lt;/span&gt; ... &lt;span class="nt"&gt;&amp;lt;/entry&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;entry&amp;gt;&lt;/span&gt; ... &lt;span class="nt"&gt;&amp;lt;/entry&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- 200,000 more entries --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/feed&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I want three things out of every &lt;code&gt;&amp;lt;entry&amp;gt;&lt;/code&gt;: the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, the author's &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt;, and the acquisition &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;'s &lt;code&gt;href&lt;/code&gt;. I do not care about anything else. I am also aware that "200,000 more entries" is not a hypothetical — real catalogs ship feeds with hundreds of thousands of records. I am not going to load that into memory.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why xmlutil wasn't quite the shape
&lt;/h4&gt;

&lt;p&gt;Before I tell you what I did, a quick word on xmlutil. &lt;a href="https://www.linkedin.com/in/pauldv/" rel="noopener noreferrer"&gt;Paul de Vrieze&lt;/a&gt; has been working on it for years. It runs on the entire Kotlin Multiplatform target list (JVM, JS, Native, Android), plugs into kotlinx-serialization, handles QName-aware namespaces with prefix repair and Clark notation, encodes XML back out as well as decoding it, and supports mixed content, sealed-class polymorphism, and inheritance. If you need to &lt;em&gt;encode&lt;/em&gt; XML, or your document matches a schema you fully control, that's the library to reach for. I mean it.&lt;/p&gt;

&lt;p&gt;But.&lt;/p&gt;

&lt;p&gt;The default deserialization path, &lt;code&gt;XML.decodeFromString(...)&lt;/code&gt;, builds the whole tree. Fine for config files, painful for a 2 GB feed.&lt;/p&gt;

&lt;p&gt;There is a streaming escape hatch: &lt;code&gt;decodeWrappedToSequence(reader)&lt;/code&gt;. It is real and it works. I checked. The constraints: it expects a &lt;code&gt;&amp;lt;container&amp;gt;&amp;lt;Item/&amp;gt;&amp;lt;Item/&amp;gt;&amp;lt;/container&amp;gt;&lt;/code&gt; shape, it is marked &lt;code&gt;@OptIn(ExperimentalXmlUtilApi::class)&lt;/code&gt;, and the part that mattered to me is that every field on every &lt;code&gt;Item&lt;/code&gt; still has to be declared on a &lt;code&gt;@Serializable&lt;/code&gt; data class, including the ones I do not care about, because that is how a binder works. Binders mirror.&lt;/p&gt;

&lt;p&gt;What I wanted was a different shape of tool: find the records anywhere in this document, decode three fields per record, ignore the rest, never buffer the whole thing. That's a different model from a binder, and there's room for both.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why staks wasn't the shape either
&lt;/h4&gt;

&lt;p&gt;Quick aside on my own previous attempt. staks works, and I still use it for personal projects. Its DSL is designed for a Kotlin codebase with a Kotlin developer at the keyboard. The moment you put a Java consumer in the picture — say, a Spring Boot service in a polyglot codebase that wants the same parser — you discover that "tiny Kotlin DSL" is the worst surface area to expose to javac.&lt;/p&gt;

&lt;p&gt;So when I started xml-fluss, I picked an architecture where the Kotlin user and the Java user could share the same runtime and the same annotation surface, and only the code-generator backend differs.&lt;/p&gt;

&lt;h4&gt;
  
  
  What xml-fluss looks like in five lines
&lt;/h4&gt;

&lt;p&gt;Here is roughly what xmlutil would have me write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Serializable&lt;/span&gt;
&lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"entry"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlElement&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlElement&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlElement&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlElement&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlElement&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlElement&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Serializable&lt;/span&gt; &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@XmlElement&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;
&lt;span class="nd"&gt;@Serializable&lt;/span&gt; &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@XmlAttribute&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;@XmlAttribute&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what I get to write with xml-fluss:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@XmlRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"//atom:entry"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@XmlNs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"atom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"http://www.w3.org/2005/Atom"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@XmlNs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"opds"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"http://opds-spec.org/2010/catalog"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"atom:title"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"atom:author/atom:name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"atom:link/@href"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;download&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire description. No &lt;code&gt;id&lt;/code&gt;, no &lt;code&gt;updated&lt;/code&gt;, no &lt;code&gt;summary&lt;/code&gt;, no nested &lt;code&gt;Link&lt;/code&gt; class — because I do not need any of it. The KSP processor walks this &lt;code&gt;data class&lt;/code&gt; at compile time and emits a &lt;code&gt;BookParser&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://example.org/opds/all"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toURL&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;openStream&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;BookParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${book.author} — ${book.title}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser sits on top of &lt;a href="https://github.com/FasterXML/aalto-xml" rel="noopener noreferrer"&gt;Aalto&lt;/a&gt;, a fast pull parser. It never holds more than one &lt;code&gt;Book&lt;/code&gt; in memory. The path matcher is a small NFA — a nondeterministic finite automaton, which is the same machinery regex engines use under the hood. The compiled path becomes a graph of states, and as the parser walks the document it keeps a stack of which states are currently active. When a &lt;code&gt;START_ELEMENT&lt;/code&gt; arrives that satisfies the descendant axis and the namespace, you enter record mode; when the matching &lt;code&gt;END_ELEMENT&lt;/code&gt; arrives, you emit. The descendant axis (&lt;code&gt;//&lt;/code&gt;) keeps a state alive across deeper elements so that &lt;code&gt;//book&lt;/code&gt; matches whether the &lt;code&gt;&amp;lt;book&amp;gt;&lt;/code&gt; is two levels down or twelve.&lt;/p&gt;

&lt;p&gt;Predicate filters evaluate at &lt;code&gt;START_ELEMENT&lt;/code&gt; time using only the attributes present on the opening tag. That's the design, and it's what makes the streaming guarantee real. If the parser had to look ahead at the body of the element to decide whether to enter it, you would buffer.&lt;/p&gt;

&lt;h4&gt;
  
  
  A small mini-XPath, on purpose
&lt;/h4&gt;

&lt;p&gt;I wanted just enough path syntax to express the things I actually do every week:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;entry&lt;/span&gt;&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;descendant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;axis&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;library&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;author&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;anchored&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;positional&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@featured&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'true'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;@lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="nt"&gt;www.w3.org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2005&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;Atom&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="na"&gt;@href&lt;/span&gt;&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;leaf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;child&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boolean logic is &lt;code&gt;and&lt;/code&gt; / &lt;code&gt;or&lt;/code&gt; with &lt;code&gt;and&lt;/code&gt; precedence higher. Predicates use equality, inequality, and integer position. That's it. It is not XPath 3.1 and it is not trying to be — it is the minimum syntax that lets me select the records I want without writing a visitor. A more elaborate predicate engine would mean buffering, ambiguity around evaluation order, and a much bigger surface area to debug. I'd rather keep the engine small.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Java side, where jspecify earns its keep
&lt;/h4&gt;

&lt;p&gt;xml-fluss has a second module, &lt;code&gt;xml-fluss-apt&lt;/code&gt;, that does the same thing for Java records via a plain javac annotation processor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@NullMarked&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;my.books&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@XmlRecord&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"//atom:entry"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@XmlNs&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"atom"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://www.w3.org/2005/Atom"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlChild&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"atom:title"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlChild&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"atom:author/atom:name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@XmlChild&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"atom:link/@href"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Nullable&lt;/span&gt;                                 &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;download&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;// usage&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BookParser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" — "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things are happening here that I am quietly proud of.&lt;/p&gt;

&lt;p&gt;First: a single annotation surface (&lt;code&gt;xmlfluss.*&lt;/code&gt;) on top of one shared runtime feeds two code generators (KSP for Kotlin, javac APT for Java records), so you get &lt;code&gt;Flow&amp;lt;T&amp;gt;&lt;/code&gt; or &lt;code&gt;Stream&amp;lt;T&amp;gt;&lt;/code&gt; depending on which side you write on.&lt;/p&gt;

&lt;p&gt;Second: there is no separate &lt;code&gt;nullable = true&lt;/code&gt; argument anywhere. On the Kotlin side this is free — the language already distinguishes &lt;code&gt;String&lt;/code&gt; from &lt;code&gt;String?&lt;/code&gt;, so the KSP processor just reads the type and that's the answer. Java doesn't have built-in null-safety, so the APT processor reads &lt;a href="https://jspecify.dev" rel="noopener noreferrer"&gt;jspecify&lt;/a&gt; annotations instead. A record component sitting inside a &lt;code&gt;@NullMarked&lt;/code&gt; scope without an explicit &lt;code&gt;@Nullable&lt;/code&gt; is treated as required at parse time; the &lt;code&gt;@Nullable&lt;/code&gt; ones can be absent without complaint. Same end result both languages, sourced from the type system that each one already has.&lt;/p&gt;

&lt;p&gt;Why invent another &lt;code&gt;nullable=true&lt;/code&gt; argument when both languages already give the answer?&lt;/p&gt;

&lt;h4&gt;
  
  
  What it deliberately does not do
&lt;/h4&gt;

&lt;p&gt;A short list of things xml-fluss is not, so you can rule it out fast if you need them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It does not encode XML, and it cannot.&lt;/strong&gt; This isn't a missing feature, it's structural. The annotation surface is a &lt;em&gt;query&lt;/em&gt; over an unknown document. How would you render &lt;code&gt;@XmlRecord("//author//book[3]")&lt;/code&gt; back out? Where does the third book go, under which author, with what surrounding tags that the data class never described? The path tells the parser where to &lt;em&gt;look&lt;/em&gt;, not how to &lt;em&gt;build&lt;/em&gt;. Encoding is a binder's job, because a binder knows the whole structure. If you need to write XML, that is what xmlutil is for.&lt;/li&gt;
&lt;li&gt;It does not validate against a schema. Out of scope.&lt;/li&gt;
&lt;li&gt;Predicates only see attributes of the current element, never text content of a child — see the streaming reason above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need encoding or schema validation, reach for a different tool. Predicates evaluating only on element entry is a design choice, not an oversight, but if you have a clean idea for a richer predicate language that preserves the streaming guarantee, PRs welcome.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to try it
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Gradle, Kotlin&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jvm"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.google.devtools.ksp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"2.3.7"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"site.asm0dey.xmlfluss:xml-fluss-runtime:0.0.0.3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;ksp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"site.asm0dey.xmlfluss:xml-fluss-ksp:0.0.0.3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Gradle, Java records&lt;/span&gt;
&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"site.asm0dey.xmlfluss:xml-fluss-runtime:0.0.0.3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;annotationProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"site.asm0dey.xmlfluss:xml-fluss-apt:0.0.0.3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repo is at &lt;a href="https://github.com/asm0dey/xml-fluss" rel="noopener noreferrer"&gt;github.com/asm0dey/xml-fluss&lt;/a&gt; — README, more examples, full annotation reference, the lot. Issues and PRs welcome. If you try it on a real feed and something explodes, that is the most useful gift you can give me right now.&lt;/p&gt;

&lt;p&gt;If you only need to encode XML, or you have a schema you fully control and a binding model fits your shape, please use &lt;a href="https://github.com/pdvrieze/xmlutil" rel="noopener noreferrer"&gt;xmlutil&lt;/a&gt;. Paul has put a lot of careful work into it and it shows.&lt;/p&gt;

&lt;p&gt;But if you have ever found yourself writing a SAX &lt;code&gt;ContentHandler&lt;/code&gt; at midnight, or modeling 47 wrapper classes to pull three fields out of a feed, give xml-fluss a look.&lt;/p&gt;

&lt;p&gt;I might be on parser number four by then.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>java</category>
      <category>xml</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Java celebrates its 29th birthday</title>
      <dc:creator>Pasha</dc:creator>
      <pubDate>Thu, 23 May 2024 12:47:32 +0000</pubDate>
      <link>https://forem.com/asm0dey/java-celebrates-its-29th-birthday-519e</link>
      <guid>https://forem.com/asm0dey/java-celebrates-its-29th-birthday-519e</guid>
      <description>&lt;p&gt;Java celebrates its 29th birthday today! To pay homage to Java, I have decided to look at its walk of fame from the eyes of a developer (that is, your humble servant). As the narrative below is quite personal, it omits many key points because they were not important to me. On the other hand, it includes many points that other people may consider insignificant, but not me.&lt;/p&gt;

&lt;h2&gt;
  
  
  1989: Project Oak
&lt;/h2&gt;

&lt;p&gt;Java didn’t get its name yet. It was called Oak and was a part of an ambitious project, including not only a language, but also an OS and libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  1994: Java 1.0
&lt;/h2&gt;

&lt;p&gt;Oak was renamed to Java. Shortly after, Java 1.0 saw the light.&lt;br&gt;
Those were the days of Green Threads in Java! But for better or for worse, they would be abandoned in &lt;a href="https://docs.oracle.com/cd/E19455-01/806-3461/6jck06gqe/index.html" rel="noopener noreferrer"&gt;Java 1.1&lt;/a&gt; to appear later in Java 21. Also, there were no generics, and JMM was very different (and frankly, hard to understand and deal with).&lt;/p&gt;

&lt;p&gt;In addition, AWT was already there, but Swing wasn’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  1997: JDK 1.1
&lt;/h2&gt;

&lt;p&gt;This is the year when things started to get standardized. There were multiple standards introduced: JDBC, RMI, Java Beans, i18n, etc. It’s hard to believe, but they all are still in Java and supported by frameworks such as Hibernate. Besides, they are changed in a backwards-compatible manner; many of us are in a love–hate relationship with this approach, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  1998: J2SE 1.2
&lt;/h2&gt;

&lt;p&gt;This year, Swing came into the game. Also, this is the year when JDK was renamed to J2SE: Java 2 Platform Standard Edition. This is because there was at least one other edition of J2: Mobile, a subset of Java intended to work on weak devices. I still remember my first time patching the Java heap size  on my Sony-Ericsson five years later. And games were written in Java, too.&lt;/p&gt;

&lt;p&gt;Also, CORBA was introduced. We still use it today but call it gRPC (just kidding). CORBA itself would live in Java for a long time.&lt;/p&gt;

&lt;h2&gt;
  
  
  2002: J2SE 1.4
&lt;/h2&gt;

&lt;p&gt;Every Java release introduces a bunch of interesting changes, but I’m not going to enumerate them all because as I mentioned, I want it to be more of a personal story. The reason I brought up 1.4 is that it introduced Java Web Start and Applets, a way to call Java from a web browser. It seemed like magic: I could run a full-fledged application by clicking an icon in the browser. It might have been quite slow, but it was just unbelievably powerful for something working in a browser. Remember, JS was not a real thing for the front-end development back then, let alone the development of heavy logics.&lt;/p&gt;

&lt;p&gt;By the way, &lt;a href="https://bell-sw.com/announcements/2020/11/13/BellSoft-Kicks-Off-Bundled-Offer-with-Karakun/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=pasha&amp;amp;utm_content=javabirthday" rel="noopener noreferrer"&gt;Liberica JDK supports OpenWebStart&lt;/a&gt; in case you need it!&lt;/p&gt;

&lt;h2&gt;
  
  
  2004: Java SE 5
&lt;/h2&gt;

&lt;p&gt;This was a huge one! Not only because it was the first release I worked with as a developer, but because it brought tremendous changes into Java. I dare to say that it’s the foundation of today’s Java. The release brought&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generics&lt;/li&gt;
&lt;li&gt;Enums&lt;/li&gt;
&lt;li&gt;The new (current) JMM (Java Memory Model)&lt;/li&gt;
&lt;li&gt;java.util.concurrent&lt;/li&gt;
&lt;li&gt;varargs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think that these features made Java a modern language!&lt;br&gt;
Also, it was the last release supporting Windows 98. Never liked it, I used to be a Windows XP person (until 2009, when I switched to Linux).&lt;/p&gt;

&lt;h2&gt;
  
  
  2011: Java SE 7
&lt;/h2&gt;

&lt;p&gt;It'd been a whopping five years since the release of Java 6! I moved from total noob in development to something middle senior and spent almost all my career years with Java 6. I thought it was the last Java, and later we will switch to Groovy or Scala. I even brought both of them into production!&lt;/p&gt;

&lt;p&gt;This release brought a lot of new improvements in terms of performance, invokedynamic and many other things, but for me, a young developer, it was a disappointment. I expected it to have lambdas. Groovy had them for quite some time already!&lt;/p&gt;

&lt;h2&gt;
  
  
  2014: Java SE 8
&lt;/h2&gt;

&lt;p&gt;My dream came true. They released Lambda support! I immediately stopped using for loops and replaced them with streams. Sometimes it was smart, sometimes it wasn’t. It seems I was not alone: Java 8 is still used heavily in the bigger enterprises. And for those who are not planning the migration to newer JDK versions in the near future,there’s &lt;a href="https://bell-sw.com/libericajdk-performance-edition/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=pasha&amp;amp;utm_content=javabirthday" rel="noopener noreferrer"&gt;Liberica JDK Performance Edition&lt;/a&gt; that couples JDK 8 and JVM 17 and brings the performance of newer Java versions right to your door, almost no code changes required!&lt;/p&gt;

&lt;p&gt;I will name one more thing: DateTime API that looks almost exactly as JodaTime API.&lt;/p&gt;

&lt;h2&gt;
  
  
  2018: Java SE 11
&lt;/h2&gt;

&lt;p&gt;I can call this release the first backward-incompatible release I used. The thing is, some modules were removed and I had to add them as external dependencies. For example, Java EE was not part of JDK anymore, as well as all its annotations, etc. So I had to add them to my already huge pom.xml.&lt;br&gt;
But do you remember that they added CORBA in 1998? It took 20 years to remove it, from Java 1.2 to Java 11! Would be cooler to remove it in version 12, right?&lt;/p&gt;

&lt;p&gt;Java 11 brought many interesting things, for example ZGC and the native HTTP Client. I didn’t need to call new HttpUrlConnection() anymore! Also, they’ve deprecated the JS engine running on Java, called Nashorn. I didn’t mention it and never used it, but the interesting fact is: it was introduced in Java 8! It sounds like the pace really increased!&lt;/p&gt;

&lt;h2&gt;
  
  
  2020: Java SE 15
&lt;/h2&gt;

&lt;p&gt;Nobody noticed this release because at this time, the release cadence was two releases per year. That’s too many releases to catch up with for a mere mortal!&lt;br&gt;
Bit it was an interesting release because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shenandoah was officially introduced;&lt;/li&gt;
&lt;li&gt;Text blocks appeared;&lt;/li&gt;
&lt;li&gt;EdDSA encryption is built into the JDK.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The story goes on
&lt;/h2&gt;

&lt;p&gt;After Java 11, there were two more LTS releases, JDK 17 and 21, and the next LTS release is due this September.&lt;br&gt;
But at some point, I fell in love with Kotlin and Java and myself… We didn’t part ways, but I stopped following its evolution closely. The story doesn’t end, though, and there were many exciting features added to the platform, such as records, virtual threads, pattern matching, etc. And even more novelties are in the makings!&lt;br&gt;
So let’s wish Java Happy Birthday and many years of prosperity ahead!&lt;/p&gt;

</description>
      <category>java</category>
      <category>birthday</category>
      <category>history</category>
      <category>experience</category>
    </item>
  </channel>
</rss>
