<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:social="https://jdriven.com/ns/social">
    <id>https://jdriven.com/blog</id>
    <title>JDriven Blog</title>
    <link href="https://jdriven.com/blog/atom.xml" rel="self" />
    <updated>2026-04-17T04:30:00.000Z</updated>
    <generator uri="https://hexo.io/">Hexo</generator>
    <entry>
        <id>https://jdriven.com/blog/2026/04/Lazygit/</id>
        <title>Lazygit</title>
        <social:hashtags>#Git #Tools #DevOps</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/04/Lazygit/"/>
        <published>2026-04-17T04:30:00.000Z</published>
        <updated>2026-04-17T04:30:00.000Z</updated>
        <author>
            <name>Ronald</name>
            <social:twitter>kqosh1</social:twitter>
            <uri>https://jdriven.com/blog/author/ronald-koster</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Arguably the most popular version control system is Git. Many developers use the command-line version alongside
a Git plugin in their favorite IDE. Now those work just fine, but did you know there is also a very nice
TUI (Text-based User Interface, alternately Terminal User Interface) for Git called &lt;a href=&#34;https://github.com/jesseduffield/lazygit&#34;&gt;Lazygit&lt;/a&gt;.
It is a very cool tool. Read on for more information.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Arguably the most popular version control system is Git. Many developers use the command-line version alongside
a Git plugin in their favorite IDE. Now those work just fine, but did you know there is also a very nice
TUI (Text-based User Interface, alternately Terminal User Interface) for Git called &lt;a href=&#34;https://github.com/jesseduffield/lazygit&#34;&gt;Lazygit&lt;/a&gt;.
It is a very cool tool. Read on for more information.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;How to Start and Install&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After &lt;a href=&#34;https://github.com/jesseduffield/lazygit?tab=readme-ov-file#installation&#34;&gt;installation&lt;/a&gt; it starts really fast,
just type &lt;code&gt;lazygit&lt;/code&gt; in the your terminal in your project directory and you get something like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/04/lazygit_screenshot.png&#34; alt=&#34;lazygit_screenshot.png&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;Figure 1. Lazygit Screenshot&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Usage&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As you can see it gives a very nice overview of your Git workflow and repository status.
And as with any TUI navigating and executing commands can be done extremely quickly and easily using your keyboard only
(see &lt;a href=&#34;https://github.com/jesseduffield/lazygit/tree/master/docs/keybindings&#34;&gt;keybindings&lt;/a&gt;). The keybindings can also
be popped up with the &#39;?&#39; key. On top of that Lazygit also responds to your mouse.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/04/lazygit_keybindings.png&#34; alt=&#34;lazygit_keybindings.png&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;Figure 2. Lazygit Keybindings&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;All in all, it is a very handy tool and a great addition to Git on the command-line and your IDE Git plugin.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="Git" scheme="https://jdriven.com/blog/category/Git/" />
        <category term="Tools" scheme="https://jdriven.com/blog/category/Git/Tools/" />
        <category term="Version Control System" scheme="https://jdriven.com/blog/category/Git/Tools/Version-Control-System/" />
        <category term="DevOps" scheme="https://jdriven.com/blog/category/Git/Tools/Version-Control-System/DevOps/" />
        <category term="Git" scheme="https://jdriven.com/blog/tag/Git/" />
        <category term="Tools" scheme="https://jdriven.com/blog/tag/Tools/" />
        <category term="DevOps" scheme="https://jdriven.com/blog/tag/DevOps/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/04/PKI-in-a-Nutshell/</id>
        <title>PKI in a Nutshell</title>
        <social:hashtags>#Security #PKI #PublicKeyInfrastructure #Certificates #PublicKey #PrivateKey #Keystore #Truststore</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/04/PKI-in-a-Nutshell/"/>
        <published>2026-04-14T05:30:00.000Z</published>
        <updated>2026-04-14T05:30:00.000Z</updated>
        <author>
            <name>Ronald</name>
            <social:twitter>kqosh1</social:twitter>
            <uri>https://jdriven.com/blog/author/ronald-koster</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This blog briefly describes theoretically how Public Key Infrastructure (PKI) works.
It also introduces the key concepts used in PKI.
This is done by describing encryption, decryption, hashing, signing, and authentication using mathematical notations.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This blog briefly describes theoretically how Public Key Infrastructure (PKI) works.
It also introduces the key concepts used in PKI.
This is done by describing encryption, decryption, hashing, signing, and authentication using mathematical notations.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;PKI in a Nutshell&lt;/h3&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Intro&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Given an asymmetric encryption algorithm E and accompanying Decryption algorithm D.
It uses a key pair (&lt;em&gt;K&lt;sub&gt;e&lt;/sub&gt;, K&lt;sub&gt;d&lt;/sub&gt;&lt;/em&gt;) to encrypt a plain text message &lt;em&gt;x&lt;/em&gt; into a cipher text &lt;em&gt;y&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;encryption: E(&lt;em&gt;K&lt;sub&gt;e&lt;/sub&gt;, x&lt;/em&gt;) = &lt;em&gt;y&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;decyption: D(&lt;em&gt;K&lt;sub&gt;d&lt;/sub&gt;, y&lt;/em&gt;) = &lt;em&gt;x&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Alice and Bob&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Suppose Alice and Bob want to exchange encrypted messages with each other, and they also want to sign
the messages so that they can be sure the messages they receive are from each other indeed, and not from some
malignant third party.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Alice owns two key pairs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;keyPairA1 ={&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;, K&lt;sub&gt;A1d&lt;/sub&gt;&lt;/em&gt;}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;keyPairA2 = {&lt;em&gt;K&lt;sub&gt;A2e&lt;/sub&gt;, K&lt;sub&gt;A2d&lt;/sub&gt;&lt;/em&gt;}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Alice publishes the keys &lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt; and &lt;em&gt;K&lt;sub&gt;A2d&lt;/sub&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Bob thas two key pairs as well:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;keyPairB1 = {&lt;em&gt;K&lt;sub&gt;B1e&lt;/sub&gt;, K&lt;sub&gt;B1d&lt;/sub&gt;&lt;/em&gt;}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;keyPairB2 = {&lt;em&gt;K&lt;sub&gt;B2e&lt;/sub&gt;, K&lt;sub&gt;B2d&lt;/sub&gt;&lt;/em&gt;}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And he publishes &lt;em&gt;K&lt;sub&gt;B1e&lt;/sub&gt;&lt;/em&gt; and &lt;em&gt;K&lt;sub&gt;B2d&lt;/sub&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The keys that are published are called &lt;strong&gt;public keys&lt;/strong&gt;, the ones you keep for yourself are called &lt;strong&gt;private keys&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Key pairs are kept in a &lt;strong&gt;keystore&lt;/strong&gt;. Trusted public keys, or the certificates thereof (see below),
are kept in a &lt;strong&gt;truststore&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Encryption&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Bob can now send an encrypted message to Alice using her &lt;strong&gt;public encryption key&lt;/strong&gt; &lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt; with&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;E(&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;, x&lt;/em&gt;) = &lt;em&gt;y&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Which can only be decrypted by Alice with her &lt;strong&gt;private decryption key&lt;/strong&gt; &lt;em&gt;K&lt;sub&gt;A1d&lt;/sub&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;D(&lt;em&gt;K&lt;sub&gt;A1d&lt;/sub&gt;, y&lt;/em&gt;) = &lt;em&gt;x&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And vice versa Alice can send encrypted message to Bob using the public key &lt;em&gt;K&lt;sub&gt;B1e&lt;/sub&gt;&lt;/em&gt; from Bob.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Signing&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Bob can use his &lt;strong&gt;private signing key&lt;/strong&gt; &lt;em&gt;K&lt;sub&gt;B2e&lt;/sub&gt;&lt;/em&gt; to sign his encrypted messages.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Given a hashing algorithm H&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;h&lt;/em&gt; = H(&lt;em&gt;y&lt;/em&gt;) = &lt;em&gt;hash of y&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Next define&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;s&lt;/em&gt; = S(&lt;em&gt;y&lt;/em&gt;) = E(&lt;em&gt;K&lt;sub&gt;B2e&lt;/sub&gt;&lt;/em&gt;, H(&lt;em&gt;y&lt;/em&gt;)) = &lt;em&gt;signature&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Bob sends (&lt;em&gt;y&lt;/em&gt;, &lt;em&gt;s&lt;/em&gt;) to Alice.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Now Alice can verify the cipher text &lt;em&gt;y&lt;/em&gt; is from Bob by applying the verify operation V using Bob&amp;#8217;s &lt;strong&gt;public verification key&lt;/strong&gt; &lt;em&gt;K&lt;sub&gt;B2d&lt;/sub&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;V(&lt;em&gt;s&lt;/em&gt;) = D(&lt;em&gt;K&lt;sub&gt;B2d&lt;/sub&gt;, s&lt;/em&gt;) = &lt;em&gt;h&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And she can also calculate &lt;em&gt;h&lt;/em&gt; herself from the received &lt;em&gt;y&lt;/em&gt;. Both hashes must be equal.
Since only the owner of &lt;em&gt;K&lt;sub&gt;B2e&lt;/sub&gt;&lt;/em&gt; could have sent the signature &lt;em&gt;s&lt;/em&gt; encrypted in this way she knows this
message is from Bob.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;NB The same is true if Bob had signed the plain text &lt;em&gt;x&lt;/em&gt; instead of the cipher text &lt;em&gt;y&lt;/em&gt;. The choice is somewhat arbitrary.
Albeit, that signing the cipher text &lt;em&gt;y&lt;/em&gt; is a bit safer. Because when signing &lt;em&gt;x&lt;/em&gt; and Alice&amp;#8217;s private key has somehow
been acquired by a malignant third party Mallory, Mallory can decrypt &lt;em&gt;y&lt;/em&gt; and reencrypt it with his
own private key and reuse the signature. When &lt;em&gt;y&lt;/em&gt; is signed, he cannot reuse the signature.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Vice versa Alice can sign her message to Bob using her signing key &lt;em&gt;K&lt;sub&gt;A2e&lt;/sub&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Certificates&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;What if Alice and Bob have no secure channel to exchange their public keys? Well suppose they do both have a secure channel with
a third party CA (Certification Authority) whom they trust, which means they trust the CA&amp;#8217;s public verification key &lt;em&gt;K&lt;sub&gt;CAd&lt;/sub&gt;&lt;/em&gt;.
They offer their public keys to CA to have them signed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For example Alice offers her public encryption key to be signed by CA&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;S&lt;sub&gt;CA&lt;/sub&gt;(&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;) = E(&lt;em&gt;K&lt;sub&gt;CAe&lt;/sub&gt;&lt;/em&gt;, H(&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;)) = &lt;em&gt;s&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And Alice publishes&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;c&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt; = {&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;, S&lt;sub&gt;CA&lt;/sub&gt;(&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;)} = {&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;, &lt;em&gt;s&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;} = &lt;em&gt;certificate of K&lt;sub&gt;A1e&lt;/sub&gt; signed by&lt;/em&gt; CA&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Bob can now verify the key is from Alice by calculating&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;V&lt;sub&gt;CA&lt;/sub&gt;(&lt;em&gt;s&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;) = D(&lt;em&gt;K&lt;sub&gt;CAd&lt;/sub&gt;&lt;/em&gt;, &lt;em&gt;s&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;) = &lt;em&gt;h&lt;sub&gt;v&lt;/sub&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;H(&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;) = &lt;em&gt;h&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The following should be true: &lt;em&gt;h&lt;sub&gt;v&lt;/sub&gt;&lt;/em&gt; = &lt;em&gt;h&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The same can be done for the other public keys of Alice and Bob. So by publishing certificates of their
public keys instead of the keys alone they can safely exchange messages without having a secure
channel to exchange their public keys directly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For convenience CA does not publish its verification key alone, but a self-signed certificate of it&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;c&lt;sub&gt;CAd&lt;/sub&gt;&lt;/em&gt; = {&lt;em&gt;K&lt;sub&gt;CAd&lt;/sub&gt;&lt;/em&gt;, S&lt;sub&gt;CA&lt;/sub&gt;(&lt;em&gt;K&lt;sub&gt;CAd&lt;/sub&gt;&lt;/em&gt;)}&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Chain of Trust&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Above is an example of a chain of trust. Alice and Bob trust CA. This a chain of length 2. Longer chains are
more common. For example when Alice and Bob use a certificate from CA&lt;sub&gt;2&lt;/sub&gt;, which is signed by CA&lt;sub&gt;1&lt;/sub&gt;, which is signed by
a self-signed certificate of CA&lt;sub&gt;0&lt;/sub&gt;=CA&lt;sub&gt;root&lt;/sub&gt;. They both trust CA&lt;sub&gt;0&lt;/sub&gt;, and Alice has her keys signed by CA&lt;sub&gt;2&lt;/sub&gt;.
For example the chain for her public encryption key:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;c&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt; = {&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;, &lt;em&gt;s&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;} = {&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;, S&lt;sub&gt;CA2&lt;/sub&gt;(&lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;)}&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;c&lt;sub&gt;CA2d&lt;/sub&gt;&lt;/em&gt; = {&lt;em&gt;K&lt;sub&gt;CA2d&lt;/sub&gt;&lt;/em&gt;, &lt;em&gt;s&lt;sub&gt;CA2d&lt;/sub&gt;&lt;/em&gt;} = {&lt;em&gt;K&lt;sub&gt;CA2d&lt;/sub&gt;&lt;/em&gt;, S&lt;sub&gt;CA1&lt;/sub&gt;(&lt;em&gt;K&lt;sub&gt;CA2d&lt;/sub&gt;&lt;/em&gt;)}&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;c&lt;sub&gt;CA1d&lt;/sub&gt;&lt;/em&gt; = {&lt;em&gt;K&lt;sub&gt;CA1d&lt;/sub&gt;&lt;/em&gt;, &lt;em&gt;s&lt;sub&gt;CA1d&lt;/sub&gt;&lt;/em&gt;} = {&lt;em&gt;K&lt;sub&gt;CA1d&lt;/sub&gt;&lt;/em&gt;, S&lt;sub&gt;CA0&lt;/sub&gt;(&lt;em&gt;K&lt;sub&gt;CA1d&lt;/sub&gt;&lt;/em&gt;)}&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;c&lt;sub&gt;CA0d&lt;/sub&gt;&lt;/em&gt; = {&lt;em&gt;K&lt;sub&gt;CA0d&lt;/sub&gt;&lt;/em&gt;, &lt;em&gt;s&lt;sub&gt;CA0d&lt;/sub&gt;&lt;/em&gt;} = {&lt;em&gt;K&lt;sub&gt;CA0d&lt;/sub&gt;&lt;/em&gt;, S&lt;sub&gt;CA0&lt;/sub&gt;(&lt;em&gt;K&lt;sub&gt;CA0d&lt;/sub&gt;&lt;/em&gt;)}&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;So Bob trusts the public encryption key of Alice, &lt;em&gt;K&lt;sub&gt;A1e&lt;/sub&gt;&lt;/em&gt;, because it is signed by CA&lt;sub&gt;2&lt;/sub&gt;, which is
signed by CA&lt;sub&gt;1&lt;/sub&gt;, which is signed by CA&lt;sub&gt;0&lt;/sub&gt;, which he trusts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;It is common practice to send the entire chain upon publishing/sending of one&amp;#8217;s certificate.
So usually one keeps the entire chain for each certificate in one&amp;#8217;s &lt;strong&gt;keystore&lt;/strong&gt;.
Since the public key is part of the certificate a keystore usually contains
entries with in it a private key and the associated certificate chain.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The certificates one trusts are kept a so called &lt;strong&gt;truststore&lt;/strong&gt;.
The sender&amp;#8217;s chain of certificates must end at a certificate in one&amp;#8217;s truststore.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="PKI" scheme="https://jdriven.com/blog/category/PKI/" />
        <category term="Public Key Infrastructure" scheme="https://jdriven.com/blog/category/PKI/Public-Key-Infrastructure/" />
        <category term="Security" scheme="https://jdriven.com/blog/category/PKI/Public-Key-Infrastructure/Security/" />
        <category term="Certificates" scheme="https://jdriven.com/blog/category/PKI/Public-Key-Infrastructure/Security/Certificates/" />
        <category term="Security" scheme="https://jdriven.com/blog/tag/Security/" />
        <category term="PKI" scheme="https://jdriven.com/blog/tag/PKI/" />
        <category term="Public Key Infrastructure" scheme="https://jdriven.com/blog/tag/Public-Key-Infrastructure/" />
        <category term="Certificates" scheme="https://jdriven.com/blog/tag/Certificates/" />
        <category term="Public Key" scheme="https://jdriven.com/blog/tag/Public-Key/" />
        <category term="Private Key" scheme="https://jdriven.com/blog/tag/Private-Key/" />
        <category term="Keystore" scheme="https://jdriven.com/blog/tag/Keystore/" />
        <category term="Truststore" scheme="https://jdriven.com/blog/tag/Truststore/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/04/Nushell-Niceties-Bumping-Semantic-Version/</id>
        <title>Nushell Niceties: Bumping Semantic Version</title>
        <social:hashtags>#Nushell</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/04/Nushell-Niceties-Bumping-Semantic-Version/"/>
        <published>2026-04-14T04:52:23.000Z</published>
        <updated>2026-04-14T04:52:23.000Z</updated>
        <author>
            <name>mrhaki</name>
            <social:mastodon>mrhaki@mastodon.online</social:mastodon>
            <social:bluesky>mrhaki.com</social:bluesky>
            <uri>https://jdriven.com/blog/author/mrhaki</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In a &lt;a href=&#34;https://jdriven.com/blog/2026/04/Nushell-Niceties-Transform-Values-Into-Semver-Types/&#34;&gt;previous blogpost&lt;/a&gt; you can learn about the &lt;code&gt;semver&lt;/code&gt; command in Nushell to transform a string value into a semver type. With the &lt;code&gt;semver bump&lt;/code&gt; command you can increase one of the components of the semver type. For example to increase the major version part you can use &lt;code&gt;semver bump major&lt;/code&gt;. This command will also update the minor and patch parts if needed. The result is a semver type and you can use &lt;code&gt;into value&lt;/code&gt; to transform it to a string type.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In a &lt;a href=&#34;https://jdriven.com/blog/2026/04/Nushell-Niceties-Transform-Values-Into-Semver-Types/&#34;&gt;previous blogpost&lt;/a&gt; you can learn about the &lt;code&gt;semver&lt;/code&gt; command in Nushell to transform a string value into a semver type. With the &lt;code&gt;semver bump&lt;/code&gt; command you can increase one of the components of the semver type. For example to increase the major version part you can use &lt;code&gt;semver bump major&lt;/code&gt;. This command will also update the minor and patch parts if needed. The result is a semver type and you can use &lt;code&gt;into value&lt;/code&gt; to transform it to a string type.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In the following example several of the &lt;code&gt;semver bump&lt;/code&gt; commands are used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-nu hljs&#34; data-lang=&#34;nu&#34;&gt;use std/assert

# After installing the nu_plugin_semver plugin and
# adding it to the plugin registry you can use
# the plugin for Nushell code.
plugin use semver

# Transform string to semver value.
let version = &#39;4.0.2&#39;

# Bumping major part of version by adding 1 to the current value.
assert equal ($version | semver bump major | into value) &#39;5.0.0&#39;

# Bumping minor part by adding 1 to the value.
# The option `--ignore-errors` makes sure when an error occurs the
# original input value is returned. The shorthand `-i` is also valid.
assert equal ($version | semver bump minor --ignore-errors | into value) &#39;4.1.0&#39;

# Bumping patch part by adding 1 to the value.
assert equal ($version | semver bump patch | into value) &#39;4.0.3&#39;

# Bumping to a release candidate version, patch is unchanged.
assert equal ($version | semver bump rc | into value) &#39;4.0.2-rc.1&#39;

# Bumping to a next alpha version by adding 1 to patch value and
# pre-release part `alpha.1`.
assert equal ($version | semver bump alpha | into value) &#39;4.0.3-alpha.1&#39;

# Bumping to a next beta version by adding 1 to patch value and
# pre-release part `beta.1`.
assert equal ($version | semver bump beta | into value) &#39;4.0.3-beta.1&#39;

# Using `bump release` will remove the pre (release) part of semver value.
assert equal (&#39;4.0.3-rc.1&#39; | semver bump release | into value) &#39;4.0.3&#39;


# The same bump commands can be used for a semver type input.
let full_version = &#39;4.0.2-rc.1+build-20260205&#39; | into semver

assert equal ($full_version | semver bump major | into value) &#39;5.0.0&#39;
assert equal ($full_version | semver bump minor | into value) &#39;4.1.0&#39;
assert equal ($full_version | semver bump patch | into value) &#39;4.0.2+build-20260205&#39;
assert equal ($full_version | semver bump rc | into value) &#39;4.0.2-rc.2+build-20260205&#39;
assert equal ($full_version | semver bump release | into value) &#39;4.0.2+build-20260205&#39;


# The `semver bump` command support the `--build-metadata` (or shorthand `-b`)
# option to add build metadata to a semver or string value.
(assert equal
        (&#39;4.2.8&#39; | semver bump minor --build-metadata &#39;build-20260205&#39; | into value)
        &#39;4.3.0+build-20260205&#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Written with Nushell 0.111.0.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.mrhaki.com/2026/03/nushell-niceties-bumping-semantic.html&#34;&gt;Original post&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</content>
        <category term="Nushell" scheme="https://jdriven.com/blog/category/Nushell/" />
        <category term="Nushell" scheme="https://jdriven.com/blog/tag/Nushell/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/04/Nushell-Niceties-Transform-Values-Into-Semver-Types/</id>
        <title>Nushell Niceties: Transform Values Into Semver Types</title>
        <social:hashtags>#Nushell</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/04/Nushell-Niceties-Transform-Values-Into-Semver-Types/"/>
        <published>2026-04-07T04:45:40.000Z</published>
        <updated>2026-04-07T04:45:40.000Z</updated>
        <author>
            <name>mrhaki</name>
            <social:mastodon>mrhaki@mastodon.online</social:mastodon>
            <social:bluesky>mrhaki.com</social:bluesky>
            <uri>https://jdriven.com/blog/author/mrhaki</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Nushell can be extended with plugins to have more functionality or types that are not part of standard Nushell. If you want to work with string values that are actually semantic version values (&lt;a href=&#34;https://semver.org&#34; class=&#34;bare&#34;&gt;https://semver.org&lt;/a&gt;) you can use the &lt;a href=&#34;https://github.com/abusch/nu_plugin_semver&#34;&gt;Nushell SemVer plugin&lt;/a&gt;. The plugin must be added to Nushell by using the command &lt;code&gt;plugin add &amp;lt;location of plugin&amp;gt;&lt;/code&gt;. You can check with &lt;code&gt;plugin list&lt;/code&gt; command if the plugin is available. This command also shows commands that the plugin adds to Nushell.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The command &lt;code&gt;into semver&lt;/code&gt; can be used to convert string values into a semver type. The semver type has 5 properties: &lt;code&gt;major&lt;/code&gt;, &lt;code&gt;minor&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;pre&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt;. You can use dot notation to get the values for these properties or use the &lt;code&gt;get&lt;/code&gt; command. In the following command a string value is transformed to a semver type: &lt;code&gt;let v = &#39;4.0.2&#39; | into semver&lt;/code&gt;. And with &lt;code&gt;$v.major&lt;/code&gt; you can extract the major part of the semver value and it returns &lt;code&gt;4&lt;/code&gt;.&lt;br&gt;
Records with the keys &lt;code&gt;major&lt;/code&gt;, &lt;code&gt;minor&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;pre&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt; can be transformed to a semver string with the &lt;code&gt;semver from-record&lt;/code&gt; command. And to transform a semver type to a record you can use the command &lt;code&gt;semver into-record&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Nushell can be extended with plugins to have more functionality or types that are not part of standard Nushell. If you want to work with string values that are actually semantic version values (&lt;a href=&#34;https://semver.org&#34; class=&#34;bare&#34;&gt;https://semver.org&lt;/a&gt;) you can use the &lt;a href=&#34;https://github.com/abusch/nu_plugin_semver&#34;&gt;Nushell SemVer plugin&lt;/a&gt;. The plugin must be added to Nushell by using the command &lt;code&gt;plugin add &amp;lt;location of plugin&amp;gt;&lt;/code&gt;. You can check with &lt;code&gt;plugin list&lt;/code&gt; command if the plugin is available. This command also shows commands that the plugin adds to Nushell.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The command &lt;code&gt;into semver&lt;/code&gt; can be used to convert string values into a semver type. The semver type has 5 properties: &lt;code&gt;major&lt;/code&gt;, &lt;code&gt;minor&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;pre&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt;. You can use dot notation to get the values for these properties or use the &lt;code&gt;get&lt;/code&gt; command. In the following command a string value is transformed to a semver type: &lt;code&gt;let v = &#39;4.0.2&#39; | into semver&lt;/code&gt;. And with &lt;code&gt;$v.major&lt;/code&gt; you can extract the major part of the semver value and it returns &lt;code&gt;4&lt;/code&gt;.&lt;br&gt;
Records with the keys &lt;code&gt;major&lt;/code&gt;, &lt;code&gt;minor&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;pre&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt; can be transformed to a semver string with the &lt;code&gt;semver from-record&lt;/code&gt; command. And to transform a semver type to a record you can use the command &lt;code&gt;semver into-record&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In the following example different string values are transformed and from a semver type or record:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-nu hljs&#34; data-lang=&#34;nu&#34;&gt;use std/assert

# After installing the nu_plugin_semver plugin and
# adding it to the plugin registry you can use
# the plugin for Nushell code.
plugin use semver

# With `into semver` you can transform a string
# into a semver value with the fields
# major, minor, patch, pre and build.
let version = &#39;4.0.2&#39; | into semver

assert equal $version.major 4
assert equal $version.minor 0
assert equal $version.patch 2
assert equal ($version | get patch) 2

# Type is semver and no longer a string type.
assert equal ($version | describe) semver

# A full version with all parts.
# The pre (release) part is after the patch and - up until the +.
# The build part is everything after the +.
let full_version = &#39;4.0.2-rc.1+build-20260205&#39; | into semver

assert equal $full_version.major 4
assert equal $full_version.minor 0
assert equal $full_version.patch 2
assert equal $full_version.pre &#39;rc.1&#39;
assert equal $full_version.build &#39;build-20260205&#39;

# A version with major, minor, patch and build part.
let build_version = &#39;4.0.2+build-20260205&#39; | into semver

assert equal $build_version.build &#39;build-20260205&#39;
assert equal $build_version.major 4
assert equal $build_version.minor 0
assert equal $build_version.patch 2


# Using `semver to-record` to transform a semver value or string
# to a record with the keys major, minor, patch, pre and build.
assert equal ($version | semver to-record) {
    major: 4 minor: 0 patch: 2 pre: &#39;&#39; build: &#39;&#39;
}
assert equal (&#39;1.2.3&#39; | semver to-record) {
    major: 1 minor: 2 patch: 3 pre: &#39;&#39; build: &#39;&#39;
}
assert equal (&#39;4.0.2-rc.1+build-20260205&#39; | semver to-record) {
    major: 4 minor: 0 patch: 2 pre: &#39;rc.1&#39; build: &#39;build-20260205&#39;
}

# Using `semver from-record` to transform a record with keys
# major, minor, patch, pre and build to a semver value.
(assert equal
        ({major: 4 minor: 0 patch: 2 pre: &#39;&#39; build: &#39;&#39;} | semver from-record)
        &#39;4.0.2&#39;)
(assert equal
        ({major: 4 minor: 0 patch: 2 pre: &#39;rc.1&#39; build: &#39;build-20260205&#39;} | semver from-record)
        &#39;4.0.2-rc.1+build-20260205&#39;)


# A list of string value can be transformed to a list of semver values.
(assert equal ([&#39;4.0.2-rc.1+build-20260205&#39; &#39;1.2.3&#39;] | into semver)
    [(&#39;4.0.2-rc.1+build-20260205&#39; | into semver) (&#39;1.2.3&#39; | into semver)])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Written with Nushell 0.110.0.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.mrhaki.com/2026/02/nushell-niceties-transform-values-into.html&#34;&gt;Original post&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</content>
        <category term="Nushell" scheme="https://jdriven.com/blog/category/Nushell/" />
        <category term="Nushell" scheme="https://jdriven.com/blog/tag/Nushell/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-3/</id>
        <title>Battle-testing Temporal - Part 3</title>
        <social:hashtags>#Java #DurableExecution #Temporal.io #Poker #Workflow</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-3/"/>
        <published>2026-03-31T20:00:00.000Z</published>
        <updated>2026-03-31T20:00:00.000Z</updated>
        <author>
            <name>Robbert</name>
            <uri>https://jdriven.com/blog/author/robbert</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In part 3 of this series about Temporal it is finally time to explore cross-workflow creation, communication &amp;amp; orchestration.
Upgrading the poker use-case from single-table to supporting multi-table tournaments of arbitrary size.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Diving into:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;How to spawn child workflows and their intended behavior&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using signals to communicate between workflows&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set up a Tournament workflow orchestrating Table workflows&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;(&lt;a href=&#34;https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-2&#34;&gt;See here for part 2&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In part 3 of this series about Temporal it is finally time to explore cross-workflow creation, communication &amp;amp; orchestration.
Upgrading the poker use-case from single-table to supporting multi-table tournaments of arbitrary size.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Diving into:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;How to spawn child workflows and their intended behavior&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using signals to communicate between workflows&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set up a Tournament workflow orchestrating Table workflows&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;(&lt;a href=&#34;https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-2&#34;&gt;See here for part 2&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Spawning Child workflows&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;From inside any workflow it is possible to create and start child workflows.
This is done by creating a specific type of workflow stub using &lt;code&gt;Workflow.newChildWorkflowStub&lt;/code&gt;, which takes a slightly augmented set of options.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;String tableWorkflowId = String.format(&#34;%s-%d&#34;, tournamentId, tableNumber);
ChildWorkflowOptions options = ChildWorkflowOptions.newBuilder()
        .setTaskQueue(&#34;all_tables&#34;)
        .setWorkflowId(tableWorkflowId)
        .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON) // specific for childs
        .build();
TableWorkflow tableWorkflow = Workflow.newChildWorkflowStub(TableWorkflow.class, options);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;While &lt;code&gt;PARENT_CLOSE_POLICY_ABANDON&lt;/code&gt; sounds ominous, what it means is that when the workflow that started the child workflow is done, the child workflow will be left alone and thus &lt;strong&gt;continues to run&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Other options would be to directly terminate it or request a cancellation upon completion of the parent workflow.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The &lt;code&gt;tableWorkflow&lt;/code&gt; represents the stub, but just like before its &lt;code&gt;workflow&lt;/code&gt; method still need to be called to actually start.
From inside a workflow you do not need a &lt;code&gt;WorkflowClient&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Instead, you have the option to directly use the stub, synchronously awaiting its method completion or using the &lt;code&gt;Async&lt;/code&gt; class for asynchronous execution.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;TableState tableState = TableState.fromInitial(...); // the workflow input

// this calls and blocks until the child is done.
tableWorkflow.workflow(tableState);

// this calls it asynchronously
Async.procedure(tableWorkflow::workflow, tableState);

// returns a promise (useful for non-void workflow methods)
Promise&amp;lt;...&amp;gt; resultPromise = Async.function(tableWorkflow::workflow, tableState);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Clearly for the tournament/table use-case I want to start multiple table workflows asynchronously.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Acquiring long-term stubs&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A bit quirky perhaps, but it seems &lt;code&gt;Workflow.newChildWorkflowStub&lt;/code&gt; provides a stub to the current first &lt;em&gt;Run&lt;/em&gt; of the child workflow.
Meaning that whenever a table &lt;em&gt;continues as new&lt;/em&gt;, the stub is not suitable to send signals.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Therefore, the tournament workflow uses &lt;code&gt;Workflow.newExternalWorkflowStub&lt;/code&gt; directly after starting the child workflows to actually acquire stubs that survive child workflows moving to new &lt;em&gt;Runs&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Which is basically just a &lt;em&gt;get stub by workflow id&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;newExternalWorkflowStub&lt;/code&gt; can also be used to acquire stubs to any other workflow from inside workflows.
I use this to signal the parent tournament workflow from inside the table workflows as well.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Sending signals between workflows&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;There isn’t much to it when it comes to sending a signal, once you have a stub ready.
You can just call any &lt;code&gt;@SignalMethod&lt;/code&gt; directly on the stub.
With signals being meant to be fast in terms of delivery as well as execution there is no need for asynchronous behavior.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As mentioned before, a workflow process will not even yield when calling a signal method like it does for calling activities. Effectively making it behave like any other plain old java method call.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;The tournament/table signals&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Having built this use-case before, I already had a pretty good idea of how the flow should look like.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;E.g. supporting:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;slowly closing of tables as players go bust&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tables dealing with new incoming players&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ranking players across all tables&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Matching it to signal-based parent/child workflow communication felt most natural and turned out to be pretty straight-forward.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The following diagram provides an overview of both the signals between the workflows and the actions (sent as updates) from the client. Depicting a 20-player tournament, but the flow remains the same for any amount of players/tables.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/03/cross-wf-signals.png&#34; alt=&#34;Continued workflow&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;Figure 1. The signals/updates between the components&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Once enough players have been ranked (lost their chips), a table can be stopped and once it&amp;#8217;s closed any remaining players are moved to other tables.
Repeating this process until the last table is closed.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Ranking of players&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Zooming in on the code of an incoming signal, in this case the ranking of a player.
As signaled from a table workflow.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Note that I followed the practice of capturing signals in queues and having the workflow methods itself deal with the work at the appropriate time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;private final Deque&amp;lt;PlayerToRank&amp;gt; playersToRank = new ArrayDeque&amp;lt;&amp;gt;();

@Override
public void onRankPlayer(PlayerToRank ptr) {
    playersToRank.add(ptr);
}

@Override
public void workflow(TournamentState tournamentState) {
    ...
    while (!playersToRank.isEmpty()) {
        // using the signal receive order as ranking order (counting down)
        rankPlayer(playersToRank.pop());
    }
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Tournament Workflow&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Looking a bit deeper into the workflow method of the new Tournament workflow.
It is basically responsible for handling the incoming signals from the table workflows when started and &lt;em&gt;Buyins&lt;/em&gt; from players before the tournament has started. (Not supporting Rebuys)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;It has therefore two specific states, &lt;strong&gt;&lt;em&gt;waiting&lt;/em&gt;&lt;/strong&gt; upon start and &lt;strong&gt;&lt;em&gt;started&lt;/em&gt;&lt;/strong&gt;. Each state supporting distinctly different signals/updates.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I choose to directly reflect this into the basic structure of the code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@Override
public void workflow(TournamentState tournamentState) {
    state = tournamentState;

    while (state.getStatus() == WAITING) {
        Workflow.await(() -&amp;gt; incomingWaitingWork());

        while (!playersToAdd.isEmpty()) {
            addPlayerToTournament(playersToAdd.pop());
        }

        if (state.seatsFilled()) {
            startTournament(); // starts the tables as child workflows
        }
    }

    acquireTableWorkflows(); // get the long-term stubs to tables

    while (state.getStatus() == STARTED) {
        Workflow.await(() -&amp;gt; state.getStatus() == DONE || incomingStartedWork());

        while (!playersToRank.isEmpty()) {
            rankPlayer(playersToRank.pop());
        }
        while (!closedTables.isEmpty()) {
            handleClosedTable(closedTables.pop());
        }
    }

    Workflow.await(Workflow::isEveryHandlerFinished);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Obviously the client and worker registration should be updated as well to make the whole thing work again, but it is basically just more of the same seen earlier.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Starting a tournament instead of directly starting a table from the client.
Acquiring stubs to all tables once the tournament has started:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;if (tournamentEvent instanceof TournamentStarted) {
    int tables = ((TournamentStarted) tournamentEvent).tables();
    List&amp;lt;TableWorkflow&amp;gt; tableWorkflows = IntStream.rangeClosed(1, tables)
            .mapToObj(tableId -&amp;gt; client.newWorkflowStub(TableWorkflow.class,
                    String.format(&#34;%s-%s&#34;, tournamentId, tableId)))
            .toList();
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Temporal Child Workflow support&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Apart from visualizing the signals/updates and activities, looking at the history dashboard of a workflow also shows when it spawns a child workflow.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Also showing their status and running time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/03/child_workflows.png&#34; alt=&#34;Child workflows&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;Figure 2. Tournament workflow with one last table active&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Learnings so far&lt;/h3&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;Applying the program flow from a previous event-based iteration of the logic to the temporal based workflow implementation works pretty much 1-to-1. Migration to fast queueing signal handlers again improving overall quality of the code in the process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Signals are guaranteed to be fed one by one to the workflow in the order they are received and registered into the history. Being used to kafka, this again maps well to my own default metal model.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The visual inspecting a workflow using the Temporal dashboard is quite handy when it comes to debugging and manually confirming correctness of a workflow execution.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Next up&amp;#8230;&amp;#8203;&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Stay tuned for more to come as it is time, overdue even, to explore (unit)testing Temporal workflows as well as the first steps towards account management and financial processes like &lt;em&gt;Deposit&lt;/em&gt;, &lt;em&gt;Buy-in&lt;/em&gt; and &lt;em&gt;Payout&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="Java" scheme="https://jdriven.com/blog/tag/Java/" />
        <category term="Durable Execution" scheme="https://jdriven.com/blog/tag/Durable-Execution/" />
        <category term="Temporal.io" scheme="https://jdriven.com/blog/tag/Temporal-io/" />
        <category term="Poker" scheme="https://jdriven.com/blog/tag/Poker/" />
        <category term="Workflow" scheme="https://jdriven.com/blog/tag/Workflow/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-2/</id>
        <title>Battle-testing Temporal - Part 2</title>
        <social:hashtags>#Java #DurableExecution #Temporal.io #Poker #Workflow</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-2/"/>
        <published>2026-03-26T21:00:00.000Z</published>
        <updated>2026-03-26T21:00:00.000Z</updated>
        <author>
            <name>Robbert</name>
            <uri>https://jdriven.com/blog/author/robbert</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In part 2 of this series about Temporal I want to dive deeper into some of the details involved in making workflows actually run and run properly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Diving into:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;Starting and communicating with a Workflow&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Supporting replay, continueAsNew and early signals&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adding an Activity and hooking it all up together&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;(&lt;a href=&#34;https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-1&#34;&gt;See here for part 1&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In part 2 of this series about Temporal I want to dive deeper into some of the details involved in making workflows actually run and run properly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Diving into:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;Starting and communicating with a Workflow&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Supporting replay, continueAsNew and early signals&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adding an Activity and hooking it all up together&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;(&lt;a href=&#34;https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-1&#34;&gt;See here for part 1&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Starting and communicating with a Workflow&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Now that I&amp;#8217;ve shown a bit of the basics of how workflow code could look like, it&amp;#8217;s time to look at actual code instead of pseudocode before diving deeper.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The most basic version of the workflow looks like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@WorkflowInterface
public interface TableWorkflow {
    @WorkflowMethod
    void workflow(InitialTable initialTable);
    @UpdateValidatorMethod(updateName = &#34;onCheck&#34;)
    void validateCheck(Check check);
    @UpdateMethod
    void onCheck(Check check);
    ...
}

public class TableHandler implements TableWorkflow {
    private TableState state;
    @Override
    public void workflow(InitialTable initialTable) {
        state = TableState.fromInitial(initialTable);
        while (state.getStatus() != FINISHED) {
            // do stuff
        }
    }
    @Override
    public void validateCheck(Check check) {
        // see part 1
    }
    @Override
    public void onCheck(Check check) {
        // see part 1
    }
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As you can see all the Temporal annotations are added to a separate &lt;code&gt;interface&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In order to actually start a workflow you need two things:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;a &lt;code&gt;Worker&lt;/code&gt; that registers itself as able to run a particular workflow implementation &lt;code&gt;class&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a &lt;code&gt;WorkflowClient&lt;/code&gt; to tell Temporal to start the actual workflow using the &lt;code&gt;interface&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Registering the worker&lt;/h4&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;void startWorker() {
    WorkflowServiceStubs serviceStubs = WorkflowServiceStubs.newLocalServiceStubs();
    WorkflowClient client = WorkflowClient.newInstance(serviceStubs);
    WorkerFactory factory = WorkerFactory.newInstance(client);
    Worker worker = factory.newWorker(&#34;all_tables&#34;); // &amp;lt;--- the task queue
    worker.registerWorkflowImplementationTypes(TableHandler.class); // &amp;lt;-- the workflow implementation
    factory.start();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As you can see its quite flexible, with a worker being able to specify any number of specific workflows that it is able to run. Furthermore, every worker must also specify a &lt;em&gt;task queue&lt;/em&gt;, allowing for specific workers to work only for subsets of workflows of the specified workflow type(s). Effectively able to divide work in any number of different ways.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Creating a client and starting a workflow&lt;/h4&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;void startWorkflow(String workflowId) {
    WorkflowServiceStubs serviceStubs = WorkflowServiceStubs.newLocalServiceStubs();
    WorkflowClient client = WorkflowClient.newInstance(serviceStubs);
    WorkflowOptions options = WorkflowOptions.newBuilder()
            .setTaskQueue(&#34;all_tables&#34;) // same task queue
            .setWorkflowId(workflowId)
            .build();

    TableWorkflow tableWorkflow = client.newWorkflowStub(TableWorkflow.class, options);
    InitialTable initialTable = new InitialTable(...)
    WorkflowClient.start(tableWorkflow::workflow, initialTable);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As you can see, you first create a WorkflowClient. Then you request a stub of a certain workflow interface type, specifying any details in the options.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;the stub can then be used inside the &lt;code&gt;WorkflowClient.start&lt;/code&gt; method to provide a method reference and the appropriate amount of input variables. In an indirect, but fully type-safe way.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Note that apart from &lt;code&gt;newWorkflowStub&lt;/code&gt; the following flavors also exist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;newExternalWorkflowStub&lt;/code&gt; to communicate to other workflows from inside workflows.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;newChildWorkflowStub&lt;/code&gt; to start a workflow as a child from within another workflow.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Supporting replay, continueAsNew and early signals&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With the basic real-code example laid-out, a few more tweaks are needed to properly support restarting of the workflow.
Restarting either as result of a failure or as part of the best-practice to restart the workflow from its current state as checkpoint.
To clean up a history that is reaching its size limits.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As with other event-sourced systems, when event history grows too large, non-functional issues start to arise. So it&amp;#8217;s best to start from a fresh checkpoint once every while.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Again, the framework gives the user full control over where and when to do this. Even so, there is no way to prevent &lt;em&gt;early signals&lt;/em&gt;. Which I noticed specifically happening around replays of workflows. Signals may in fact be fed to a workflow before its &lt;code&gt;@WorkflowMethod&lt;/code&gt; has been called. Meaning in this case, &lt;strong&gt;the &lt;code&gt;state&lt;/code&gt; variable may still be &lt;code&gt;null&lt;/code&gt; when inside a @SignalMethod or @UpdateMethod.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;So three tweaks are needed:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;changing the workflow method to directly accept the TableState in any form&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;triggering &lt;code&gt;continueAsNew&lt;/code&gt; when history becomes to large&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;dealing with &lt;code&gt;state&lt;/code&gt; in @SignalMethod or @UpdateMethod&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;public class TableHandler implements TableWorkflow {
    private TableState state;

    @WorkflowInit
    public TableHandler(TableState tableState) { &lt;i class=&#34;conum&#34; data-value=&#34;1&#34;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
        this.state = tableState; &lt;i class=&#34;conum&#34; data-value=&#34;3&#34;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
    }

    @Override
    public void workflow(TableState tableState) { &lt;i class=&#34;conum&#34; data-value=&#34;1&#34;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
        while (state.getStatus() != FINISHED) {
            // do stuff

            if (Workflow.getInfo().getHistoryLength() &amp;gt;= 5000) {
                // make sure other methods finished completely
                Workflow.await(Workflow::isEveryHandlerFinished);
                Workflow.continueAsNew(state); &lt;i class=&#34;conum&#34; data-value=&#34;2&#34;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
                return; // immediately stop this workflow instance
            }
        }
    }
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&#34;not relying on &lt;code&gt;state&lt;/code&gt; in @UpdateMethod&#34; proved to be effectively impossible, not being able to validate the user input without knowledge of the state. Conveniently Temporal provides the &lt;code&gt;@WorkflowInit&lt;/code&gt; annotation which you can put on the constructor. Effectively switching to the more typical constructor-based state injection to help with simple state always being available.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;admonitionblock warning&#34;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&#34;icon&#34;&gt;
&lt;i class=&#34;fa icon-warning&#34; title=&#34;Warning&#34;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&#34;content&#34;&gt;
You can only run simple non-blocking logic in constructors
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Its good form to use &lt;code&gt;Workflow.await(Workflow::isEveryHandlerFinished)&lt;/code&gt; before signaling to Temporal the workflow should be started fresh from a particular state with &lt;code&gt;Workflow.continueAsNew(state)&lt;/code&gt;.
This is because Temporal will create a new instance of the workflow class providing the given &lt;code&gt;state&lt;/code&gt; as input of the &lt;code&gt;workflow&lt;/code&gt; method.
But any &lt;code&gt;@UpdateMethod&lt;/code&gt; that was somehow interleaved half-way and is still not fully finished at this point, will likely be problematic.
Therefore, it&amp;#8217;s best to use the specific &lt;code&gt;await&lt;/code&gt; condition for this.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;admonitionblock tip&#34;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&#34;icon&#34;&gt;
&lt;i class=&#34;fa icon-tip&#34; title=&#34;Tip&#34;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&#34;content&#34;&gt;
By keeping all &lt;code&gt;@UpdateMethod&lt;/code&gt; free from activities/awaits and just queueing work, no interleaving can happen.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The theoretical maximum history length to check for depends highly on the amount of bytes per recorded event.
The default is maximum size 50MB for the entire history. Meaning small events will take you further than large events (and so will binary encoding over the JSON default).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;admonitionblock tip&#34;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&#34;icon&#34;&gt;
&lt;i class=&#34;fa icon-tip&#34; title=&#34;Tip&#34;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&#34;content&#34;&gt;
I would recommend using your personal use-case and user-experience with the Temporal Dashboard to guide you to a history size threshold.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph lead&#34;&gt;
&lt;p&gt;Are we hardened yet?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;To the best of my knowledge, this workflow is now fully ready to handle (deliberate) restarts and weird edge-cases.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Continued workflows&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;When looking that the dashboard you can spot continued workflows quite easily:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/03/continued_workflows.png&#34; alt=&#34;Continued workflow&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;Figure 1. The same workflow continued into a new &lt;strong&gt;Run&lt;/strong&gt; two times&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As you can see, each time it continues from a certain state (checkpoint) a new run is started using a unique &lt;strong&gt;Run ID&lt;/strong&gt;. The concept of a &lt;em&gt;Run&lt;/em&gt; is mostly relevant in the context of a client that can either point to the workflow (automatically the active run), or point to a specific &lt;strong&gt;Run ID&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Replayed workflows&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A workflow that failed or otherwise got killed half-way completion, will automatically be brought back online by the framework whenever a suitable worker is active (again).
This is done entirely based on event-sourcing, with the framework restarting the workflow from its initial state following by feeding it all recorded signals.
Where applicable during its re-execution instantly returning previously recorded values of things like &lt;code&gt;Workflow.newRandom()&lt;/code&gt; or results of recorded calls to activities.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The workflow itself being oblivious to whether or not its being replayed or at what point normal progression resumes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Idiomatically a workflow that is restarted with a replay also doesn&amp;#8217;t count as a new &lt;em&gt;Run&lt;/em&gt;, but rather as a continuation of the same run. With the dashboard only showing actual workflow-level errors if any. E.g. stopping and starting a worker mid-process merely resulting in a visible time-gap of progress being made.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/03/workflow_timegap.png&#34; alt=&#34;Continued workflow&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;Figure 2. A workflow with the worker stopped and started mid-way&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Adding the Activity into the mix&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;While the above example code is now presumed to be properly written, it is not showing the Activity to push events to clients yet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Using an &lt;code&gt;Activity&lt;/code&gt; looks somewhat the same as acquiring a stub to a &lt;code&gt;Workflow&lt;/code&gt;, but with specific options to deal with timeouts and retries.
With each call persisted in history by default, this is where Temporal shines.
&lt;strong&gt;Able to keep retrying an activity until it succeeds, even across restarts of the workflow itself for as long as you specify.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;public class TableHandler implements TableWorkflow {
    private final TableEventPublisher eventPublisher = Workflow
            .newActivityStub(TableEventPublisher.class, ActivityOptions.newBuilder()
                    .setStartToCloseTimeout(ofSeconds(10))
                    .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(3).build())
                    .build());

    private void putBlinds() {
        // uses the activity to create a side effect (in this case publishing an event)
        eventPublisher.blindsPut(new BlindsPut(...));
        // create some delay before dealing the cards
        Workflow.sleep(Duration.ofSeconds(2)); // &amp;lt;-- returns instantly during replay
    }
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My minimal activity implementation looks like this for now&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@ActivityInterface
public interface TableEventPublisher {
    void blindsPut(BlindsPut blindsPut);
    ...
}
// local-jvm implementation that just puts incoming events on an internal queue
public class QueueTableEventPublisher implements TableEventPublisher {
    private final BlockingDeque&amp;lt;TableEvent&amp;gt; queue = new LinkedBlockingDeque&amp;lt;&amp;gt;();

    @Override
    public void blindsPut(BlindsPut blindsPut) {
        queue.add(blindsPut);
    }

    // ... other specific event methods

    public TableEvent nextEvent() throws InterruptedException {
        return queue.take();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Now, just like for the workflow, the activity needs to be registered by a worker.
Unlike for workflows, not the implementation &lt;code&gt;class&lt;/code&gt; but a specific &lt;em&gt;object&lt;/em&gt; needs to be registered. Allowing for easy integration with existing externally managed beans etc. and easy testing!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;QueueTableEventPublisher tableEventPublisher = new QueueTableEventPublisher();
worker.registerActivitiesImplementations(tableEventPublisher);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Hooking it all together, with a local dummy-client implementation listening to the published events and sending player actions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;public class PokerServer {
    void main() {
        QueueTableEventPublisher tableEventPublisher = new QueueTableEventPublisher();
        String workflowId = UUID.randomUUID().toString();
        startWorker(tableEventPublisher); // see earlier example
        startWorkflow(workflowId);        // see earlier example
        startClient(tableEventPublisher, workflowId);
    }

    void startClient(QueueTableEventPublisher eventPublisher, String workflowId) {
        WorkflowServiceStubs serviceStubs = WorkflowServiceStubs.newLocalServiceStubs();
        WorkflowClient client = WorkflowClient.newInstance(serviceStubs);
        TableWorkflow tableWorkflow = client.newWorkflowStub(TableWorkflow.class, workflowId);

        while (true) {
            // getting next event
            TableEvent tableEvent = tableEventPublisher.nextEvent();

            // sending a player action
            tableWorkflow.onCheck(new Check(...));
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Learnings so far&lt;/h3&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;Honestly, the &lt;em&gt;early signals&lt;/em&gt; caught me off-guard and I had to double-check if this was indeed intended behavior of the framework, but it is. Choosing not to lose signals in edge-cases over what to me would appear to be a much friendlier default programming model of guaranteed availability of workflow input. Good to learn that they solved this by adding &lt;code&gt;@WorkflowInit&lt;/code&gt; which provides exactly that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Support for checkpointing by &lt;code&gt;Workflow.continueAsNew&lt;/code&gt; is straight forward, and it&amp;#8217;s quite nice that you can pick any point in the workflow. From history size based to the &lt;em&gt;raising of the blinds&lt;/em&gt; or something else that makes more sense to a specific use-case.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The choice to use &lt;code&gt;interface&lt;/code&gt; and &lt;code&gt;class&lt;/code&gt; for Workflows but an &lt;em&gt;object&lt;/em&gt; for Activities seems spot on. Making it a breeze to bring in anything from highly dependency-managed implementations to mocks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Next up&amp;#8230;&amp;#8203;&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Stay tuned for more to come as the code is ready for the next architectural step into multi-table tournaments with a tournament workflow orchestrating its per-table workflows as its &lt;code&gt;ChildWorkflows&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;(&lt;a href=&#34;https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-3&#34;&gt;See here for part 3&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="Java" scheme="https://jdriven.com/blog/tag/Java/" />
        <category term="Durable Execution" scheme="https://jdriven.com/blog/tag/Durable-Execution/" />
        <category term="Temporal.io" scheme="https://jdriven.com/blog/tag/Temporal-io/" />
        <category term="Poker" scheme="https://jdriven.com/blog/tag/Poker/" />
        <category term="Workflow" scheme="https://jdriven.com/blog/tag/Workflow/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-1/</id>
        <title>Battle-testing Temporal - Part 1</title>
        <social:hashtags>#Java #DurableExecution #Temporal.io #Poker #Workflow</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-1/"/>
        <published>2026-03-24T21:00:00.000Z</published>
        <updated>2026-03-24T21:00:00.000Z</updated>
        <author>
            <name>Robbert</name>
            <uri>https://jdriven.com/blog/author/robbert</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://temporal.io&#34;&gt;Temporal&lt;/a&gt; is gaining traction spearheading a new generation of Workflow tools/frameworks.
Namely &lt;code&gt;Durable Execution&lt;/code&gt; frameworks. Unlike most Workflow tools/frameworks, &lt;code&gt;Durable Execution&lt;/code&gt; frameworks take a code-centric approach (no external models) meant to assist the developer in building complex Workflows (Sagas) that can take anywhere from seconds to months. There is no limit.
Promising out-of-the-box support for (re)starting Workflows from exactly where they were (upon failure) based on managed event-sourced state management.
Said to support a wide-range of use-cases from multistep financial processes to ML-pipelines in the wild.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;From a 1000-feet view it seems like a &lt;code&gt;Durable Execution&lt;/code&gt; framework like Temporal could indeed be the first developer-friendly generation of Workflow engines to assist a wide variety of long-running processes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;So join me on this journey and let&amp;#8217;s find out if it is truly possible to write production-grade long-running processes in a &lt;strong&gt;non-evasive developer-friendly&lt;/strong&gt; manner by asking my favorite architecture question&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Will it Poker?&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://temporal.io&#34;&gt;Temporal&lt;/a&gt; is gaining traction spearheading a new generation of Workflow tools/frameworks.
Namely &lt;code&gt;Durable Execution&lt;/code&gt; frameworks. Unlike most Workflow tools/frameworks, &lt;code&gt;Durable Execution&lt;/code&gt; frameworks take a code-centric approach (no external models) meant to assist the developer in building complex Workflows (Sagas) that can take anywhere from seconds to months. There is no limit.
Promising out-of-the-box support for (re)starting Workflows from exactly where they were (upon failure) based on managed event-sourced state management.
Said to support a wide-range of use-cases from multistep financial processes to ML-pipelines in the wild.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;From a 1000-feet view it seems like a &lt;code&gt;Durable Execution&lt;/code&gt; framework like Temporal could indeed be the first developer-friendly generation of Workflow engines to assist a wide variety of long-running processes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;So join me on this journey and let&amp;#8217;s find out if it is truly possible to write production-grade long-running processes in a &lt;strong&gt;non-evasive developer-friendly&lt;/strong&gt; manner by asking my favorite architecture question&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Will it Poker?&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;In more words&lt;/em&gt;, does the architecture withstand all the (non-)functional requirements that stem from supporting a fully-fledged server poker gaming server capable of supporting arbitrary amounts of poker tournaments, each supporting arbitrary amounts of players.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Dealing with:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Transferring of funds from/to player accounts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Coordination between multiple tables (games) part of the same tournament&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Server initiated actions/timeouts like dealing cards and default player actions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stateful behavior that needs to survive all sorts of failures&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Usage of randomization to shuffle decks of cards&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;High traceability &amp;amp; observability requirements&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Low-latency fan-out of updates to clients&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clients with incentive to cheat/break the system&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Turning it into my go-to proof-of-concept use case.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;admonitionblock warning&#34;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&#34;icon&#34;&gt;
&lt;i class=&#34;fa icon-warning&#34; title=&#34;Warning&#34;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&#34;content&#34;&gt;
&lt;em&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; this is not &#34;you should build game servers like this&#34;, nor a &#34;you should build game servers with Temporal&#34; blog.&lt;/em&gt;
&lt;em&gt;It is merely blog series about testing if Temporal is up to the task for such complex use-case, highlighting which tradeoffs/benefits it brings.&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph lead&#34;&gt;
&lt;p&gt;Skipping the plumping code to set up a workflow, I want to focus in this part 1 on the actual business logic instead.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2&gt;DIY Approach - what I had to start with&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The following pseudocode provides the basic generalized set-up of the code for a turn-based (Poker table) game server.
While it has nothing to do with Temporal, it provides a baseline for the code before adding Temporal into the mix.
Feel free to laugh at the semi-intentional naive approach ;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;synchronized void runGame(GameState state) {
    while(!state.done) {
        makeProgessIfPossible();
        wait(playerActionTimeout); // Object.wait
        if (!actionTaken) {
            playForCurrentPlayer();
            turnToNextPlayer();
        }
        this.actionTaken = false;
        persistState(state);
    }
}

synchronized void playerAction(Action action) {
    if (legalAction(action)) {
        doAction(action);
        this.actionTaken = true;
        turnToNextPlayer();
        persistState(state);
        notifyAll(); //Object.notifyAll
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;how this patterns works&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For those not familiar with plain old Java object locking,this works because the &lt;code&gt;Object.wait(playerActionTimeout)&lt;/code&gt; method yields its thread and releases its lock on the gameserver object such that another thread (of the player) may execute the &lt;code&gt;playerAction&lt;/code&gt; within the specified timeout.
With the &lt;code&gt;Object.notifyAll()&lt;/code&gt; call explicitly notifying waiting threads to be woken up out their waiting state.
(So that &lt;code&gt;Object.wait&lt;/code&gt; never waits longer then needed)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;the rough part&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In order to properly re-start upon failure, not only does such server code require carefully self-managed
persisted state management, it also requires some sort of (distributed) locking mechanism to make
sure exactly one server is running/restarting the game at any given point in time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;When bringing observability, error handling, test support, versioning and transactionality into the mix;
depending on the exact use-case, building such layer yourself is no small feat.
I know this because I built one myself once before. Using the same use-case, as part of another architectural exploration of Message-Driven Moduliths based on Kafka.
While great as a feasibility study for such architecture, I would recommend against rolling your own framework for any professional use-case.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2&gt;Enter - Temporal&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;So, could &lt;a href=&#34;https://temporal.io&#34;&gt;Temporal&lt;/a&gt; be such framework? Doing the heavy lifting while keeping my own code simple?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And how would vanilla Java code look like when adapted to a Temporal &lt;code&gt;Workflow&lt;/code&gt; that supports &lt;em&gt;durable execution&lt;/em&gt;?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Directly translated to Temporal&lt;/h3&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@WorkflowMethod
void runGame(GameState state) {
    while (!state.done) {
        makeProgessIfPossible();
        if (!Workflow.await(playerActionTimeout, () -&amp;gt; this.actionTaken)) {
            playDefaultActionForCurrentPlayer();
            turnToNextPlayer();
        }
    }
}

@SignalMethod
void playerAction(Action action) {
    if (legalAction(action)) {
        doAction(action);
        this.actionTaken = true;
        turnToNextPlayer();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The first thing to notice is how little actually had to change to run. The wait/notify has been replaced by the &lt;code&gt;Workflow.await(timeout, conditionFunction)&lt;/code&gt; awaiting a specific condition.
&lt;code&gt;synchronized&lt;/code&gt; is no longer needed because Temporal will never ever have multiple threads active in the same workflow at the same time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Manually persisting the state is also gone, as Temporal will replay all events (signals) upon restart if needed
to re-reach the same internal game state automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In order to update any externally running process, some state change needs to be reported. Temporal provides 4 options for this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Calling another Workflow&amp;#8217;s &lt;code&gt;@SignalMethod&lt;/code&gt; (to update another workflow)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Implementing &lt;code&gt;@QueryMethod&lt;/code&gt; for fast retrieval of some current state&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pushing state out by means of calling an &lt;code&gt;ActivityInterface&lt;/code&gt; that would store/push state to somewhere&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New since 2025: Implementing an &lt;code&gt;@UpdateMethod&lt;/code&gt; that is able to &lt;strong&gt;process data and send back a result, just like the primary workflow method&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;While the latest one is obviously very powerful, I felt it would introduce a lot of changes to the Workflow as well my envisioned architecture.
Which at this point is assumed to look like something this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/03/single_table_arch.png&#34; alt=&#34;Event History&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;Figure 1. Initial Architecture&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Specifically sending Actions to the workflow and fanning out results from different components.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Therefore, I opted to stay close to the baseline code and keep sending out state changes as events by using an Activity from anywhere in the code that had a meaningful change to covey to the clients.
Not caring too much about latency for now. Properly battle-testing high-frequency use of Activities in the process.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Why the above code-pattern is not correct anymore&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The direct translation from the object lock-based approach to a Workflow introduces one small but vital difference in the execution ordering of the code!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In Temporal, Activities are recorded in history first and then scheduled for execution asynchronously.
As a result of this, &lt;strong&gt;calling an activity (even a &lt;code&gt;void&lt;/code&gt; method) triggers a yield of the thread/process that currently executing the workflow method/signal handler that called it.&lt;/strong&gt;
Meaning its flow is potentially interleaved with (parts of) one or more other @SignalMethod/@WorkflowMethod methods before continuing from where it called the activity.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@WorkflowMethod
void runGame(GameState state) {
    someActivity.doSomething(); // activities yield just like Workflow.await...
    // &amp;lt;-- other workflow code may execute in between!
    somethingElse();
    // &amp;lt;-- no other workflow code can run in between
    somethingElse();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Coming from a baseline that did not expect any interleaving except on explicit calls to &lt;code&gt;Object.wait&lt;/code&gt; (now &lt;code&gt;Workflow.await&lt;/code&gt;) calls, this causes subtle issues resulting from out-of-order program flow.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Idiomatic Temporal workflow pattern&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Because of the plethora of issues that may arise from executing complex logic from multiple entrypoints, the general advice seems to keep &lt;code&gt;@SignalMethod&lt;/code&gt; methods dumb.
Instead of fully processing a signal itself, storing any incoming work as a result of a signal in some form of queue.
Leaving it up to the sole &lt;code&gt;@WorkflowMethod&lt;/code&gt; to actually process.
(To me this made quite a bit of sense, which is another reason not to dive into &lt;code&gt;@UpdateMethod&lt;/code&gt; based state sharing approach for now.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;When applied to the example above, this translates to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@WorkflowMethod
void runGame(GameState state) {
    while(!state.done) {
        makeProgessIfPossible();
        Action action;
        if (Workflow.await(playerActionTimeout, () -&amp;gt; this.action != null)) {
            action = this.action;
        } else {
            action = defaultActionForCurrentPlayer();
        }
        doAndClearAction(action); // sets state.action back to null
    }
}

@SignalMethod
void playerAction(Action action) {
    if (this.action == null &amp;amp;&amp;amp; legalAction(action)) {
        this.action = action;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Not only does this solve the immediate problems with initial naive translated design, it actually deduplicates logic and significantly lowers the cognitive load of dealing with competing sub-processes mutating state and causing side-effects.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You could say, Temporal forced me into a better design.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Note that I didn&amp;#8217;t use an actual queue in this example, but instead opted to ignore any additional incoming action before the current one was processed.
I mean, you can&amp;#8217;t bravely declare &lt;em&gt;CALL&lt;/em&gt; then followed by a sneaky &lt;em&gt;fold&lt;/em&gt; and expect the &lt;em&gt;fold&lt;/em&gt; to count.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Idiomatic Temporal input processing&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;While not selecting &lt;code&gt;@UpdateMethod&lt;/code&gt; to covey state back to clients, it does seem to be a better fit then a &lt;code&gt;@SignalMethod&lt;/code&gt; when it comes to processing end-user input. With specific support for light-weight validation and feedback, &lt;em&gt;before&lt;/em&gt; recording &lt;strong&gt;only valid&lt;/strong&gt; signals into history. Making it easier to read later.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Revisiting:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@SignalMethod
void playerAction(Action action) {
    if (this.action == null &amp;amp;&amp;amp; legalAction(action)) {
        this.action = action;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;into something like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@UpdateValidatorMethod(updateName = &#34;playerAction&#34;)
void playerAction(Action action) {
    if (this.action != null) {
        throw new IllegalArgumentException(&#34;action already set...&#34;);
    }
    if (!legalAction(action)) {
        throw new IllegalArgumentException(&#34;illegal action...&#34;);
    }
}
@UpdateMethod
void playerAction(Action action) {
    if (this.action != null &amp;amp;&amp;amp; legalAction(action)) {
        this.action = action;
    } else {
        log.warn(&#34;action {} no longer valid, state: {}&#34;, action, state);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Note that I didn&amp;#8217;t actually remove the validations from the &lt;code&gt;@UpdateMethod&lt;/code&gt;, as the execution of these methods can again be interleaved with any workflow processes. This does however add two things:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;feedback to clients in the form of exceptions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;prevents recording and showing typical bogus actions in the event log&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Workflows, determinism and side effects&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In a programming model where signals can be replayed to reproduce the state to a certain previous state, two things are problematic:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;methods that introduce side effects by making changes outside the workflow,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;methods that produce different results each time they are used.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Dealing with side effects&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;To prevent re-creating any side effects upon replay, all code that triggers side effects must be explicitly
run as an &lt;code&gt;Activity&lt;/code&gt;.
With an Activity also just being code, but behind an &lt;code&gt;interface&lt;/code&gt; marked as &lt;code&gt;@ActivityInterface&lt;/code&gt;.
(Depending on configuration though, the activity code may run remotely on a completely different/dedicated microservice.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The idea behind this is simple. Upon replay, all events will be replayed but all activities will &lt;strong&gt;not&lt;/strong&gt;.
Preventing unwanted duplication of side effects.
Instead, any outcome of an activity will be directly shared back to the workflow by the framework, serving it from its persisted history.
So if &lt;code&gt;someActivity.doSomething()&lt;/code&gt; returned &lt;code&gt;&#34;foo&#34;&lt;/code&gt; the first time, it will also return &lt;code&gt;&#34;foo&#34;&lt;/code&gt; to the workflow during replay, &lt;strong&gt;but its method body will never actually run again.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;Note that like most things in a distributed persisted environment Temporal guarantees &lt;strong&gt;at least once&lt;/strong&gt; delivery.&lt;/em&gt;
&lt;em&gt;Therefore, Workflow, Signal, Update and Activity methods must still deal with being triggered multiple times in edge cases like partial failures, possibly needing idempotency checks to prevent duplicate processing.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Dealing with different results&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In order to support methods that produce different results each time the Temporal frameworks &lt;code&gt;Workflow&lt;/code&gt; class comes with build-in replacements that record their calls and results in history.
Apart from the structural changes in the baseline code, I also had to replace these sort of functions with their Temporal counterparts:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;Workflow.randomUUID()&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;Workflow.newRandom()&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;Workflow.currentTimeMillis()&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Embracing Event-Sourcing&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Another adaption from the original baseline code, that was based on persisting its entire state through 1 generic event, is going all-in on the event-sourced nature of Temporal defining small and specific signals and activities.
With the framework persisting &lt;strong&gt;everything but&lt;/strong&gt; the actual state itself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Not only is it more idiomatic EDA, using specific signals and activities to send back events for everything, it helps a lot in leveraging the out-of-the-box observability to gain insights!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = &#34;type&#34;)
@JsonSubTypes({
        @Type(value = PlayerFolded.class, name = &#34;PlayerFolded&#34;),
        @Type(value = PlayerBetted.class, name = &#34;PlayerBetted&#34;),
        @Type(value = PlayerChecked.class, name = &#34;PlayerChecked&#34;),
        @Type(value = PlayerRaised.class, name = &#34;PlayerRaised&#34;),
        ...
})
sealed public interface TableEvent permits
        PlayerFolded, PlayerBetted, PlayerChecked, PlayerRaised, ... {
    UUID tournamentId();
    int tableId();
    long eventId();
    Date occurredAt();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With each concrete &lt;code&gt;record&lt;/code&gt; containing at least these four fields, but in addition anything else specific to its event.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;All used as separate activities and update methods:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@ActivityInterface
public interface TableEventPublisher {
    void playerFolded(PlayerFolded playerFolded);
    void playerChecked(PlayerChecked playerChecked);
    void playerBetted(PlayerBetted playerBetted);
    ...
}

@WorkflowInterface
public interface TableWorkflow {
    @UpdateMethod void onFold(Fold fold);
    @UpdateMethod void onCheck(Check check);
    @UpdateMethod void onBet(Bet bet);
    ...
    @UpdateValidatorMethod(updateName = &#34;onFold&#34;) void validateFold(Fold fold);
    @UpdateValidatorMethod(updateName = &#34;onCheck&#34;) void validateCheck(Check check);
    @UpdateValidatorMethod(updateName = &#34;onBet&#34;) void validateBet(Bet bet);
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;When running the actual game, looking at the build-in dashboard it creates a nice overview updated real-time as the game goes on. (Filtering on Activities for brevity)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/03/temporal_events.png&#34; alt=&#34;Event History&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;Figure 2. Temporal Event History Dashboard (time on X-axis &amp;#8594;)&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Learnings so far&lt;/h3&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;Development of a long-running stateful process feels quite natural, without any particular invasive behavior or altering the general devx of typical software development. Which is quite unlike other workflow frameworks that tent to be based on (BPMN-)model-first based approaches.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Signal handlers must be implemented in a very specific way such that they queue their signal to not introduce subtle bugs (more on this in next part). With the framework not shielding the user from implementing code that seemingly works but isn&amp;#8217;t truly correct. I guess this is the exact other edge of the sword when it comes to full freedom to model the workflow directly in code (wrongly).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Overall Temporal sets up a nice environment for workflows to run in, completely restartable, observable and deterministic based on event-sourcing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choosing how to share the workflow state to clients seems to have a high impact on the overall code/flow and should not be considered an afterthought.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Next up&amp;#8230;&amp;#8203;&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Stay tuned for getting down and dirty with hardening the workflow for recreation from state, continuation &amp;amp; communication.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;(&lt;a href=&#34;https://jdriven.com/blog/2026/03/Battle-testing-Temporal-Part-2&#34;&gt;See here for part 2&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="Java" scheme="https://jdriven.com/blog/tag/Java/" />
        <category term="Durable Execution" scheme="https://jdriven.com/blog/tag/Durable-Execution/" />
        <category term="Temporal.io" scheme="https://jdriven.com/blog/tag/Temporal-io/" />
        <category term="Poker" scheme="https://jdriven.com/blog/tag/Poker/" />
        <category term="Workflow" scheme="https://jdriven.com/blog/tag/Workflow/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/03/OAuth-2.0-Explained-with-UML/</id>
        <title>OAuth 2.0 and OIDC Explained with UML</title>
        <social:hashtags>#Security #OpenIDConnect #UML #Authentication #OAuth2.0 #OIDC #Authorization #SSO #SingleSign-On</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/03/OAuth-2.0-Explained-with-UML/"/>
        <published>2026-03-04T06:30:00.000Z</published>
        <updated>2026-03-04T06:30:00.000Z</updated>
        <author>
            <name>Ronald</name>
            <social:twitter>kqosh1</social:twitter>
            <uri>https://jdriven.com/blog/author/ronald-koster</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The purpose of Open Authorization 2.0 (OAuth 2.0) is to give an application (the &#34;Client&#34;) limited access to your data at
another service (the &#34;Resource Server&#34;), without having to give your password to that application. When OIDC is added
Single Sign-On (SSO) is supported as well. The flow of these protocols can nicely be shown in a UML Sequence Diagram.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The purpose of Open Authorization 2.0 (OAuth 2.0) is to give an application (the &#34;Client&#34;) limited access to your data at
another service (the &#34;Resource Server&#34;), without having to give your password to that application. When OIDC is added
Single Sign-On (SSO) is supported as well. The flow of these protocols can nicely be shown in a UML Sequence Diagram.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;OAuth 2.0 with optional OIDC&lt;/h3&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Registration&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Before running the &lt;strong&gt;Client&lt;/strong&gt; app as depicted in de diagram below, one must register its &lt;code&gt;client_id&lt;/code&gt;, &lt;code&gt;client_secret&lt;/code&gt;, and
&lt;code&gt;redirect URIs&lt;/code&gt; at the &lt;strong&gt;Authorization Server&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;When JSON Web Key Set (JWKS) is used the &lt;strong&gt;Resource Server&lt;/strong&gt; uses the public key to decrypt the JWT (JSON Web Token),
which has been encrypted by the &lt;strong&gt;Authorization Server&lt;/strong&gt; with its private key. Else if a symmetric key is used to encrypt
the token that must have been shared by the &lt;strong&gt;Authorization Server&lt;/strong&gt; with the &lt;strong&gt;Resourcse Server&lt;/strong&gt; first.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;The Flow&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;OAuth 2.0 deals with authorizations (aka. permissions). It is meant for getting the authorizations of a user
(the &lt;strong&gt;Resource Owner&lt;/strong&gt;) to access resources at an external &lt;strong&gt;Resource Server&lt;/strong&gt; from an external &lt;strong&gt;Authorization Server&lt;/strong&gt;.
With &#39;external&#39; meaning the &lt;strong&gt;Resource Server&lt;/strong&gt; is not part of the organisation of the &lt;strong&gt;Client&lt;/strong&gt; application.
The authorizations are packed in an Access Token and an accompanying Refresh Token. The latter, to refresh the
Access Token when it expires.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Optionally OpenID Connect (OIDC) is for authentications. It is an extension on OAuth and is used to acquire some user
specific data from the &lt;strong&gt;Authorization Server&lt;/strong&gt;. That data is provided via an ID Token, and can be used by the &lt;strong&gt;Client&lt;/strong&gt;
to verify the name, e-mail address, etc. provided by the user.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Here is an example of the roles:&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&#34;tableblock frame-all grid-all&#34; style=&#34;width: 50%;&#34;&gt;
&lt;colgroup&gt;
&lt;col style=&#34;width: 50%;&#34;&gt;
&lt;col style=&#34;width: 50%;&#34;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Role&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;strong&gt;Resource Owner&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;You (the user)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;strong&gt;Client&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Slack&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;strong&gt;Authorization Server&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Google Accounts / OAuth&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;strong&gt;Resource Server&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Google Calendar API&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Switching on OIDC can be done by adding the value &lt;code&gt;openid&lt;/code&gt; to the &lt;code&gt;scope&lt;/code&gt; parameter (see diagram below).
For example like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OAuth 2.0 only:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-none hljs&#34;&gt;scope=https://www.googleapis.com/auth/calendar.readonly&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OAuth 2.0 with OIDC:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-none hljs&#34;&gt;scope=openid profile email https://www.googleapis.com/auth/calendar.readonly&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Description:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;OIDC scopes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;openid&lt;/code&gt;: also use OIDC.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;profile&lt;/code&gt;: request standard profile info of user, like, name, photo, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;email&lt;/code&gt;: also request the user&amp;#8217;s email address.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;OAuth 2.0 scope example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;a href=&#34;https://www.googleapis.com/auth/calendar.readonly&#34; class=&#34;bare&#34;&gt;https://www.googleapis.com/auth/calendar.readonly&lt;/a&gt;&lt;/code&gt;: request readonly permission for Google Calendar API.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The scopes &lt;code&gt;profile&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; are sometimes also added when using OAuth 2.0 only. In that case this is sometimes
interpreted by the &lt;strong&gt;Authorization Server&lt;/strong&gt; as an additional request to access some user data endpoint. The &lt;strong&gt;Client&lt;/strong&gt;
can then request user data with an additional call to that endpoint using the Access Token.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Depicted below is the flow of an OAuth 2.0 process with optional OIDC.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/03/oauth2-diagram.png&#34; alt=&#34;oauth2-diagram.png&#34;&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;Figure 1. OAuth 2.0 and OIDC Sequence Diagram&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;span class=&#34;small&#34;&gt;(This image was generated from &lt;a href=&#34;https://bitbucket.org/jdriven/plantuml-asciidoc-example&#34;&gt;these sources&lt;/a&gt;,
see also &lt;a href=&#34;https://jdriven.com/blog/2025/10/PlantUML-in-AsciiDoc-with-Maven&#34;&gt;PlantUML in AsciiDoc with Maven&lt;/a&gt;.)&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="OAuth 2.0" scheme="https://jdriven.com/blog/category/OAuth-2-0/" />
        <category term="OIDC" scheme="https://jdriven.com/blog/category/OAuth-2-0/OIDC/" />
        <category term="OpenID Connect" scheme="https://jdriven.com/blog/category/OAuth-2-0/OIDC/OpenID-Connect/" />
        <category term="UML" scheme="https://jdriven.com/blog/category/OAuth-2-0/OIDC/OpenID-Connect/UML/" />
        <category term="Authentication" scheme="https://jdriven.com/blog/category/OAuth-2-0/OIDC/OpenID-Connect/UML/Authentication/" />
        <category term="Authorization" scheme="https://jdriven.com/blog/category/OAuth-2-0/OIDC/OpenID-Connect/UML/Authentication/Authorization/" />
        <category term="Security" scheme="https://jdriven.com/blog/category/OAuth-2-0/OIDC/OpenID-Connect/UML/Authentication/Authorization/Security/" />
        <category term="SSO" scheme="https://jdriven.com/blog/category/OAuth-2-0/OIDC/OpenID-Connect/UML/Authentication/Authorization/Security/SSO/" />
        <category term="Single Sign-On" scheme="https://jdriven.com/blog/category/OAuth-2-0/OIDC/OpenID-Connect/UML/Authentication/Authorization/Security/SSO/Single-Sign-On/" />
        <category term="Security" scheme="https://jdriven.com/blog/tag/Security/" />
        <category term="OpenID Connect" scheme="https://jdriven.com/blog/tag/OpenID-Connect/" />
        <category term="UML" scheme="https://jdriven.com/blog/tag/UML/" />
        <category term="Authentication" scheme="https://jdriven.com/blog/tag/Authentication/" />
        <category term="OAuth 2.0" scheme="https://jdriven.com/blog/tag/OAuth-2-0/" />
        <category term="OIDC" scheme="https://jdriven.com/blog/tag/OIDC/" />
        <category term="Authorization" scheme="https://jdriven.com/blog/tag/Authorization/" />
        <category term="SSO" scheme="https://jdriven.com/blog/tag/SSO/" />
        <category term="Single Sign-On" scheme="https://jdriven.com/blog/tag/Single-Sign-On/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/03/Flyway-the-Right-Way/</id>
        <title>Flyway the Right Way</title>
        <social:hashtags>#SoftwareDevelopment #DatabaseManagement #Flyway #Rollback</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/03/Flyway-the-Right-Way/"/>
        <published>2026-03-03T06:30:00.000Z</published>
        <updated>2026-03-03T06:30:00.000Z</updated>
        <author>
            <name>Ronald</name>
            <social:twitter>kqosh1</social:twitter>
            <uri>https://jdriven.com/blog/author/ronald-koster</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Sometimes when you upgrade your application the new version does not work correctly, and you need to downgrade. When you are unlucky you did some changes in your
database that cannot be undone. In that case you need to restore the database from a backup, which is usually quite cumbersome and time-consuming.
How nice would it be if you could just run an undo script which is much more light weight. This blog describes how you can do that using &lt;a href=&#34;https://flywaydb.org&#34;&gt;Flyway&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Sometimes when you upgrade your application the new version does not work correctly, and you need to downgrade. When you are unlucky you did some changes in your
database that cannot be undone. In that case you need to restore the database from a backup, which is usually quite cumbersome and time-consuming.
How nice would it be if you could just run an undo script which is much more light weight. This blog describes how you can do that using &lt;a href=&#34;https://flywaydb.org&#34;&gt;Flyway&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Steps to follow&lt;/h3&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;Create a downgrade script for every upgrade script.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make both scripts idempotent, by using &lt;code&gt;IF EXISTS&lt;/code&gt; constructs. This makes your scripts more robust and testable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Do &lt;strong&gt;not&lt;/strong&gt; add irreversible changes to the upgrade script. So do not use statements like &lt;code&gt;delete&lt;/code&gt; or &lt;code&gt;drop&lt;/code&gt;. Instead, use &lt;code&gt;rename&lt;/code&gt;,
and do the actual deletes and drops some version later.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Here is an example for MariaDB:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;V1.7__rename_table_city_and_person_columns.sql&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-sql hljs&#34; data-lang=&#34;sql&#34;&gt;ALTER TABLE IF EXISTS city RENAME TO deprecated_city;

ALTER TABLE
    person RENAME COLUMN IF EXISTS name TO deprecated_name,
    person ADD COLUMN IF NOT EXISTS first_name VARCHAR(255),
    person ADD COLUMN IF NOT EXISTS last_name VARCHAR(255);

UPDATE person SET first_name = deprecated_name;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;U1.7__rename_table_city_and_person_columns.sql&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-sql hljs&#34; data-lang=&#34;sql&#34;&gt;ALTER TABLE IF EXISTS deprecated_city RENAME TO city;

ALTER TABLE person RENAME COLUMN IF EXISTS deprecated_name TO name;
--  No need to drop added columns, they are harmless.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And then a few releases later:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;V1.11__drop_deprecated_table_and_column.sql&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-sql hljs&#34; data-lang=&#34;sql&#34;&gt;DROP TABLE IF EXISTS deprecated_city;

ALTER TABLE person DROP COLUMN IF EXISTS deprecated_name;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And now there is no undo script, because this cannot be undone. However, since this is a few releases later you
should have noticed any issues with the missing (because they were renamed) table and column in earlier
releases already.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;NB When you run the undo script manually, you will have to update the flyway history table manually as well.
See also Flyway in the &lt;a href=&#34;https://jdriven.com/blog/2025/04/Flyway-in-the-command-line&#34;&gt;Flyway in the command-line&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="Software Development" scheme="https://jdriven.com/blog/category/Software-Development/" />
        <category term="Database Management" scheme="https://jdriven.com/blog/category/Software-Development/Database-Management/" />
        <category term="Flyway" scheme="https://jdriven.com/blog/category/Software-Development/Database-Management/Flyway/" />
        <category term="Rollback" scheme="https://jdriven.com/blog/category/Software-Development/Database-Management/Flyway/Rollback/" />
        <category term="Software Development" scheme="https://jdriven.com/blog/tag/Software-Development/" />
        <category term="Database Management" scheme="https://jdriven.com/blog/tag/Database-Management/" />
        <category term="Flyway" scheme="https://jdriven.com/blog/tag/Flyway/" />
        <category term="Rollback" scheme="https://jdriven.com/blog/tag/Rollback/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/03/Nushell-Niceties-Checking-If-Value-Is-In-List-Or-String-Or-Key-In-Record/</id>
        <title>Nushell Niceties: Checking If Value Is In List Or String Or Key In Record</title>
        <social:hashtags>#Nushell</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/03/Nushell-Niceties-Checking-If-Value-Is-In-List-Or-String-Or-Key-In-Record/"/>
        <published>2026-03-03T04:46:18.000Z</published>
        <updated>2026-03-03T04:46:18.000Z</updated>
        <author>
            <name>mrhaki</name>
            <social:mastodon>mrhaki@mastodon.online</social:mastodon>
            <social:bluesky>mrhaki.com</social:bluesky>
            <uri>https://jdriven.com/blog/author/mrhaki</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Nushell has the &lt;code&gt;in&lt;/code&gt; operator to check if a value is an element of list. With the same &lt;code&gt;in&lt;/code&gt; operator you can check if a string is a substring of another string. And finally you can use the &lt;code&gt;in&lt;/code&gt; operator to check if a key is in a record. When you use the operator the value you want to check is defined before the operator and the list, other string or record is defined after the operator.&lt;br&gt;
The &lt;code&gt;not-in&lt;/code&gt; operator is the opposite of the &lt;code&gt;in&lt;/code&gt; operator and checks if a value is &lt;strong&gt;not&lt;/strong&gt; in list or other string or key in a record.&lt;br&gt;
It is also possible to use the &lt;code&gt;has&lt;/code&gt; and &lt;code&gt;not-has&lt;/code&gt; operators to do the same checks, but the value you want to check is set after the operator. The list, other string or record is set before the operator.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Nushell has the &lt;code&gt;in&lt;/code&gt; operator to check if a value is an element of list. With the same &lt;code&gt;in&lt;/code&gt; operator you can check if a string is a substring of another string. And finally you can use the &lt;code&gt;in&lt;/code&gt; operator to check if a key is in a record. When you use the operator the value you want to check is defined before the operator and the list, other string or record is defined after the operator.&lt;br&gt;
The &lt;code&gt;not-in&lt;/code&gt; operator is the opposite of the &lt;code&gt;in&lt;/code&gt; operator and checks if a value is &lt;strong&gt;not&lt;/strong&gt; in list or other string or key in a record.&lt;br&gt;
It is also possible to use the &lt;code&gt;has&lt;/code&gt; and &lt;code&gt;not-has&lt;/code&gt; operators to do the same checks, but the value you want to check is set after the operator. The list, other string or record is set before the operator.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In the following example the operators are used with lists:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-nu hljs&#34; data-lang=&#34;nu&#34;&gt;use std/assert

# Using in operator to check if a value is in a list.
assert (1 in [1 2 3 4])
assert (&#39;Nushell&#39; in [&#39;Nushell&#39; &#39;rocks&#39;])

# Using not-in operator to check if a value is not in a list.
assert (1 not-in [2 3 4])
assert (&#39;nushell&#39; not-in [&#39;Nushell&#39; &#39;rocks&#39;])


# Using has operator to check if a value is in a list.
assert ([1 2 3 4] has 1)
assert ([&#39;Nushell&#39; &#39;rocks&#39;] has &#39;Nushell&#39;)

# Using not-has operator to check if a value is not in a list.
assert ([1 2 3 4] not-has 5)
assert ([&#39;Nushell&#39; &#39;rocks&#39;] not-has &#39;nushell&#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In the following example the operators are used with strings:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-nu hljs&#34; data-lang=&#34;nu&#34;&gt;use std/assert

# Using in operator to check if a value is in a string.
assert (&#39;Nushell&#39; in &#39;Nushell rocks!&#39;)

# Using not-in operator to check if a value is not in a string.
assert (&#39;awesome&#39; not-in &#39;Nushell rocks!&#39;)


# Using has operator to check if a value is in a string.
assert (&#39;Nushell rocks&#39; has &#39;Nushell&#39;)

# Using not-has operator to check if a value is not in a string.
assert (&#39;Nushell rocks&#39; not-has &#39;awesome&#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In the following example the operators are used with records:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-nu hljs&#34; data-lang=&#34;nu&#34;&gt;use std/assert

# Using in operator to check if a key is in a record.
assert (&#39;name&#39; in {name: &#39;mrhaki&#39; location: &#39;Tilburg&#39;})

# Using not-in operator to check if a key is not in a record.
assert (&#39;age&#39; not-in {name: &#39;mrhaki&#39; location: &#39;Tilburg&#39;})


# Using has operator to check if a key is in a record.
assert ({name: &#39;mrhaki&#39; location: &#39;Tilburg&#39;} has name)

# Using not-has operator to check if a key is not in a record.
assert ({name: &#39;mrhaki&#39; location: &#39;Tilburg&#39;} not-has age)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Written with Nushell 0.110.0.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.mrhaki.com/2026/01/nushell-niceties-checking-if-value-is.html&#34;&gt;Original post&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</content>
        <category term="Nushell" scheme="https://jdriven.com/blog/category/Nushell/" />
        <category term="Nushell" scheme="https://jdriven.com/blog/tag/Nushell/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/02/Kotlin-protip-indexed-access-operator-overloading/</id>
        <title>Kotlin protip: indexed access operator overloading with multiple indices</title>
        <social:hashtags>#kotlin</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/02/Kotlin-protip-indexed-access-operator-overloading/"/>
        <published>2026-02-25T07:00:00.000Z</published>
        <updated>2026-02-25T07:00:00.000Z</updated>
        <author>
            <name>Riccardo</name>
            <social:mastodon>riccardo@mastodon.online</social:mastodon>
            <uri>https://jdriven.com/blog/author/riccardo-lippolis</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Operator overloading is one of Kotlin&amp;#8217;s most beloved features.
It lets you give familiar syntax to custom types, and you&amp;#8217;re probably already using it without even noticing it.
Take the &lt;code&gt;Map&lt;/code&gt; interface, for example: the indexed access operator (&lt;code&gt;[]&lt;/code&gt;) is used to read and write entries:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val m = mutableMapOf&amp;lt;Int, String&amp;gt;()
m[1] = &#34;one&#34;     // m.put(1, &#34;one&#34;)
val value = m[1] // m.get(1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The same syntax works for arrays, lists, and any type that declares an &lt;code&gt;operator fun get&lt;/code&gt; or &lt;code&gt;operator fun set&lt;/code&gt;.
Did you know that you can overload the indexed access operator with &lt;strong&gt;multiple indices&lt;/strong&gt;?&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Operator overloading is one of Kotlin&amp;#8217;s most beloved features.
It lets you give familiar syntax to custom types, and you&amp;#8217;re probably already using it without even noticing it.
Take the &lt;code&gt;Map&lt;/code&gt; interface, for example: the indexed access operator (&lt;code&gt;[]&lt;/code&gt;) is used to read and write entries:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val m = mutableMapOf&amp;lt;Int, String&amp;gt;()
m[1] = &#34;one&#34;     // m.put(1, &#34;one&#34;)
val value = m[1] // m.get(1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The same syntax works for arrays, lists, and any type that declares an &lt;code&gt;operator fun get&lt;/code&gt; or &lt;code&gt;operator fun set&lt;/code&gt;.
Did you know that you can overload the indexed access operator with &lt;strong&gt;multiple indices&lt;/strong&gt;?&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Example: A two-dimensional grid that stores its data in a one-dimensional Array&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A common scenario where multi-index access is handy is a two-dimensional grid.
Below is a &lt;code&gt;Grid&lt;/code&gt; class that keeps its state in a single &lt;code&gt;Array&lt;/code&gt; and exposes a natural &lt;code&gt;grid[x, y]&lt;/code&gt; syntax for both reading and writing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;class Grid(val width: Int, val height: Int) {
    /** Create a one-dimensional array, initially filled with all zeroes */
    private val data = Array(width * height) { 0 }

    /** Convert (x, y) to the underlying one-dimensional index */
    private fun index(x: Int, y: Int): Int = y * width + x

    /** Read the element at (x, y), or return `null` for non-existing coordinates */
    operator fun get(x: Int, y: Int): Int? = data.getOrNull(index(x, y))

    /** Write a new value at (x, y) */
    operator fun set(x: Int, y: Int, value: Int) {
        require(x in 0..&amp;lt;grid.width &amp;amp;&amp;amp; y in 0..&amp;lt;grid.height)
        data[index(x, y)] = value
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Using the Grid&lt;/h4&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val grid = Grid(width = 3, height = 2) // 3 columns × 2 rows

grid[1, 0] = 42     // Set cell (1, 0) to value 42
println(grid[1, 0]) // → 42

// Print the entire grid
for (y in 0..&amp;lt;grid.height) {
    for (x in 0..&amp;lt;grid.width) {
        print(&#34;${grid[x, y]} &#34;)
    }
    println()
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With the &lt;code&gt;operator fun get&lt;/code&gt; and &lt;code&gt;operator fun set&lt;/code&gt; signatures, the compiler simply translates &lt;code&gt;grid[x, y]&lt;/code&gt; int a call to &lt;code&gt;grid.get(x, y)&lt;/code&gt;.
Similarly, &lt;code&gt;grid[x, y] = v&lt;/code&gt; translates to &lt;code&gt;grid.set(x, y, v)&lt;/code&gt;. Multiple indices are just multiple parameters.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;admonitionblock note&#34;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&#34;icon&#34;&gt;
&lt;i class=&#34;fa icon-note&#34; title=&#34;Note&#34;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&#34;content&#34;&gt;
In this example, I am using &lt;code&gt;Int&lt;/code&gt; parameters only, but this can be anything. For example, it is perfectly valid to write &lt;code&gt;operator fun get(x: String, y: Int): Double { &amp;#8230;&amp;#8203; }&lt;/code&gt;, and call the function like this: &lt;code&gt;grid[&#34;hello&#34;, 5]&lt;/code&gt;.
The corresponding &lt;code&gt;set&lt;/code&gt; function would be: &lt;code&gt;operator fun set(x: String, y: Int, value: Double) { &amp;#8230;&amp;#8203; }&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Why overload with multiple indices?&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Using a multi-index signature makes the grid&amp;#8217;s API feel more natural: &lt;code&gt;grid[x, y]&lt;/code&gt; reads like &#34;the cell at (x, y)&#34;,
while keeping the underlying one-dimensional array hidden from callers, so they don&amp;#8217;t need to worry about &lt;a href=&#34;https://en.wikipedia.org/wiki/Row-_and_column-major_order&#34;&gt;row-major ordering&lt;/a&gt;.
This encapsulation also gives you the flexibility to swap out the storage strategy later without touching any client code.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Summary&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin’s indexed access operator can be overloaded for any number of indices (e.g. a 3D grid with an &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, and &lt;code&gt;z&lt;/code&gt; coordinate).
A 2D grid backed by a 1D array is a perfect example: just declare &lt;code&gt;operator fun get(x, y)&lt;/code&gt; and &lt;code&gt;operator fun set(x, y, value)&lt;/code&gt; and you get a clean, idiomatic API.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Shoutout to &lt;a href=&#34;https://jdriven.com/blog/author/alexanderchatzizacharias&#34;&gt;Alexander Chatzizacharias&lt;/a&gt; for reminding me of this awesome Kotlin feature.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="kotlin" scheme="https://jdriven.com/blog/category/kotlin/" />
        <category term="operator overloading" scheme="https://jdriven.com/blog/category/kotlin/operator-overloading/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/tag/kotlin/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-12-sealed-classes-and-enums/</id>
        <title>Bonus: From Java to Kotlin – Part XII: Sealed Classes vs Enums / Polymorphism</title>
        <social:hashtags>#java #kotlin #designpatterns</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-12-sealed-classes-and-enums/"/>
        <published>2026-02-24T08:00:00.000Z</published>
        <updated>2026-02-24T08:00:00.000Z</updated>
        <author>
            <name>Justus</name>
            <uri>https://jdriven.com/blog/author/jbrugman</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 12&lt;/strong&gt;:
In this post, we explore how to model a &lt;strong&gt;finite set of types&lt;/strong&gt; in Java — the “old way” with enums or class hierarchies, the “new way” with Java SE 17+ sealed classes — and then see how Kotlin handles them elegantly with sealed classes.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 12&lt;/strong&gt;:
In this post, we explore how to model a &lt;strong&gt;finite set of types&lt;/strong&gt; in Java — the “old way” with enums or class hierarchies, the “new way” with Java SE 17+ sealed classes — and then see how Kotlin handles them elegantly with sealed classes.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The problem&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Suppose you have a concept with a &lt;strong&gt;finite number of types&lt;/strong&gt;, like a payment status:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Pending&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Completed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Failed&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You want to be able to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Represent the different types&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optionally store extra data per type&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Handle each type safely and exhaustively&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In other words: you want your program to know whether your money has arrived, is fashionably late, or vanished into the void — all without having to play detective in instanceof checks.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Java &#39;the old way&#39;: Enums or Class Hierarchies&lt;/h3&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Using Enums&lt;/h4&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;public enum PaymentStatus {
    PENDING,
    COMPLETED,
    FAILED
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;PaymentStatus status = PaymentStatus.PENDING;

switch (status) {
  case PENDING:
    System.out.println(&#34;Pending...&#34;);
    break;
  case COMPLETED:
    System.out.println(&#34;Completed!&#34;);
    break;
  case FAILED:
    System.out.println(&#34;Failed!&#34;);
    break;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ Works for simple states&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;❌ Cannot store per-case extra data easily&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;❌ Cannot attach behavior without extra boilerplate&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Using Class Hierarchies&lt;/h4&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;abstract class PaymentStatus {}

class Pending extends PaymentStatus {}
class Completed extends PaymentStatus {
    private final LocalDateTime completedAt;
    Completed(LocalDateTime completedAt) { this.completedAt = completedAt; }
}
class Failed extends PaymentStatus {
    private final String reason;
    Failed(String reason) { this.reason = reason; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;PaymentStatus status = new Completed(LocalDateTime.now());

if (status instanceof Completed c) {
  System.out.println(&#34;Completed at &#34; + c.completedAt);
} else if (status instanceof Failed f) {
  System.out.println(&#34;Failed because &#34; + f.reason);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ Can store extra data&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;❌ Boilerplate + &lt;code&gt;instanceof&lt;/code&gt; checks&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;❌ No compile-time guarantee that all cases are handled&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Java &#39;the new way&#39;: Sealed Classes with Exhaustive Switch (Java 21+)&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java SE 17 introduced &lt;strong&gt;sealed classes&lt;/strong&gt; (JEP 409), allowing a class or interface to define which subtypes are permitted.
With Java 21, enhanced &lt;strong&gt;switch statements&lt;/strong&gt; and &lt;strong&gt;pattern matching&lt;/strong&gt; make it possible for the compiler to enforce &lt;strong&gt;exhaustive handling&lt;/strong&gt; of all sealed subtypes — similar to Kotlin&amp;#8217;s &lt;code&gt;when&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;public abstract sealed class PaymentStatus
  permits Pending, Completed, Failed { }

public final class Pending extends PaymentStatus { }

public final class Completed extends PaymentStatus {
  private final LocalDateTime completedAt;
  public Completed(LocalDateTime completedAt) { this.completedAt = completedAt; }
}

public final class Failed extends PaymentStatus {
  private final String reason;
  public Failed(String reason) { this.reason = reason; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Using an &lt;strong&gt;exhaustive switch&lt;/strong&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;PaymentStatus status = new Completed(LocalDateTime.now());

switch (status) {
  case Pending p -&amp;gt; System.out.println(&#34;Pending...&#34;);
  case Completed c -&amp;gt; System.out.println(&#34;Completed at &#34; + c.completedAt);
  case Failed f -&amp;gt; System.out.println(&#34;Failed because &#34; + f.reason);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ The compiler ensures that &lt;strong&gt;all subtypes&lt;/strong&gt; of &lt;code&gt;PaymentStatus&lt;/code&gt; are handled&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ No &lt;code&gt;default&lt;/code&gt; branch needed&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ Safe and concise, similar to Kotlin&amp;#8217;s &lt;code&gt;when&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Note: The older switch syntax without pattern matching still requires a &lt;code&gt;default&lt;/code&gt; and does not enforce exhaustiveness.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Kotlin way: Sealed Classes&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin&amp;#8217;s sealed classes work similarly to Java&amp;#8217;s sealed classes in that the compiler &lt;strong&gt;knows all possible subtypes&lt;/strong&gt;.
However, unlike Java, you do &lt;strong&gt;not need to explicitly list the permitted subclasses&lt;/strong&gt; — all subclasses &lt;strong&gt;must be declared in the same file&lt;/strong&gt;.
This automatically keeps the class hierarchy &#34;closed&#34; and allows the compiler to enforce exhaustive &lt;code&gt;when&lt;/code&gt; expressions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Finite set of subtypes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optional data per subtype&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exhaustive &lt;code&gt;when&lt;/code&gt; expressions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Concise syntax with minimal boilerplate&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;sealed class PaymentStatus

data object Pending : PaymentStatus()
data class Completed(val completedAt: LocalDateTime) : PaymentStatus()
data class Failed(val reason: String) : PaymentStatus()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val status: PaymentStatus = Completed(LocalDateTime.now())

when (status) {
  is Pending -&amp;gt; println(&#34;Pending...&#34;)
  is Completed -&amp;gt; println(&#34;Completed at ${status.completedAt}&#34;)
  is Failed -&amp;gt; println(&#34;Failed because ${status.reason}&#34;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ Compiler ensures all cases are handled&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ No &lt;code&gt;instanceof&lt;/code&gt; boilerplate&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ Each subtype can hold its own data&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Polymorphism without &lt;code&gt;when&lt;/code&gt;&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Sealed classes can also encapsulate behavior:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;sealed class PaymentStatus {
  abstract fun message(): String
}

data object Pending : PaymentStatus() {
  override fun message() = &#34;Pending...&#34;
}

data class Completed(val completedAt: LocalDateTime) : PaymentStatus() {
  override fun message() = &#34;Completed at $completedAt&#34;
}

data class Failed(val reason: String) : PaymentStatus() {
  override fun message() = &#34;Failed because $reason&#34;
}

val status: PaymentStatus = Completed(LocalDateTime.now())
println(status.message())&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ Cleaner polymorphic code&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ Each subtype contains its own behavior&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Short catch-up: What is Polymorphism?&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Polymorphism is the ability of a type to take multiple forms.
In practice, it means you can write code that works on a &lt;strong&gt;general type&lt;/strong&gt; (like a superclass or interface)
but behaves differently depending on the &lt;strong&gt;specific subtype&lt;/strong&gt; at runtime.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For example, with a sealed &lt;code&gt;PaymentStatus&lt;/code&gt;, you can handle &lt;code&gt;Pending&lt;/code&gt;, &lt;code&gt;Completed&lt;/code&gt;, or &lt;code&gt;Failed&lt;/code&gt;
in the same &lt;code&gt;switch&lt;/code&gt; or &lt;code&gt;when&lt;/code&gt; block, but each subtype can have its own data and behavior.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Enum vs Sealed Class vs Data Class&lt;/h3&gt;
&lt;table class=&#34;tableblock frame-all grid-all stretch&#34;&gt;
&lt;colgroup&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Feature&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Enum&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Java Sealed (JDK 21+)&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Kotlin Sealed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Finite set of types&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Extra data per type&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;❌ (tricky)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Custom behavior per type&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅ (requires methods / boilerplate)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅ (clean)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Exhaustive checks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅ (switch)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅ (with enhanced switch / pattern matching)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅ (compiler enforced)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Boilerplate&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Low&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Medium&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Low&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;div class=&#34;title&#34;&gt;Rule of thumb&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Enum → simple flags&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Data class → flat objects&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sealed class → hierarchies with data and behavior&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Takeaway&lt;/h3&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Java “old way”: enums or class hierarchies → works, but often verbose&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Java “new way”: sealed classes → controlled hierarchies, safer, closer to Kotlin&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kotlin sealed classes → expressive, concise, compile-time safe, and polymorphic&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;admonitionblock note&#34;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&#34;icon&#34;&gt;
&lt;i class=&#34;fa icon-note&#34; title=&#34;Note&#34;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&#34;content&#34;&gt;
&lt;div class=&#34;dlist&#34;&gt;
&lt;dl&gt;
&lt;dt class=&#34;hdlist1&#34;&gt;Tip&lt;/dt&gt;
&lt;dd&gt;
&lt;p&gt;In Kotlin, you can also use a &lt;code&gt;sealed interface&lt;/code&gt; instead of a &lt;code&gt;sealed class&lt;/code&gt;.
This is useful if some subtypes need to inherit from another class (since Kotlin allows only single class inheritance).
The compiler still enforces &lt;strong&gt;exhaustive &lt;code&gt;when&lt;/code&gt; checks&lt;/strong&gt;.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;sealed interface PaymentStatus

data object Pending : PaymentStatus
data class Completed(val completedAt: LocalDateTime) : PaymentStatus
data class Failed(val reason: String) : PaymentStatus&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ More flexibility without losing the benefits of sealed classes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;✅ Still fully compile-time safe with exhaustive &lt;code&gt;when&lt;/code&gt; expressions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;div class=&#34;title&#34;&gt;Rule of thumb&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;code&gt;sealed class&lt;/code&gt; by default if no other superclass is needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;code&gt;sealed interface&lt;/code&gt; when you want extra flexibility in the type hierarchy.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Sources / Links&lt;/h3&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/sealed-classes.html&#34;&gt;Sealed Classes in Kotlin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/enum-classes.html&#34;&gt;Enum Classes in Kotlin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/control-flow.html#when-expressions-and-statements&#34;&gt;When Expressions and Statements in Kotlin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://openjdk.org/jeps/409&#34;&gt;JEP 409 – Sealed Classes in Java 17&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="java" scheme="https://jdriven.com/blog/category/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/category/java/kotlin/" />
        <category term="Software development" scheme="https://jdriven.com/blog/category/java/kotlin/Software-development/" />
        <category term="java" scheme="https://jdriven.com/blog/tag/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/tag/kotlin/" />
        <category term="design patterns" scheme="https://jdriven.com/blog/tag/design-patterns/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-11-builders/</id>
        <title>From Java to Kotlin – Part XI: The Builder Pattern vs Type-safe Builders / DSL</title>
        <social:hashtags>#java #kotlin #designpatterns</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-11-builders/"/>
        <published>2026-02-23T08:00:00.000Z</published>
        <updated>2026-02-23T08:00:00.000Z</updated>
        <author>
            <name>Justus</name>
            <uri>https://jdriven.com/blog/author/jbrugman</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 11&lt;/strong&gt;:
You want to construct an object.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With many parameters.
Some optional.
Readable.
Preferably without a constructor that looks like a password.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 11&lt;/strong&gt;:
You want to construct an object.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With many parameters.
Some optional.
Readable.
Preferably without a constructor that looks like a password.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The problem&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You have a class with multiple fields.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Some required.
Some optional.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And you want:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;readability&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;immutability&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;no telescoping constructors&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In Java, this usually means one thing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The &lt;strong&gt;Builder pattern&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Java way&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The classic version:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;public class User {
  private final String name;
  private final String email;
  private final int age;

  private User(Builder builder) {
    this.name = builder.name;
    this.email = builder.email;
    this.age = builder.age;
  }

  public static class Builder {
    private String name;
    private String email;
    private int age = -1;

    public Builder name(String name) {
      this.name = name;
      return this;
    }

    public Builder email(String email) {
      this.email = email;
      return this;
    }

    public Builder age(int age) {
      this.age = age;
      return this;
    }

    public User build() {
      return new User(this);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;User user = new User.Builder()
    .name(&#34;John&#34;)
    .email(&#34;john@doe.com&#34;)
    .age(42)
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;It works.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;But it’s a &lt;strong&gt;lot&lt;/strong&gt; of code for:
“Create an object.”&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;So we reach for Lombok:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;@Builder
public class User {
  private String name;
  private String email;
  private int age;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Better.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;But still:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;User user = User.builder()
    .name(&#34;John&#34;)
    .email(&#34;john@doe.com&#34;)
    .age(42)
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;We are solving a language limitation with a pattern.
And then solving the pattern with an annotation processor.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Kotlin way&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In Kotlin, the primary constructor already gives you most of this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;data class User(
  val name: String,
  val email: String,
  val active: Boolean = true
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val user = User(
  name = &#34;John&#34;,
  email = &#34;john@doe.com&#34;
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Named arguments&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Default values&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Immutability&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No builder required&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And it stays readable — even with many parameters.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;But what if the object becomes complex?&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This is where Kotlin does something Java simply cannot do:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Type-safe builders&lt;/strong&gt;, often used to create a small &lt;strong&gt;DSL&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;A quick note: what is a DSL?&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;DSL&lt;/strong&gt; stands for &lt;strong&gt;Domain-Specific Language&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;That sounds heavier than it is.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;It simply means:
A small, readable language focused on one specific task.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Not a new programming language —
just a more expressive way to use Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;html {
  body {
    h1(&#34;Hello&#34;)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;dependencies {
  implementation(&#34;org.jetbrains.kotlin:kotlin-stdlib&#34;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You are not calling constructors.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You are &lt;strong&gt;describing a structure&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;That is the key difference.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A builder chains method calls.
A DSL models a domain.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;A Kotlin type-safe builder&lt;/h3&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val user = user {
  name = &#34;John&#34;
  email = &#34;john@doe.com&#34;
  age = 42
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;fun user(block: UserBuilder.() -&amp;gt; Unit): User =
  UserBuilder().apply(block).build()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This gives you:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a scoped configuration block&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;compile-time safety&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;no partially built objects leaking out&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And the call site reads like configuration instead of construction.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;ps. An example of the &lt;code&gt;UserBuilder&lt;/code&gt; is below here in the &#39;Validating while building&#39; part.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Why this matters&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The Java builder pattern is:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ceremony for optional parameters&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ceremony for readability&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ceremony for immutability&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin removes the need for that ceremony.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And when you &lt;strong&gt;do&lt;/strong&gt; need structure for complex hierarchies, a DSL gives you something the classic builder never could:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Structure in the call site.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Not just chaining.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Takeaway&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In Java:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You build because constructors don’t scale.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In Kotlin:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You use the constructor —
and only build when you’re actually building something.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A builder constructs objects.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A DSL describes them.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Coming from Lombok?&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Your Kotlin replacement is usually:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a &lt;code&gt;data class&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;default values&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;named arguments&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Not another builder.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And your code gets smaller.
And clearer.
By default.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;When &lt;strong&gt;not&lt;/strong&gt; to use a DSL&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A DSL is not a default replacement for a constructor or a data class.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;If you only have:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a flat object&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a handful of parameters&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;no nested structure&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;then a regular constructor with named arguments is clearer and simpler.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val user = User(
  name = &#34;John&#34;,
  email = &#34;john@doe.com&#34;
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;No builder.
No DSL.
No extra types.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Just the model.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A DSL starts to make sense when:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;you are building a hierarchy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the structure matters&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the block improves readability&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;you would otherwise end up with deeply nested constructors or long method chains&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;you want easy validation within the builder&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Validating while building&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A nice bonus of a Kotlin DSL builder is that you can validate your object state before it’s constructed.
For example, you can enforce rules in the builder itself:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;class UserBuilder {
  var name: String = &#34;&#34;
  var email: String = &#34;&#34;
  var age: Int = -1
  var role: String? = null

  fun build(): User {
    require(name.isNotBlank()) { &#34;Name must not be blank&#34; }
    require(email.contains(&#34;@&#34;)) { &#34;Email must be valid&#34; }
    require(age &amp;gt;= 0) { &#34;Age must be non-negative&#34; }
    if (role != null) require(age &amp;gt;= 18) { &#34;Users with a role must be at least 18&#34; }
    return User(name, email, age)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val user = user {
  name = &#34;John&#34;
  email = &#34;john@doe.com&#34;
  age = 42
  role = &#34;admin&#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This demonstrates how type-safe builders can enforce both simple and dependent validations automatically at build time, keeping your objects consistent without extra boilerplate — something that would be more cumbersome with Java builders.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Note: This can be done in Java as well, it&amp;#8217;s an advantage of the builder-pattern versus using the constructor.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Typical good fits are:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;HTML / UI trees&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;routing definitions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;test data builders&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;complex configuration objects&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Using a DSL for a simple data holder is not expressive.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;It’s noise.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And unlike in Java, in Kotlin you don’t need a pattern to compensate for the language.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;So don’t.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Sources / Links&lt;/h3&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/data-classes.html&#34;&gt;Kotlin Data Classes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/functions.html#named-arguments&#34;&gt;Named Arguments&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/functions.html#default-arguments&#34;&gt;Default Arguments&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/type-safe-builders.html&#34;&gt;Type-safe Builders&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/lambdas.html#function-literals-with-receiver&#34;&gt;Function literals with receiver (lambdas with receiver)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://projectlombok.org/features/Builder&#34;&gt;Lombok @Builder&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="java" scheme="https://jdriven.com/blog/category/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/category/java/kotlin/" />
        <category term="Software development" scheme="https://jdriven.com/blog/category/java/kotlin/Software-development/" />
        <category term="java" scheme="https://jdriven.com/blog/tag/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/tag/kotlin/" />
        <category term="design patterns" scheme="https://jdriven.com/blog/tag/design-patterns/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/02/Digital-Sovereignty/</id>
        <title>Digital Sovereignty, A Dutch company&#39;s dilemma in the age of US hyperscalers</title>
        <social:hashtags>#Teachradar</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/02/Digital-Sovereignty/"/>
        <published>2026-02-22T06:56:00.000Z</published>
        <updated>2026-02-22T06:56:00.000Z</updated>
        <author>
            <name>Erik</name>
            <social:mastodon>erikj@mastodon.nl</social:mastodon>
            <uri>https://jdriven.com/blog/author/erik-pronk</uri>
        </author>
        <summary type="html">&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;If you run into a wall, don&amp;#8217;t turn around and give up.
Figure out how to climb it,
go through it, or work around it.&#34; — Michael Jordan&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The grip tightens&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Dutch companies find themselves in an uncomfortable position.
Their data flows through AWS, Azure, or Google Cloud. Their productivity tools are Microsoft 365 or Google Workspace.
Their AI capabilities depend on OpenAI or Anthropic. The convenience is undeniable, but so is the dependency.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Digital sovereignty isn&amp;#8217;t just a buzzword, it&amp;#8217;s about control.
Control over your data, your operations, and ultimately, your business continuity when geopolitical winds shift.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;If you run into a wall, don&amp;#8217;t turn around and give up.
Figure out how to climb it,
go through it, or work around it.&#34; — Michael Jordan&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The grip tightens&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Dutch companies find themselves in an uncomfortable position.
Their data flows through AWS, Azure, or Google Cloud. Their productivity tools are Microsoft 365 or Google Workspace.
Their AI capabilities depend on OpenAI or Anthropic. The convenience is undeniable, but so is the dependency.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Digital sovereignty isn&amp;#8217;t just a buzzword, it&amp;#8217;s about control.
Control over your data, your operations, and ultimately, your business continuity when geopolitical winds shift.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The real considerations&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Legal and regulatory risk:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The CLOUD Act gives US authorities access to data held by US companies, regardless of where it&amp;#8217;s stored.
For Dutch companies it can create a legal minefield to handle sensitive data, like healthcare records, financial information or government contracts.
GDPR compliance becomes complex when your cloud provider can be compelled to hand over EU citizen data to US authorities without being allowed to tell you it happened.
Which raises the question: can you even be compliant when a non-compliant government can compel your data?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Operational dependency:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;What happens when AWS has an outage in eu-west-1? Your business stops.
Not because your code broke, but because someone else&amp;#8217;s infrastructure did.
What if Microsoft changes pricing by 30%? You pay it, because migrating is harder than absorbing the increase.
What if your hyperscaler exits a service you depend on, or geopolitical tensions lead to service restrictions? You scramble.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You&amp;#8217;re not just buying infrastructure, you are outsourcing strategic decision making.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Economic sovereignty:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Every euro spent on US hyperscalers leaves the European economy.
At scale, that&amp;#8217;s billions flowing to Seattle and Silicon Valley annually.
The Netherlands has strong tech capabilities, but the ecosystem struggles to grow when every default architecture decision routes money and talent away from it.
The companies you&amp;#8217;re funding with your cloud bill are also your competitors in the war for engineering talent.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Innovation lock-in:&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Hyperscaler-specific services like Lambda, Azure Functions, or BigQuery create deep technical debt that&amp;#8217;s easy to miss until it&amp;#8217;s too late.
Your developers learn to think in AWS primitives, not distributed systems.
Your architecture encodes hyperscaler assumptions at every layer.
By the time you want to leave, you&amp;#8217;re not migrating infrastructure, you&amp;#8217;re rewriting your product.
Switching costs don&amp;#8217;t just become prohibitive, they become existential.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Your options&lt;/h3&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;The status quo&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Let&amp;#8217;s be honest, for many companies, sticking with AWS, Azure or Google Cloud remains the pragmatic choice.
The hyperscalers didn&amp;#8217;t become dominant by accident.
They offer unmatched scale and reliability that most companies can&amp;#8217;t replicate and a feature velocity that keeps you competitive.
The question is: are you Netflix and do you need it? Most companies aren&amp;#8217;t, and most are running infrastructure that&amp;#8217;s wildly overprovisioned for their actual needs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;If you&amp;#8217;re a startup trying to move fast, or you operate globally and need infrastructure on every continent, or frankly, if you lack the internal expertise to run infrastructure yourself, the hyperscalers make sense.
The key is doing it consciously, not by default.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Bert Hubert, Dutch technologist and founder of PowerDNS, makes this point sharply in his &lt;a href=&#34;https://berthub.eu/articles/posts/ft-on-european-cloud/&#34;&gt;Europe&amp;#8217;s executives need to skill up to solve our total US cloud dependency&lt;/a&gt;: leadership has been indoctrinated that nothing other than US clouds is possible, while the IT people who ran perfectly fine local infrastructure haven&amp;#8217;t gone anywhere.
Their skills are still around.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The smart play here isn&amp;#8217;t pretending sovereignty doesn&amp;#8217;t matter.
It&amp;#8217;s mitigation.
Implement strict data residency controls.
Negotiate contractual protections where possible.
And critically, maintain architectural portability and avoid the deepest lock-in traps even if you&amp;#8217;re staying put.
Think of it as keeping your options open for a future you can&amp;#8217;t predict.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;The European alternative&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;There&amp;#8217;s a growing constellation of European cloud providers that deserve serious consideration.
OVHcloud in France, Hetzner in Germany, TransIP and Cyso here in the Netherlands, and emerging sovereign cloud initiatives like Gaia-X that promise GDPR-native infrastructure from the ground up.
For a comprehensive overview of European alternatives across many categories, check out &lt;a href=&#34;https://european-alternatives.eu/&#34;&gt;European Alternatives&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;These providers shine when your data is genuinely sensitive and your scale doesn&amp;#8217;t demand a global footprint. You&amp;#8217;d be surprised how far well-architected infrastructure on European providers will take you.
They&amp;#8217;re often 30-50% cheaper than hyperscalers, which isn&amp;#8217;t trivial when cloud bills run into six or seven figures.
And there&amp;#8217;s something to be said for supporting European digital infrastructure, every company that chooses European clouds makes the ecosystem stronger.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;But let&amp;#8217;s not romanticize it.
European providers offer fewer managed services, which means more operational burden on your teams.
Their global footprint is smaller, so if you need low latency in Singapore, you&amp;#8217;re out of luck.
Their AI and ML offerings lag behind what you get from AWS SageMaker or Google Vertex AI.
And the talent pool is smaller, finding engineers who know OVHcloud is harder than finding AWS experts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The European cloud path works best when sovereignty is a genuine requirement, not just a nice-to-have, and when you have the engineering maturity to handle more infrastructure yourself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;One more thing.
Moving from one cloud provider to another doesn&amp;#8217;t eliminate lock-in risk, it just shifts it.
If you&amp;#8217;re migrating to a European provider to escape hyperscaler dependency, build portability into your architecture from day one.
Use standard technologies, containerize your workloads, avoid provider-specific services where possible.
You need an exit strategy even for your sovereignty solution.
The goal isn&amp;#8217;t to trade one lock-in for another, it&amp;#8217;s to maintain the flexibility to move if circumstances change.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;The hybrid approach&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;What if you didn&amp;#8217;t have to choose?
The hybrid sovereignty model says: put different workloads where they make the most sense.
Keep your sensitive customer data and core systems on European infrastructure.
Run your commodity workloads, development environments, testing, public-facing content on hyperscalers where they&amp;#8217;re convenient.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This is the sophisticated choice, but it requires sophistication to execute.
You need clear policies about what goes where.
You need the operational maturity to manage multiple cloud providers without drowning in complexity.
Kubernetes becomes your abstraction layer, giving you portability across environments.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The hybrid approach works beautifully when you have complex regulatory requirements that demand sovereignty for some data or workloads but not all.
Financial services companies often land here, customer transactions on European infrastructure, their marketing website on a global CDN.
Healthcare companies put patient records in sovereign clouds but run their research analytics wherever the compute is cheapest.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The risk is ending up with the worst of both worlds, the complexity of multi-cloud without the benefits.
You need strong engineering leadership and clear architectural principles to make this work.
And be honest with yourself: hybrid can become a way to avoid making a real choice, letting the problem linger rather than solving it.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;The private cloud&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;On-premises infrastructure and private clouds sound like a relic from 2010, but they&amp;#8217;re alive and well in certain sectors.
Financial services, healthcare, and government contractors often find that regulatory requirements simply mandate it.
If you already have data center investments and your workloads are stable and predictable, a private cloud can make economic sense.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The modern approach doesn&amp;#8217;t mean buying servers and racking them yourself.
It means OpenStack or Kubernetes-based private clouds in Dutch data centers like Equinix or NorthC.
Using tools like Terraform and Ansible, you get the same self-service agility as AWS, you can spin up infrastructure in minutes instead of weeks, but everything runs on hardware you control.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This path requires deep infrastructure expertise.
You need 24/7 operations capability.
You need to match hyperscaler SLAs with your own team.
It&amp;#8217;s not for everyone, but for organizations with the right profile, large enterprises, regulated industries, governments it&amp;#8217;s a viable sovereignty strategy that also happens to offer complete control.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/02/digital-sovereignty.png&#34; alt=&#34;digital sovereignty&#34; width=&#34;75%&#34;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;How to prioritize&lt;/h3&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Start with data classification&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Not all data is created equal.
Map everything you have by sensitivity.
If you&amp;#8217;re GDPR-compliant, you&amp;#8217;ve already done this. Your records of processing activities are exactly that map.
Your critical sovereign data, customer PII, health records, financial transactions, anything touching state secrets, that&amp;#8217;s your first priority.
Then, sensitive business data, like proprietary algorithms and strategic plans.
Finally, commodity data that doesn&amp;#8217;t matter much, public content, anonymized analytics, development environments.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The critical sovereign data needs to leave hyperscalers first, or at minimum needs the strongest protections.
Everything else can wait.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Assess your regulatory exposure&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Some companies face existential regulatory risk, others face minor compliance headaches.
If you&amp;#8217;re a government supplier, you&amp;#8217;re high priority.
Governments are asking harder questions about where their data lives.
Healthcare and financial services companies handling regulated data are high priority.
Critical infrastructure operators are high priority.
If you process any personal data, you&amp;#8217;re also high priority.
The CLOUD Act means you may simply be unable to meet your GDPR obligations while staying on US hyperscalers.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Audit your technical dependencies&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;How deep is your lock-in?
If you&amp;#8217;re using only IaaS, virtual machines, block storage, networking, you can migrate relatively easily.
Managed databases add moderate complexity but are still portable.
Serverless and proprietary services like AWS Lambda or Azure Cognitive Services create high complexity.
Deep AI/ML platform integration creates very high complexity.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Start your sovereignty journey with the loosely coupled workloads.
Don&amp;#8217;t begin by trying to migrate your most hyperscaler-dependent application, you&amp;#8217;ll fail and give up.
Start with something achievable.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Run the numbers&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Build a real cost-benefit analysis.
Calculate your current hyperscaler spend.
Estimate migration costs honestly, engineering time, tooling, risk, opportunity cost.
Project ongoing operational costs of alternatives.
Factor in risk costs like potential regulatory fines or business disruption.
Don&amp;#8217;t forget strategic value, customer trust, competitive advantage, negotiating leverage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Build a three-year total cost of ownership model.
Sometimes sovereignty is more expensive. Sometimes it&amp;#8217;s cheaper.
Basecamp&amp;#8217;s &lt;a href=&#34;https://basecamp.com/cloud-exit&#34;&gt;cloud exit&lt;/a&gt; is a useful reality check: they saved $3.2M annually by moving off AWS and Google Cloud onto their own hardware.
At least you&amp;#8217;ll know.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Know your capabilities&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Be brutally honest about your team&amp;#8217;s capabilities.
Do you have infrastructure engineering talent who can run production systems?
Can you operate Kubernetes reliably?
Do you have 24/7 operations capability, or are you a 9-to-5 shop?
Can you realistically match hyperscaler SLAs, and do you need to?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;If your capabilities are low, don&amp;#8217;t try to build a private cloud. Start with managed European providers who handle the operational burden. You don&amp;#8217;t need to suffer for sovereignty.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Dutch advantage&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The Netherlands is uniquely positioned to lead on digital sovereignty.
We have great digital infrastructure.
We have a progressive data protection culture that values privacy.
We have the technical talent pool to execute sophisticated strategies.
We have government support for digital sovereignty initiatives.
And our geographic position makes us a natural European data gateway.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Dutch companies can demonstrate that sovereignty and innovation aren&amp;#8217;t mutually exclusive.
We can build the playbook others follow.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Conclusion: Sovereignty as strategy&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Digital sovereignty isn&amp;#8217;t about nationalism or technophobia.
It&amp;#8217;s about strategic autonomy in an uncertain world.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The Netherlands has a peculiar talent.
We&amp;#8217;re a small country that consistently punches above our weight at the Winter Olympics, dominating speed skating against nations ten times our size.
We do this not through brute force, but through focus, technique, and relentless optimization of what we&amp;#8217;re good at.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Digital sovereignty requires the same mindset.
We can&amp;#8217;t outspend AWS. We can&amp;#8217;t match Google&amp;#8217;s global infrastructure footprint.
But we can be strategic, focused, and smart about where we compete and where we collaborate.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For Dutch companies, the question isn&amp;#8217;t &#34;hyperscaler or not?&#34; but &#34;which workloads, under what conditions, with what safeguards?&#34;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Start with your most sensitive data.
Build portable architectures. Support European alternatives where viable.
Maintain pragmatism, sometimes AWS is the right answer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Make it a conscious choice, not a default.
Your future flexibility depends on decisions you make today.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The grip of hyperscalers is strong, but not unbreakable.
Like our speed skaters finding the perfect line on the ice, it requires intention, investment, and a clear-eyed view of what sovereignty means for your specific business.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;We&amp;#8217;ve proven we can compete on the world stage when we focus on our strengths.
Digital sovereignty is our next race to win.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="Sovereignty" scheme="https://jdriven.com/blog/category/Sovereignty/" />
        <category term="Organizational structure" scheme="https://jdriven.com/blog/category/Sovereignty/Organizational-structure/" />
        <category term="Teachradar" scheme="https://jdriven.com/blog/tag/Teachradar/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/02/Running-COBOL-in-IntelliJ/</id>
        <title>Running COBOL in IntelliJ</title>
        <social:hashtags>#intellijidea #ide</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/02/Running-COBOL-in-IntelliJ/"/>
        <published>2026-02-21T06:00:00.000Z</published>
        <updated>2026-02-21T06:00:00.000Z</updated>
        <author>
            <name>Jacob</name>
            <uri>https://jdriven.com/blog/author/jacobvlingen</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Within the JVM community, once in a while somebody jokes about getting old and therefore needing to start learning COBOL.
Usually, someone responds that there are indeed some “precious older people” who wrote COBOL, or dare I say it, still write COBOL today.
For me, COBOL has always sounded like a relic of the past.
Something you read about, not something you seriously consider learning.
But lately, I have been hearing rumours about companies starting to recruit new employees specifically to fill COBOL vacancies.
That triggered me hard enough to start wondering.
Can I actually run COBOL on my own laptop today, and if so, can I have a reasonably nice developer experience while doing so?&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Within the JVM community, once in a while somebody jokes about getting old and therefore needing to start learning COBOL.
Usually, someone responds that there are indeed some “precious older people” who wrote COBOL, or dare I say it, still write COBOL today.
For me, COBOL has always sounded like a relic of the past.
Something you read about, not something you seriously consider learning.
But lately, I have been hearing rumours about companies starting to recruit new employees specifically to fill COBOL vacancies.
That triggered me hard enough to start wondering.
Can I actually run COBOL on my own laptop today, and if so, can I have a reasonably nice developer experience while doing so?&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;So I needed to tackle two things: installing a compiler to actually run COBOL code, and finding an IDE that would give me a decent developer experience.
For me, that effectively meant using IntelliJ.
It is the IDE I use every day, and thus I am very much familiar with it.
Ideally, that meant finding a COBOL plugin that would integrate reasonably well into my existing setup.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Luckily, finding a plugin took about five seconds.
The &lt;a href=&#34;https://plugins.jetbrains.com/plugin/24811-zowe-cobol-language-support&#34;&gt;Zowe™ COBOL Language Support&lt;/a&gt; plugin looked like exactly what I needed, so I installed it.
Next, I let ChatGPT generate a classic hello world example for me, because I definitely do not know any COBOL yet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-COBOL hljs&#34; data-lang=&#34;COBOL&#34;&gt;       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO.

       PROCEDURE DIVISION.
           DISPLAY &#34;Hello, world&#34;.
           STOP RUN.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And then I immediately hit a wall.
There was no run button in sight.
After a bit of investigation, it turned out that the plugin only provides TextMate-based syntax highlighting and LSP support.
That means decent code coloring and some basic validation, but no support for compiling or actually running COBOL code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;So I thought of something else.
What if I simply installed a compiler myself and used IntelliJ&amp;#8217;s &#34;Run Configuration&#34; to run a shell script that compiles and runs the currently opened COBOL file?
That way, it would still almost feel like native IntelliJ support.
Installing a COBOL compiler on macOS turned out to be trivial.
A single &lt;code&gt;brew install gnucobol&lt;/code&gt; was enough to get GNU COBOL up and running.
The only thing missing was the script itself, which could easily be generated by AI.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-shell hljs&#34; data-lang=&#34;shell&#34;&gt;#!/bin/zsh
set -e

error() {
  echo &#34;\033[31m$1\033[0m&#34;
}

if [ -z &#34;$1&#34; ]; then
  error &#34;No file provided.&#34;
  exit 1
fi

SRC=&#34;$1&#34;
EXT=&#34;${SRC##*.}&#34;

if [[ &#34;$EXT&#34; != &#34;cob&#34; &amp;amp;&amp;amp; &#34;$EXT&#34; != &#34;cbl&#34; ]]; then
  error &#34;This is not a COBOL file. Only .cob or .cbl extensions are supported!&#34;
  exit 1
fi

OUT=&#34;$(basename &#34;$SRC&#34; .$EXT)&#34;

cobc -x &#34;$SRC&#34; -o &#34;$OUT&#34;
./&#34;$OUT&#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;To make this work, I needed to figure out a way to select the current opened file.
After a bit of research, I discovered that IntelliJ provides a number of &lt;a href=&#34;https://www.jetbrains.com/help/idea/built-in-macros.html&#34;&gt;built-in macros&lt;/a&gt;, which are essentially variables you can use to define paths, options, and other command-line arguments.
The &lt;code&gt;$FilePath$&lt;/code&gt; macro, which resolves to the absolute path of the current file, looked like exactly what I needed.
Unfortunately, it turned out to be a dead end!
At the time of writing, macros are &lt;a href=&#34;https://youtrack-production.pub.aws.intellij.net/issue/IJPL-103033/Add-macros-support-for-script-path-script-options-in-Shell-Script-RC&#34;&gt;not supported&lt;/a&gt; in Shell Script run configurations, which meant this approach would not work after all.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Bummer.
So what now?
Luckily, ChatGPT pointed me to IntelliJ’s &lt;a href=&#34;https://www.jetbrains.com/help/idea/configuring-third-party-tools.html&#34;&gt;External Tools&lt;/a&gt; feature.
This allows you to configure third-party command-line applications and run them directly from IntelliJ.
And more importantly, external tools do support macros.
That was exactly what I needed!
So I wired it up, and configured the tool like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/02/configure-run-cobol-as-external-tool.png&#34; alt=&#34;configure run cobol as external tool&#34;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Then I had to go to the Tools → External Tools → Run COBOL menu, to run a COBOL file:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-shell hljs&#34; data-lang=&#34;shell&#34;&gt;/path/to/run.sh /path/to/hello.cob
Hello, world

Process finished with exit code 0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Awesome!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;But still, I had to click three times to run the script&amp;#8230;&amp;#8203;
So I figured, there&amp;#8217;s another thing I can do to make it even more fleshed out.
At the top of the IDE sits the toolbar, which is fully customizable.
For every action IntelliJ knows about, you can add a custom button to the toolbar.
That meant I could create a dedicated action, link it to my “Run COBOL” external tool, and place it right where a run button should be.
To make it complete, I used the COBOL rhino as the button icon:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;/blog/uploads/2026/02/run-cobol-button.png&#34; alt=&#34;run cobol button&#34;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And there you have it: a one-click button I can press anytime I want.
With that, my journey to setting up the environment is complete.
Now unto the next task, starting to learn the language!&lt;/p&gt;
&lt;/div&gt;</content>
        <category term="COBOL" scheme="https://jdriven.com/blog/category/COBOL/" />
        <category term="IntelliJ IDEA" scheme="https://jdriven.com/blog/category/COBOL/IntelliJ-IDEA/" />
        <category term="intellij idea" scheme="https://jdriven.com/blog/tag/intellij-idea/" />
        <category term="ide" scheme="https://jdriven.com/blog/tag/ide/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-10-Threads/</id>
        <title>From Java to Kotlin – Part X: Virtual Threads and Coroutines</title>
        <social:hashtags>#java #kotlin #concurrency</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-10-Threads/"/>
        <published>2026-02-20T08:00:00.000Z</published>
        <updated>2026-02-20T08:00:00.000Z</updated>
        <author>
            <name>Justus</name>
            <uri>https://jdriven.com/blog/author/jbrugman</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 10&lt;/strong&gt;: You need to do many things at once.
Mostly waiting. Sometimes working.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 10&lt;/strong&gt;: You need to do many things at once.
Mostly waiting. Sometimes working.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The problem&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Modern apps spend a lot of time waiting:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Waiting for a web request&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Waiting for a database query&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Waiting for a file or message from another system&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;During I/O waits, threads are blocked, which often leads to low CPU utilization.
So the real question is not:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;“How fast can we run code?”&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;but&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;“How can we wait efficiently?”&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Java way (classic threads)&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java’s old answer: give each task a &lt;strong&gt;thread&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Each thread blocks while waiting&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The OS schedules threads for you&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Works fine for a few tasks&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Problems appear with &lt;strong&gt;thousands of tasks&lt;/strong&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Threads are relatively heavy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Memory usage grows fast&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Shared data = easy to make mistakes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Debugging is tricky&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Think of threads as &lt;strong&gt;people waiting on chairs&lt;/strong&gt;. Each blocks a chair while they wait — expensive if the room is full.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Java way (Java 21+): Virtual Threads&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java 21 introduced &lt;strong&gt;virtual threads&lt;/strong&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Look like threads in code&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JVM schedules them efficiently&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Blocking a virtual thread doesn’t block an OS thread&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can create thousands with a significantly lower memory overhead&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;Thread virtualThread = Thread.startVirtualThread(() -&amp;gt;
    System.out.println(message + &#34; from a virtual thread!&#34;)
);

virtualThread.join();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Here: the code still “blocks,” but it’s cheap.
Your mental model stays the same: &lt;strong&gt;threads are doing work&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Virtual threads are perfect if:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Most code is I/O-bound&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You have existing blocking APIs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You don’t want to rewrite everything&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Kotlin way: Coroutines&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin asks a different question:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;“What does it &lt;strong&gt;mean&lt;/strong&gt; to wait in code?”&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Coroutines are &lt;strong&gt;not threads&lt;/strong&gt;. They are small, lightweight units of work that &lt;strong&gt;pause and resume&lt;/strong&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;While waiting, no thread is blocked&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Waiting becomes explicit in the API&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cancellation and lifetimes are structured&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;fun greeting(message: String) = runBlocking {
    println(message)

    launch {
        println(&#34;$message from a coroutine!&#34;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;runBlocking&lt;/code&gt; = create a coroutine scope from a blocking (non-coroutine) context. &lt;a href=&#34;#nuance&#34;&gt;(* Nuance)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;launch&lt;/code&gt; = start a coroutine&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;No thread is wasted. Waiting is visible, explicit, and safe.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Suspending functions&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In Kotlin, waiting is part of the type system:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;suspend fun loadUser(): User&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This tells the caller: “This function may pause.”
Hidden delays? Gone. Cancellation? Easier.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Coroutines encourage &lt;strong&gt;structured concurrency&lt;/strong&gt;: every coroutine has a parent, lifetimes are bounded, leaks are prevented.
Virtual threads do not provide this.
Comparable behavior requires explicit use of StructuredTaskScope.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Who schedules what?&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Key similarity&lt;/strong&gt;: both models abstract away low-level thread management.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Key difference&lt;/strong&gt;: who owns and exposes the scheduling model.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Virtual threads:
Scheduling is handled transparently by the JVM.
Developers write blocking-style code, and the JVM decides when virtual threads run.
Threads are cheap, but scheduling is largely an implementation detail of the runtime.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Coroutines:
Scheduling is handled by the coroutine framework.
Coroutines run on threads, but a coroutine dispatcher determines when and where a coroutine executes.
Developers can explicitly choose or configure this dispatcher.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Note: Coroutines still execute on underlying threads, but the coroutine scheduler controls when a coroutine is active.
With virtual threads, this control resides entirely within the JVM.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&#34;tableblock frame-all grid-all stretch&#34;&gt;
&lt;colgroup&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Feature&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Threads&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Virtual Threads (Java 21+)&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Coroutines (Kotlin)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Resource cost&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Heavy (1 MB+ per thread)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Light (a few KB per thread)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Very light (bytes per coroutine)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Blocking&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Blocks OS thread&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Blocks virtual thread (cheap)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Suspends coroutine, &lt;strong&gt;does not block thread&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Scheduling&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;OS / JVM&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;JVM schedules virtual threads&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Kotlin scheduler (&lt;code&gt;Dispatchers&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Mental model&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Threads are doing work&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Threads are doing work, but cheap&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Coroutine pauses/resumes; waiting is explicit&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Structured concurrency&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅ (when using &lt;code&gt;StructuredTaskScope&lt;/code&gt; since Java 21, optional but without it ❌)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✅ Parent-child relationship, scoped lifetimes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Typical usage&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Multi-threaded code&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Existing blocking code with many tasks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Async I/O, structured concurrency, modern Kotlin apps&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;runBlocking&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;N/A&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;N/A&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Used only for &lt;strong&gt;bridging blocking code&lt;/strong&gt;, e.g., in &lt;code&gt;main()&lt;/code&gt; or tests; not standard in production&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;paragraph note&#34;&gt;
&lt;p&gt;Analogy:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Threads = people blocking chairs while waiting&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Virtual Threads = same people, chairs magically multiply&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Coroutines = people read a book while waiting, then continue&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Interoperability reality&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin runs on the JVM, so:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Coroutines &lt;strong&gt;can&lt;/strong&gt; run on virtual threads&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Virtual threads &lt;strong&gt;don’t replace&lt;/strong&gt; &lt;code&gt;suspend&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Libraries still need to be coroutine-aware&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Using virtual threads doesn’t remove the need to think about async boundaries.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Takeaway&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java asks:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;“How can we make blocking cheap?”&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin asks:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;“What does it really mean to wait in code?”&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Virtual threads: make blocking cheap.
Perfect if you have existing blocking code and many I/O-bound tasks, without consuming too much memory.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Coroutines: make waiting explicit and safe.
Functions that can pause are visible via suspend, and structured concurrency helps manage lifetimes and cancellation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4 id=&#34;nuance&#34;&gt;Extra nuance – runBlocking&lt;/h4&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;runBlocking is mainly used to bridge blocking code and coroutines, e.g., in main() or tests.
In real Kotlin applications, you usually use CoroutineScope with &lt;code&gt;launch&lt;/code&gt; or &lt;code&gt;async&lt;/code&gt; without &lt;code&gt;runBlocking&lt;/code&gt;.
RunBlocking examples may be confusing; it is not the standard way to write coroutines in production code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;// Application-wide scope
val appScope = CoroutineScope(Dispatchers.Default)

fun main() {
  appScope.launch {
    println(&#34;Coroutine started on thread: ${Thread.currentThread().name}&#34;)
    delay(1000L)
    println(&#34;Coroutine finished after 1 second&#34;)
  }

  println(&#34;Main function continues immediately&#34;)

  // Keep the JVM alive for demonstration purposes only
  Thread.sleep(1500L)

  // Proper shutdown
  appScope.cancel()
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;What happens here:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CoroutineScope(Dispatchers.Default)&lt;/code&gt; + &lt;code&gt;Dispatchers.Default)&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Creates an application-level coroutine scope backed by a shared thread pool.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The lifecycle must be managed explicitly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;launch { &amp;#8230;&amp;#8203; }&lt;/p&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;starts a coroutine without blocking a thread.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;delay(1000L)&lt;/p&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Suspends the coroutine without blocking a thread. The underlying thread is free to run other coroutines.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The main function continues immediately.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Thread.sleep(1500L)&lt;/p&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Used here only to keep the JVM alive for demonstration purposes.
In real applications (e.g., servers, Android apps), lifecycle management keeps the application running, so this is usually unnecessary.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;In short:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java = “I’m blocking, but it’s cheap.”&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin = “I’m waiting, and everyone knows it.”&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Code comparison: Virtual Threads vs Coroutines&lt;/h3&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Java (Virtual Threads)&lt;/h4&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;Thread virtualThread = Thread.startVirtualThread(() -&amp;gt; {
    try {
        Thread.sleep(500); // simulate waiting for I/O
        System.out.println(&#34;Hello from a virtual thread!&#34;);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new RuntimeException(e);
    }
});

virtualThread.join();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Thread.sleep&lt;/code&gt; blocks, but it’s cheap&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JVM schedules threads for you&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mental model: threads are doing work&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect3&#34;&gt;
&lt;h4&gt;Kotlin (Coroutines)&lt;/h4&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;fun greeting() = runBlocking {
    delay(500) // simulate waiting for I/O
    println(&#34;Hello from the main coroutine&#34;)

    launch {
        delay(500)
        println(&#34;Hello from a coroutine!&#34;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;delay&lt;/code&gt; suspends, does &lt;strong&gt;not&lt;/strong&gt; block&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;launch&lt;/code&gt; starts a coroutine that pauses and resumes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No wasted threads; efficient scaling&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Waiting is explicit (&lt;code&gt;suspend&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Bonus: Kotlin Coroutines – Cheat Sheet&lt;/h3&gt;
&lt;table class=&#34;tableblock frame-all grid-all stretch&#34;&gt;
&lt;colgroup&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;col style=&#34;width: 25%;&#34;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Concept&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;What it does&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Blocks thread?&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Typical usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;suspend fun&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Marks a function that can pause&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Functions that perform I/O, waiting, or long-running tasks&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;launch { &amp;#8230;&amp;#8203; }&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Starts a coroutine&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Fire-and-forget tasks, background work&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;async { &amp;#8230;&amp;#8203; }&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Starts a coroutine returning a result (&lt;code&gt;Deferred&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Parallel computations, combine results with &lt;code&gt;await()&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;runBlocking { &amp;#8230;&amp;#8203; }&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Starts a coroutine in a blocking context&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Yes&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Only in &lt;code&gt;main()&lt;/code&gt; or tests, not standard in production&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;CoroutineScope&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Manages coroutine lifetimes&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;–&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Application- or component-wide coroutines&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;Dispatchers.Default&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Dispatcher for CPU-intensive tasks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Computation, background processing&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;Dispatchers.IO&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Dispatcher for I/O-intensive tasks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Database, network, file I/O&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;Dispatchers.Main&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Dispatcher for UI thread (Android)&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;UI updates, user interactions&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;delay(time)&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Pauses a coroutine without blocking a thread&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Simulating wait, sleep-like behavior&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;admonitionblock note&#34;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&#34;icon&#34;&gt;
&lt;i class=&#34;fa icon-note&#34; title=&#34;Note&#34;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&#34;content&#34;&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Deferred&amp;lt;T&amp;gt;&lt;/code&gt; represents a coroutine that will produce a result of type &lt;code&gt;T&lt;/code&gt; in the future.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;await()&lt;/code&gt; suspends the coroutine until the &lt;code&gt;Deferred&lt;/code&gt; result is ready, without blocking the underlying thread.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Typical usage: &lt;code&gt;val result = async { computeSomething() }.await()&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Sources / Links&lt;/h3&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html&#34;&gt;Java Virtual Threads – Official Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.oracle.com/javase/tutorial/essential/concurrency/threads.html&#34;&gt;Java Threads – Official Tutorial&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/coroutines-overview.html&#34;&gt;Kotlin Coroutines – Official Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://jdriven.com/blog/2021/07/Propagating-the-Spring-SecurityContext-to-your-Kotlin-Coroutines&#34;&gt;Riccardo&amp;#8217;s blogpost&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="java" scheme="https://jdriven.com/blog/category/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/category/java/kotlin/" />
        <category term="Software development" scheme="https://jdriven.com/blog/category/java/kotlin/Software-development/" />
        <category term="java" scheme="https://jdriven.com/blog/tag/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/tag/kotlin/" />
        <category term="concurrency" scheme="https://jdriven.com/blog/tag/concurrency/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-9-Statics/</id>
        <title>From Java to Kotlin – Part IX: Statics and Companion Objects</title>
        <social:hashtags>#java #kotlin</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-9-Statics/"/>
        <published>2026-02-19T08:00:00.000Z</published>
        <updated>2026-02-19T08:00:00.000Z</updated>
        <author>
            <name>Justus</name>
            <uri>https://jdriven.com/blog/author/jbrugman</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 9&lt;/strong&gt;:
You need something that belongs to the class.
Not to an instance.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 9&lt;/strong&gt;:
You need something that belongs to the class.
Not to an instance.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The problem&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Sometimes, behavior or data does not belong to an object.
It belongs to the &lt;strong&gt;type&lt;/strong&gt; itself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Constants.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Factory methods.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Utility logic that is conceptually “part of the class”.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In Java, this is easy.
Almost too easy.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Java way&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java has &lt;code&gt;static&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;public class User {

    public static final int MAX_NAME_LENGTH = 50;

    public static User anonymous() {
        return new User(&#34;Anonymous&#34;);
    }

    private final String name;

    public User(String name) {
        this.name = name;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;User user = User.anonymous();
int max = User.MAX_NAME_LENGTH;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Clear.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Familiar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unquestioned.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Kotlin way&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin does not have &lt;code&gt;static&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Instead, it has &lt;strong&gt;companion objects&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;class User(val name: String) {

    companion object {
        const val MAX_NAME_LENGTH = 50

        fun anonymous(): User =
            User(&#34;Anonymous&#34;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val user = User.anonymous()
val max = User.MAX_NAME_LENGTH&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Looks similar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feels similar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;But it’s not the same thing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;What is a companion object?&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A companion object is:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A real object&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tied to the class&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Created once&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Able to implement interfaces&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Able to be passed around&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In other words:
- It’s not a language trick.
- It’s an object with a name (even if you don’t give it one).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Why not just static?&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Because static breaks object composition.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;That usually leads to the obvious question: &lt;strong&gt;Why?&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Object composition works by wiring behavior through replaceable dependencies.
Static breaks object composition because it cannot be substituted.
Static methods and fields are globally fixed: they cannot be passed around, mocked, or injected.
Once you depend on a static, you are hard-wired to that implementation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;em&gt;Static creates hidden, global dependencies — which is the opposite of composition.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With companion objects, you can:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Inject behavior&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mock logic in tests&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Implement interfaces&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keep related logic grouped with the type&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Things &lt;code&gt;static&lt;/code&gt; simply cannot do.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Java interoperability&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;From Java, a companion object looks like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;User.Companion.anonymous();
int max = User.Companion.MAX_NAME_LENGTH;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;If that feels verbose, Kotlin has you covered:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;class User(val name: String) {

    companion object {
        @JvmStatic
        fun anonymous(): User =
            User(&#34;Anonymous&#34;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Now Java can call:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;User.anonymous();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Why this matters&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin replaces a language keyword with a concept.
That concept is more powerful — and more consistent.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You don’t lose statics.
You gain objects.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Takeaway&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java answers the question:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;“Does this belong to the class?”&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin asks another one:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;“What kind of thing is this, really?”&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Final note&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;@JvmStatic&lt;/code&gt; tells the Kotlin compiler to generate a real Java static member…&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Without it:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;class User {
  companion object {
    fun anonymous(): User = User()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java would see: &lt;code&gt;User.Companion.anonymous();&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With &lt;code&gt;@JvmStatic&lt;/code&gt; Java sees:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;User.anonymous();              // generated static method
User.Companion.anonymous();    // still exists&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;div class=&#34;title&#34;&gt;Future direction (Kotlin Dev Day, thank you &lt;a href=&#34;https://jdriven.com/blog/author/jacobvlingen&#34;&gt;Jacob&lt;/a&gt;!)&lt;/div&gt;
&lt;p&gt;Perhaps interesting to know: during Kotlin Dev Day, JetBrains mentioned that in the future companion objects might get a “static sibling” that works almost like Java statics:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;class User(val name: String) {
    companion fun anonymous(): User = User(&#34;Anonymous&#34;)

    companion {
        fun evenMoreAnonymous(): User = User(&#34;Anonymous&#34;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This is not yet finalized.
JetBrains explained that in 95% of public GitHub code, the “object” part of companion objects is not used.
The idea is to make the &lt;code&gt;object&lt;/code&gt; part optional, as many developers find it confusing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In short: Kotlin is exploring a way to make companion objects simpler and more accessible, while retaining their power.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Sources / Links&lt;/h3&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/object-declarations.html&#34;&gt;Kotlin Object Declarations&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/java-to-kotlin-interop.html&#34;&gt;Java Interop&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://jdriven.com/blog/2019/10/Kotlin-method-reference-to-companion-object-function&#34;&gt;Riccardo&amp;#8217;s blogpost&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="java" scheme="https://jdriven.com/blog/category/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/category/java/kotlin/" />
        <category term="Software development" scheme="https://jdriven.com/blog/category/java/kotlin/Software-development/" />
        <category term="java" scheme="https://jdriven.com/blog/tag/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/tag/kotlin/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-8-Elvis-Operator/</id>
        <title>From Java to Kotlin – Part VIII: The Ternary operator vs the Elvis Operator</title>
        <social:hashtags>#java #kotlin</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-8-Elvis-Operator/"/>
        <published>2026-02-18T08:00:00.000Z</published>
        <updated>2026-02-18T08:00:00.000Z</updated>
        <author>
            <name>Justus</name>
            <uri>https://jdriven.com/blog/author/jbrugman</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 8&lt;/strong&gt;:
You want a sensible default.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;If something is missing.
Or null.
Or both.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 8&lt;/strong&gt;:
You want a sensible default.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;If something is missing.
Or null.
Or both.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The problem&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You have a nullable value.
You want to use it if it exists.
Otherwise, fall back to something reasonable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This is not advanced logic.
It’s everyday defensive programming.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Yet in Java, it rarely looks elegant.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Java way&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java has the &lt;strong&gt;ternary operator&lt;/strong&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;String name = user.getName();
int length = name != null ? name.length() : -1;
System.out.println(&#34;Name length: &#34; + length);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This works.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It’s explicit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It’s also… noisy.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Things get worse when method calls are involved:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;int length = user.getName() != null
    ? user.getName().length()
    : -1;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Or when you start caching intermediate values just to stay readable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java does not have a null-aware operator.
So you end up spelling out the null check every time.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Kotlin way&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin combines &lt;strong&gt;safe calls&lt;/strong&gt; with the &lt;strong&gt;Elvis operator&lt;/strong&gt; (&lt;code&gt;?:&lt;/code&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val length = name?.length ?: &#34;Unknown&#34;
println(&#34;Name length: $length&#34;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Read out loud, this says:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;“Give me the length if &lt;code&gt;name&lt;/code&gt; is not null — otherwise, use &lt;code&gt;&#34;Unknown&#34;&lt;/code&gt;.”&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;No temporary variables.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No repeated method calls.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No ceremony.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Just intent.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Why is it called the Elvis operator?&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Because &lt;code&gt;?:&lt;/code&gt; looks like Elvis Presley.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Really.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;It&amp;#8217;s the hair&amp;#8230;&amp;#8203;
So now you will never unsee this again.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;One of my colleagues (hi &lt;a href=&#34;https://jdriven.com/blog/author/jasper-bogers&#34;&gt;Jasper&lt;/a&gt; 👋) makes sure this is pointed out every single time.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Kotlin does &lt;strong&gt;not&lt;/strong&gt; have a ternary operator&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And it doesn’t need one.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In Kotlin:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;if&lt;/code&gt; is an &lt;strong&gt;expression&lt;/strong&gt;, not a statement (*1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;when&lt;/code&gt; is an &lt;strong&gt;expression&lt;/strong&gt; (*1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;?:&lt;/code&gt; handles null-coalescing&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;So instead of:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;int x = condition ? a : b;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You write:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val x = if (condition) a else b&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The language stays smaller.
The intent stays clearer.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Why this matters&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Null-handling logic is everywhere.
The less visual noise it creates, the easier it is to reason about your code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The Elvis operator doesn’t add power.
It removes friction.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Takeaway&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java makes you &lt;strong&gt;spell out&lt;/strong&gt; your intent.
Kotlin lets you &lt;strong&gt;express&lt;/strong&gt; it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;And yes — it really does look like Elvis.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Final note&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;(*1) As my collegue &lt;a href=&#34;https://jdriven.com/blog/author/riccardo-lippolis&#34;&gt;Riccardo&lt;/a&gt; pointed out:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;if&lt;/code&gt; and &lt;code&gt;when&lt;/code&gt; are both expressions ànd statements in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;There is actually also a slight difference between the two:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val isEven = Random.nextInt() % 2 == 0
// ✅ if as statement, no else branch, compiles fine
if (isEven) {
  println(&#34;Even!&#34;)
}
// 🍕🍍if as expression, no else branch, does NOT compile
val isOdd = if (isEven) {
  false
}
// ✅ when as statement, not all branches covered, compiles fine
when {
  isEven -&amp;gt; println(&#34;Even!&#34;)
}
// 🍕🍍when as expression, not all branches covered, does NOT compile
val isOdd2 = when {
  isEven -&amp;gt; false
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Sources / Links&lt;/h3&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/null-safety.html&#34;&gt;Kotlin Null Safety&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/control-flow.html&#34;&gt;Kotlin Control Flow&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="java" scheme="https://jdriven.com/blog/category/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/category/java/kotlin/" />
        <category term="Software development" scheme="https://jdriven.com/blog/category/java/kotlin/Software-development/" />
        <category term="java" scheme="https://jdriven.com/blog/tag/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/tag/kotlin/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-7-Null-Safety/</id>
        <title>From Java to Kotlin – Part VII: Null Safety</title>
        <social:hashtags>#java #kotlin</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-7-Null-Safety/"/>
        <published>2026-02-17T08:00:00.000Z</published>
        <updated>2026-02-17T08:00:00.000Z</updated>
        <author>
            <name>Justus</name>
            <uri>https://jdriven.com/blog/author/jbrugman</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 7&lt;/strong&gt;:
Nulls exist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Ignoring them doesn’t make them go away.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 7&lt;/strong&gt;:
Nulls exist.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Ignoring them doesn’t make them go away.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The problem&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;NullPointerExceptions are still the most popular Java exception.
For all the wrong reasons.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Okay, the NullPointerExceptions aren’t really the problem — They&amp;#8217;re the symptom.
And Java makes it very easy to create that symptom.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In Java, any reference can be null unless you actively prevent it.
Fields, method parameters, return values, collections… all fair game.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;String city = user.getAddress().getCity().toUpperCase();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As you can see, this can explode at three different points, and the compiler is totally fine with it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;user.getAddress()&lt;/code&gt; when &lt;code&gt;user&lt;/code&gt; is null&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;user.getAddress().getCity()&lt;/code&gt; when &lt;code&gt;user.getAddress()&lt;/code&gt; is null&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the return value of &lt;code&gt;getCity()&lt;/code&gt; when you immediately dereference it (&lt;code&gt;getCity().toUpperCase()&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Kotlin way&lt;/h3&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val name: String? = null
println(name?.length ?: &#34;Unknown&#34;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Nullability is explicit.
Handled by the compiler.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Why this matters&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;You see nulls &lt;strong&gt;before&lt;/strong&gt; runtime.
Not after deployment.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Takeaway&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Kotlin turns runtime bugs into compile-time decisions.
That’s a trade worth making.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Sources / Links&lt;/h3&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/null-safety.html&#34;&gt;Kotlin Null Safety&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="java" scheme="https://jdriven.com/blog/category/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/category/java/kotlin/" />
        <category term="Software development" scheme="https://jdriven.com/blog/category/java/kotlin/Software-development/" />
        <category term="java" scheme="https://jdriven.com/blog/tag/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/tag/kotlin/" />
    </entry>
    <entry>
        <id>https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-6-Higher-Order-Functions/</id>
        <title>From Java to Kotlin – Part VI: Higher-Order Functions Without Fear</title>
        <social:hashtags>#java #kotlin</social:hashtags>
        <link rel="alternate" href="https://jdriven.com/blog/2026/02/Java-To-Kotlin-Series-6-Higher-Order-Functions/"/>
        <published>2026-02-16T08:00:00.000Z</published>
        <updated>2026-02-16T08:00:00.000Z</updated>
        <author>
            <name>Justus</name>
            <uri>https://jdriven.com/blog/author/jbrugman</uri>
        </author>
        <summary type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 6&lt;/strong&gt;:
Passing functions around sounds scary.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Until you try it.&lt;/p&gt;
&lt;/div&gt;</summary>
        <content type="html">&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Considering a move to Kotlin? Coming from a Java background?
In this short series of blog posts, I’ll take a look at familiar, straightforward Java concepts and demonstrate how you can approach them in Kotlin.
While many of these points have already been discussed in earlier posts by colleagues, my focus is simple: how you used to do it in Java, and how you do it in Kotlin.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Case 6&lt;/strong&gt;:
Passing functions around sounds scary.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Until you try it.&lt;/p&gt;
&lt;/div&gt;
&lt;span id=&#34;more&#34;&gt;&lt;/span&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Remember, a higher-order function just is a function that takes another function as a parameter or returns one.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The problem&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;We want behavior as a parameter.
Simple math.
Different operations.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Java way&lt;/h3&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;// Higher-order function with a lambda
private int calculate(int a, int b, Operation operation) {
  return operation.apply(a, b);
}
@FunctionalInterface
interface Operation {
  int apply(int a, int b);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-java hljs&#34; data-lang=&#34;java&#34;&gt;int r = calculate(5, 3, (a, b) -&amp;gt; a * b);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This works, but requires:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a functional interface&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;extra ceremony&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;The Kotlin way&lt;/h3&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;// Higher-order function with a lambda
fun calculate(a: Int, b: Int, operation: (Int, Int) -&amp;gt; Int): Int {
  return operation(a, b)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-kotlin hljs&#34; data-lang=&#34;kotlin&#34;&gt;val r = calculate(5, 3) { a, b -&amp;gt; a * b }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Functions are first-class citizens.
No interfaces required.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Why this matters&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Less ceremony lowers the barrier.
Lower barriers lead to better abstractions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Takeaway&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Java allows functional programming.
Kotlin encourages it.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3&gt;Sources / Links&lt;/h3&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://kotlinlang.org/docs/lambdas.html&#34;&gt;Kotlin Lambdas&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://medium.com/@nagarjun_nagesh/higher-order-functions-in-java-4e7375fea156&#34;&gt;Higher-Order Functions in Java&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
        <category term="java" scheme="https://jdriven.com/blog/category/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/category/java/kotlin/" />
        <category term="Software development" scheme="https://jdriven.com/blog/category/java/kotlin/Software-development/" />
        <category term="java" scheme="https://jdriven.com/blog/tag/java/" />
        <category term="kotlin" scheme="https://jdriven.com/blog/tag/kotlin/" />
    </entry>
</feed>
